Commit 1db0a7ffcee6f6ae6bd3298a960665378d304fa1
1 parent
acf8d18b
JSONHandler: rework dictionary and array handlers
Showing
4 changed files
with
89 additions
and
40 deletions
include/qpdf/JSONHandler.hh
| @@ -31,6 +31,11 @@ | @@ -31,6 +31,11 @@ | ||
| 31 | #include <stdexcept> | 31 | #include <stdexcept> |
| 32 | #include <memory> | 32 | #include <memory> |
| 33 | 33 | ||
| 34 | +// This class allows a sax-like walk through a JSON object with | ||
| 35 | +// functionality that mostly mirrors QPDFArgParser. It is primarily | ||
| 36 | +// here to facilitate automatic generation of some of the code to help | ||
| 37 | +// keep QPDFJob json consistent with command-line arguments. | ||
| 38 | + | ||
| 34 | class JSONHandler | 39 | class JSONHandler |
| 35 | { | 40 | { |
| 36 | public: | 41 | public: |
| @@ -53,7 +58,8 @@ class JSONHandler | @@ -53,7 +58,8 @@ class JSONHandler | ||
| 53 | // certain type. JSONHandler::Error is thrown otherwise. Multiple | 58 | // certain type. JSONHandler::Error is thrown otherwise. Multiple |
| 54 | // handlers may be registered, which allows the object to be of | 59 | // handlers may be registered, which allows the object to be of |
| 55 | // various types. If an anyHandler is added, no other handler will | 60 | // various types. If an anyHandler is added, no other handler will |
| 56 | - // be called. | 61 | + // be called. There is no "final" handler -- if the top-level is a |
| 62 | + // dictionary or array, just use its end handler. | ||
| 57 | 63 | ||
| 58 | typedef std::function<void( | 64 | typedef std::function<void( |
| 59 | std::string const& path, JSON value)> json_handler_t; | 65 | std::string const& path, JSON value)> json_handler_t; |
| @@ -80,19 +86,18 @@ class JSONHandler | @@ -80,19 +86,18 @@ class JSONHandler | ||
| 80 | QPDF_DLL | 86 | QPDF_DLL |
| 81 | void addBoolHandler(bool_handler_t fn); | 87 | void addBoolHandler(bool_handler_t fn); |
| 82 | 88 | ||
| 83 | - // Returns a reference to a map: keys are expected object keys, | ||
| 84 | - // and values are handlers for that object. | ||
| 85 | QPDF_DLL | 89 | QPDF_DLL |
| 86 | - std::map<std::string, std::shared_ptr<JSONHandler>>& addDictHandlers(); | ||
| 87 | - | ||
| 88 | - // Apply the given handler to any key not explicitly in dict | ||
| 89 | - // handlers. | 90 | + void addDictHandlers(void_handler_t start_fn, void_handler_t end_fn); |
| 91 | + QPDF_DLL | ||
| 92 | + void addDictKeyHandler( | ||
| 93 | + std::string const& key, std::shared_ptr<JSONHandler>); | ||
| 90 | QPDF_DLL | 94 | QPDF_DLL |
| 91 | void addFallbackDictHandler(std::shared_ptr<JSONHandler>); | 95 | void addFallbackDictHandler(std::shared_ptr<JSONHandler>); |
| 92 | 96 | ||
| 93 | - // Apply the given handler to each element of the array. | ||
| 94 | QPDF_DLL | 97 | QPDF_DLL |
| 95 | - void addArrayHandler(std::shared_ptr<JSONHandler>); | 98 | + void addArrayHandlers(void_handler_t start_fn, |
| 99 | + void_handler_t end_fn, | ||
| 100 | + std::shared_ptr<JSONHandler> item_handlers); | ||
| 96 | 101 | ||
| 97 | // Apply handlers recursively to a JSON object. | 102 | // Apply handlers recursively to a JSON object. |
| 98 | QPDF_DLL | 103 | QPDF_DLL |
| @@ -108,7 +113,12 @@ class JSONHandler | @@ -108,7 +113,12 @@ class JSONHandler | ||
| 108 | null_handler(nullptr), | 113 | null_handler(nullptr), |
| 109 | string_handler(nullptr), | 114 | string_handler(nullptr), |
| 110 | number_handler(nullptr), | 115 | number_handler(nullptr), |
| 111 | - bool_handler(nullptr) | 116 | + bool_handler(nullptr), |
| 117 | + dict_start_handler(nullptr), | ||
| 118 | + dict_end_handler(nullptr), | ||
| 119 | + array_start_handler(nullptr), | ||
| 120 | + array_end_handler(nullptr), | ||
| 121 | + final_handler(nullptr) | ||
| 112 | { | 122 | { |
| 113 | } | 123 | } |
| 114 | 124 | ||
| @@ -117,9 +127,14 @@ class JSONHandler | @@ -117,9 +127,14 @@ class JSONHandler | ||
| 117 | string_handler_t string_handler; | 127 | string_handler_t string_handler; |
| 118 | string_handler_t number_handler; | 128 | string_handler_t number_handler; |
| 119 | bool_handler_t bool_handler; | 129 | bool_handler_t bool_handler; |
| 130 | + void_handler_t dict_start_handler; | ||
| 131 | + void_handler_t dict_end_handler; | ||
| 132 | + void_handler_t array_start_handler; | ||
| 133 | + void_handler_t array_end_handler; | ||
| 134 | + void_handler_t final_handler; | ||
| 120 | std::map<std::string, std::shared_ptr<JSONHandler>> dict_handlers; | 135 | std::map<std::string, std::shared_ptr<JSONHandler>> dict_handlers; |
| 121 | std::shared_ptr<JSONHandler> fallback_dict_handler; | 136 | std::shared_ptr<JSONHandler> fallback_dict_handler; |
| 122 | - std::shared_ptr<JSONHandler> array_handler; | 137 | + std::shared_ptr<JSONHandler> array_item_handler; |
| 123 | }; | 138 | }; |
| 124 | 139 | ||
| 125 | class Members | 140 | class Members |
libqpdf/JSONHandler.cc
| @@ -46,10 +46,18 @@ JSONHandler::addBoolHandler(bool_handler_t fn) | @@ -46,10 +46,18 @@ JSONHandler::addBoolHandler(bool_handler_t fn) | ||
| 46 | this->m->h.bool_handler = fn; | 46 | this->m->h.bool_handler = fn; |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | -std::map<std::string, std::shared_ptr<JSONHandler>>& | ||
| 50 | -JSONHandler::addDictHandlers() | 49 | +void |
| 50 | +JSONHandler::addDictHandlers(void_handler_t start_fn, void_handler_t end_fn) | ||
| 51 | +{ | ||
| 52 | + this->m->h.dict_start_handler = start_fn; | ||
| 53 | + this->m->h.dict_end_handler = end_fn; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +void | ||
| 57 | +JSONHandler::addDictKeyHandler( | ||
| 58 | + std::string const& key, std::shared_ptr<JSONHandler> dkh) | ||
| 51 | { | 59 | { |
| 52 | - return this->m->h.dict_handlers; | 60 | + this->m->h.dict_handlers[key] = dkh; |
| 53 | } | 61 | } |
| 54 | 62 | ||
| 55 | void | 63 | void |
| @@ -59,9 +67,13 @@ JSONHandler::addFallbackDictHandler(std::shared_ptr<JSONHandler> fdh) | @@ -59,9 +67,13 @@ JSONHandler::addFallbackDictHandler(std::shared_ptr<JSONHandler> fdh) | ||
| 59 | } | 67 | } |
| 60 | 68 | ||
| 61 | void | 69 | void |
| 62 | -JSONHandler::addArrayHandler(std::shared_ptr<JSONHandler> ah) | 70 | +JSONHandler::addArrayHandlers(void_handler_t start_fn, |
| 71 | + void_handler_t end_fn, | ||
| 72 | + std::shared_ptr<JSONHandler> ah) | ||
| 63 | { | 73 | { |
| 64 | - this->m->h.array_handler = ah; | 74 | + this->m->h.array_start_handler = start_fn; |
| 75 | + this->m->h.array_end_handler = end_fn; | ||
| 76 | + this->m->h.array_item_handler = ah; | ||
| 65 | } | 77 | } |
| 66 | 78 | ||
| 67 | void | 79 | void |
| @@ -95,9 +107,9 @@ JSONHandler::handle(std::string const& path, JSON j) | @@ -95,9 +107,9 @@ JSONHandler::handle(std::string const& path, JSON j) | ||
| 95 | this->m->h.bool_handler(path, bvalue); | 107 | this->m->h.bool_handler(path, bvalue); |
| 96 | handled = true; | 108 | handled = true; |
| 97 | } | 109 | } |
| 98 | - if ((this->m->h.fallback_dict_handler.get() || | ||
| 99 | - (! this->m->h.dict_handlers.empty())) && j.isDictionary()) | 110 | + if (this->m->h.dict_start_handler && j.isDictionary()) |
| 100 | { | 111 | { |
| 112 | + this->m->h.dict_start_handler(path); | ||
| 101 | std::string path_base = path; | 113 | std::string path_base = path; |
| 102 | if (path_base != ".") | 114 | if (path_base != ".") |
| 103 | { | 115 | { |
| @@ -126,22 +138,19 @@ JSONHandler::handle(std::string const& path, JSON j) | @@ -126,22 +138,19 @@ JSONHandler::handle(std::string const& path, JSON j) | ||
| 126 | i->second->handle(path_base + k, v); | 138 | i->second->handle(path_base + k, v); |
| 127 | } | 139 | } |
| 128 | }); | 140 | }); |
| 129 | - | ||
| 130 | - // Set handled = true even if we didn't call any handlers. | ||
| 131 | - // This dictionary could have been empty, but it's okay since | ||
| 132 | - // it's a dictionary like it's supposed to be. | 141 | + this->m->h.dict_end_handler(path); |
| 133 | handled = true; | 142 | handled = true; |
| 134 | } | 143 | } |
| 135 | - if (this->m->h.array_handler.get()) | 144 | + if (this->m->h.array_start_handler && j.isArray()) |
| 136 | { | 145 | { |
| 146 | + this->m->h.array_start_handler(path); | ||
| 137 | size_t i = 0; | 147 | size_t i = 0; |
| 138 | j.forEachArrayItem([&i, &path, this](JSON v) { | 148 | j.forEachArrayItem([&i, &path, this](JSON v) { |
| 139 | - this->m->h.array_handler->handle( | 149 | + this->m->h.array_item_handler->handle( |
| 140 | path + "[" + QUtil::uint_to_string(i) + "]", v); | 150 | path + "[" + QUtil::uint_to_string(i) + "]", v); |
| 141 | ++i; | 151 | ++i; |
| 142 | }); | 152 | }); |
| 143 | - // Set handled = true even if we didn't call any handlers. | ||
| 144 | - // This could have been an empty array. | 153 | + this->m->h.array_end_handler(path); |
| 145 | handled = true; | 154 | handled = true; |
| 146 | } | 155 | } |
| 147 | 156 |
libtests/json_handler.cc
| @@ -28,6 +28,13 @@ static void print_json(std::string const& path, JSON value) | @@ -28,6 +28,13 @@ static void print_json(std::string const& path, JSON value) | ||
| 28 | std::cout << path << ": json: " << value.unparse() << std::endl; | 28 | std::cout << path << ": json: " << value.unparse() << std::endl; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | +static JSONHandler::void_handler_t make_print_message(std::string msg) | ||
| 32 | +{ | ||
| 33 | + return [msg](std::string const& path) { | ||
| 34 | + std::cout << path << ": json: " << msg << std::endl; | ||
| 35 | + }; | ||
| 36 | +} | ||
| 37 | + | ||
| 31 | static void test_scalar() | 38 | static void test_scalar() |
| 32 | { | 39 | { |
| 33 | std::cout << "-- scalar --" << std::endl; | 40 | std::cout << "-- scalar --" << std::endl; |
| @@ -40,41 +47,50 @@ static void test_scalar() | @@ -40,41 +47,50 @@ static void test_scalar() | ||
| 40 | static std::shared_ptr<JSONHandler> make_all_handler() | 47 | static std::shared_ptr<JSONHandler> make_all_handler() |
| 41 | { | 48 | { |
| 42 | auto h = std::make_shared<JSONHandler>(); | 49 | auto h = std::make_shared<JSONHandler>(); |
| 43 | - auto& m = h->addDictHandlers(); | 50 | + h->addDictHandlers( |
| 51 | + make_print_message("dict begin"), | ||
| 52 | + make_print_message("dict end")); | ||
| 44 | auto h1 = std::make_shared<JSONHandler>(); | 53 | auto h1 = std::make_shared<JSONHandler>(); |
| 45 | h1->addStringHandler(print_string); | 54 | h1->addStringHandler(print_string); |
| 46 | - m["one"] = h1; | 55 | + h->addDictKeyHandler("one", h1); |
| 47 | auto h2 = std::make_shared<JSONHandler>(); | 56 | auto h2 = std::make_shared<JSONHandler>(); |
| 48 | h2->addNumberHandler(print_number); | 57 | h2->addNumberHandler(print_number); |
| 49 | - m["two"] = h2; | 58 | + h->addDictKeyHandler("two", h2); |
| 50 | auto h3 = std::make_shared<JSONHandler>(); | 59 | auto h3 = std::make_shared<JSONHandler>(); |
| 51 | h3->addBoolHandler(print_bool); | 60 | h3->addBoolHandler(print_bool); |
| 52 | - m["three"] = h3; | 61 | + h->addDictKeyHandler("three", h3); |
| 53 | auto h4 = std::make_shared<JSONHandler>(); | 62 | auto h4 = std::make_shared<JSONHandler>(); |
| 54 | h4->addAnyHandler(print_json); | 63 | h4->addAnyHandler(print_json); |
| 55 | - m["four"] = h4; | ||
| 56 | - m["phour"] = h4; // share h4 | 64 | + h->addDictKeyHandler("four", h4); |
| 65 | + h->addDictKeyHandler("phour", h4); // share h4 | ||
| 57 | auto h5 = std::make_shared<JSONHandler>(); | 66 | auto h5 = std::make_shared<JSONHandler>(); |
| 58 | // Allow to be either string or bool | 67 | // Allow to be either string or bool |
| 59 | h5->addBoolHandler(print_bool); | 68 | h5->addBoolHandler(print_bool); |
| 60 | h5->addStringHandler(print_string); | 69 | h5->addStringHandler(print_string); |
| 61 | h5->addNullHandler(print_null); | 70 | h5->addNullHandler(print_null); |
| 62 | auto h5s = std::make_shared<JSONHandler>(); | 71 | auto h5s = std::make_shared<JSONHandler>(); |
| 63 | - m["five"] = h5s; | ||
| 64 | - h5s->addArrayHandler(h5); | 72 | + h->addDictKeyHandler("five", h5s); |
| 73 | + h5s->addArrayHandlers( | ||
| 74 | + make_print_message("array begin"), | ||
| 75 | + make_print_message("array end"), | ||
| 76 | + h5); | ||
| 65 | auto h6 = std::make_shared<JSONHandler>(); | 77 | auto h6 = std::make_shared<JSONHandler>(); |
| 66 | - auto& m6 = h6->addDictHandlers(); | 78 | + h6->addDictHandlers( |
| 79 | + make_print_message("dict begin"), | ||
| 80 | + make_print_message("dict end")); | ||
| 67 | auto h6a = std::make_shared<JSONHandler>(); | 81 | auto h6a = std::make_shared<JSONHandler>(); |
| 68 | - m6["a"] = h6a; | ||
| 69 | - auto& m6a = h6a->addDictHandlers(); | 82 | + h6->addDictKeyHandler("a", h6a); |
| 83 | + h6a->addDictHandlers( | ||
| 84 | + make_print_message("dict begin"), | ||
| 85 | + make_print_message("dict end")); | ||
| 70 | auto h6ab = std::make_shared<JSONHandler>(); | 86 | auto h6ab = std::make_shared<JSONHandler>(); |
| 71 | - m6a["b"] = h6ab; | 87 | + h6a->addDictKeyHandler("b", h6ab); |
| 72 | auto h6ax = std::make_shared<JSONHandler>(); | 88 | auto h6ax = std::make_shared<JSONHandler>(); |
| 73 | h6ax->addAnyHandler(print_json); | 89 | h6ax->addAnyHandler(print_json); |
| 74 | h6a->addFallbackDictHandler(h6ax); | 90 | h6a->addFallbackDictHandler(h6ax); |
| 75 | - m6["b"] = h6ab; // share | 91 | + h6->addDictKeyHandler("b", h6ab); // share |
| 76 | h6ab->addStringHandler(print_string); | 92 | h6ab->addStringHandler(print_string); |
| 77 | - m["six"] = h6; | 93 | + h->addDictKeyHandler("six", h6); |
| 78 | return h; | 94 | return h; |
| 79 | } | 95 | } |
| 80 | 96 |
libtests/qtest/json_handler/json_handler.out
| 1 | -- scalar -- | 1 | -- scalar -- |
| 2 | .: string: potato | 2 | .: string: potato |
| 3 | -- all -- | 3 | -- all -- |
| 4 | +.: json: dict begin | ||
| 5 | +.five: json: array begin | ||
| 4 | .five[0]: string: x | 6 | .five[0]: string: x |
| 5 | .five[1]: bool: false | 7 | .five[1]: bool: false |
| 6 | .five[2]: string: y | 8 | .five[2]: string: y |
| 7 | .five[3]: null | 9 | .five[3]: null |
| 8 | .five[4]: bool: true | 10 | .five[4]: bool: true |
| 11 | +.five: json: array end | ||
| 9 | .four: json: [ | 12 | .four: json: [ |
| 10 | "a", | 13 | "a", |
| 11 | 1 | 14 | 1 |
| 12 | ] | 15 | ] |
| 13 | .one: string: potato | 16 | .one: string: potato |
| 14 | .phour: json: null | 17 | .phour: json: null |
| 18 | +.six: json: dict begin | ||
| 19 | +.six.a: json: dict begin | ||
| 15 | .six.a.Q: json: "baaa" | 20 | .six.a.Q: json: "baaa" |
| 16 | .six.a.b: string: quack | 21 | .six.a.b: string: quack |
| 22 | +.six.a: json: dict end | ||
| 17 | .six.b: string: moo | 23 | .six.b: string: moo |
| 24 | +.six: json: dict end | ||
| 18 | .three: bool: true | 25 | .three: bool: true |
| 19 | .two: number: 3.14 | 26 | .two: number: 3.14 |
| 27 | +.: json: dict end | ||
| 20 | -- errors -- | 28 | -- errors -- |
| 21 | bad type at top: JSON handler: value at . is not of expected type | 29 | bad type at top: JSON handler: value at . is not of expected type |
| 30 | +.: json: dict begin | ||
| 22 | unexpected key: JSON handler found unexpected key x in object at . | 31 | unexpected key: JSON handler found unexpected key x in object at . |