From 1db0a7ffcee6f6ae6bd3298a960665378d304fa1 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Thu, 20 Jan 2022 08:53:53 -0500 Subject: [PATCH] JSONHandler: rework dictionary and array handlers --- include/qpdf/JSONHandler.hh | 37 +++++++++++----- libqpdf/JSONHandler.cc | 39 ++++++++++------- libtests/json_handler.cc | 44 +++++++++++++------- libtests/qtest/json_handler/json_handler.out | 9 ++++ 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/include/qpdf/JSONHandler.hh b/include/qpdf/JSONHandler.hh index bde134ac..0f75f946 100644 --- a/include/qpdf/JSONHandler.hh +++ b/include/qpdf/JSONHandler.hh @@ -31,6 +31,11 @@ #include #include +// This class allows a sax-like walk through a JSON object with +// functionality that mostly mirrors QPDFArgParser. It is primarily +// here to facilitate automatic generation of some of the code to help +// keep QPDFJob json consistent with command-line arguments. + class JSONHandler { public: @@ -53,7 +58,8 @@ class JSONHandler // certain type. JSONHandler::Error is thrown otherwise. Multiple // handlers may be registered, which allows the object to be of // various types. If an anyHandler is added, no other handler will - // be called. + // be called. There is no "final" handler -- if the top-level is a + // dictionary or array, just use its end handler. typedef std::function json_handler_t; @@ -80,19 +86,18 @@ class JSONHandler QPDF_DLL void addBoolHandler(bool_handler_t fn); - // Returns a reference to a map: keys are expected object keys, - // and values are handlers for that object. QPDF_DLL - std::map>& addDictHandlers(); - - // Apply the given handler to any key not explicitly in dict - // handlers. + void addDictHandlers(void_handler_t start_fn, void_handler_t end_fn); + QPDF_DLL + void addDictKeyHandler( + std::string const& key, std::shared_ptr); QPDF_DLL void addFallbackDictHandler(std::shared_ptr); - // Apply the given handler to each element of the array. QPDF_DLL - void addArrayHandler(std::shared_ptr); + void addArrayHandlers(void_handler_t start_fn, + void_handler_t end_fn, + std::shared_ptr item_handlers); // Apply handlers recursively to a JSON object. QPDF_DLL @@ -108,7 +113,12 @@ class JSONHandler null_handler(nullptr), string_handler(nullptr), number_handler(nullptr), - bool_handler(nullptr) + bool_handler(nullptr), + dict_start_handler(nullptr), + dict_end_handler(nullptr), + array_start_handler(nullptr), + array_end_handler(nullptr), + final_handler(nullptr) { } @@ -117,9 +127,14 @@ class JSONHandler string_handler_t string_handler; string_handler_t number_handler; bool_handler_t bool_handler; + void_handler_t dict_start_handler; + void_handler_t dict_end_handler; + void_handler_t array_start_handler; + void_handler_t array_end_handler; + void_handler_t final_handler; std::map> dict_handlers; std::shared_ptr fallback_dict_handler; - std::shared_ptr array_handler; + std::shared_ptr array_item_handler; }; class Members diff --git a/libqpdf/JSONHandler.cc b/libqpdf/JSONHandler.cc index 7318466f..5e3d9a8d 100644 --- a/libqpdf/JSONHandler.cc +++ b/libqpdf/JSONHandler.cc @@ -46,10 +46,18 @@ JSONHandler::addBoolHandler(bool_handler_t fn) this->m->h.bool_handler = fn; } -std::map>& -JSONHandler::addDictHandlers() +void +JSONHandler::addDictHandlers(void_handler_t start_fn, void_handler_t end_fn) { - return this->m->h.dict_handlers; + this->m->h.dict_start_handler = start_fn; + this->m->h.dict_end_handler = end_fn; +} + +void +JSONHandler::addDictKeyHandler( + std::string const& key, std::shared_ptr dkh) +{ + this->m->h.dict_handlers[key] = dkh; } void @@ -59,9 +67,13 @@ JSONHandler::addFallbackDictHandler(std::shared_ptr fdh) } void -JSONHandler::addArrayHandler(std::shared_ptr ah) +JSONHandler::addArrayHandlers(void_handler_t start_fn, + void_handler_t end_fn, + std::shared_ptr ah) { - this->m->h.array_handler = ah; + this->m->h.array_start_handler = start_fn; + this->m->h.array_end_handler = end_fn; + this->m->h.array_item_handler = ah; } void @@ -95,9 +107,9 @@ JSONHandler::handle(std::string const& path, JSON j) this->m->h.bool_handler(path, bvalue); handled = true; } - if ((this->m->h.fallback_dict_handler.get() || - (! this->m->h.dict_handlers.empty())) && j.isDictionary()) + if (this->m->h.dict_start_handler && j.isDictionary()) { + this->m->h.dict_start_handler(path); std::string path_base = path; if (path_base != ".") { @@ -126,22 +138,19 @@ JSONHandler::handle(std::string const& path, JSON j) i->second->handle(path_base + k, v); } }); - - // Set handled = true even if we didn't call any handlers. - // This dictionary could have been empty, but it's okay since - // it's a dictionary like it's supposed to be. + this->m->h.dict_end_handler(path); handled = true; } - if (this->m->h.array_handler.get()) + if (this->m->h.array_start_handler && j.isArray()) { + this->m->h.array_start_handler(path); size_t i = 0; j.forEachArrayItem([&i, &path, this](JSON v) { - this->m->h.array_handler->handle( + this->m->h.array_item_handler->handle( path + "[" + QUtil::uint_to_string(i) + "]", v); ++i; }); - // Set handled = true even if we didn't call any handlers. - // This could have been an empty array. + this->m->h.array_end_handler(path); handled = true; } diff --git a/libtests/json_handler.cc b/libtests/json_handler.cc index d5e6aea3..7f6349f9 100644 --- a/libtests/json_handler.cc +++ b/libtests/json_handler.cc @@ -28,6 +28,13 @@ static void print_json(std::string const& path, JSON value) std::cout << path << ": json: " << value.unparse() << std::endl; } +static JSONHandler::void_handler_t make_print_message(std::string msg) +{ + return [msg](std::string const& path) { + std::cout << path << ": json: " << msg << std::endl; + }; +} + static void test_scalar() { std::cout << "-- scalar --" << std::endl; @@ -40,41 +47,50 @@ static void test_scalar() static std::shared_ptr make_all_handler() { auto h = std::make_shared(); - auto& m = h->addDictHandlers(); + h->addDictHandlers( + make_print_message("dict begin"), + make_print_message("dict end")); auto h1 = std::make_shared(); h1->addStringHandler(print_string); - m["one"] = h1; + h->addDictKeyHandler("one", h1); auto h2 = std::make_shared(); h2->addNumberHandler(print_number); - m["two"] = h2; + h->addDictKeyHandler("two", h2); auto h3 = std::make_shared(); h3->addBoolHandler(print_bool); - m["three"] = h3; + h->addDictKeyHandler("three", h3); auto h4 = std::make_shared(); h4->addAnyHandler(print_json); - m["four"] = h4; - m["phour"] = h4; // share h4 + h->addDictKeyHandler("four", h4); + h->addDictKeyHandler("phour", h4); // share h4 auto h5 = std::make_shared(); // Allow to be either string or bool h5->addBoolHandler(print_bool); h5->addStringHandler(print_string); h5->addNullHandler(print_null); auto h5s = std::make_shared(); - m["five"] = h5s; - h5s->addArrayHandler(h5); + h->addDictKeyHandler("five", h5s); + h5s->addArrayHandlers( + make_print_message("array begin"), + make_print_message("array end"), + h5); auto h6 = std::make_shared(); - auto& m6 = h6->addDictHandlers(); + h6->addDictHandlers( + make_print_message("dict begin"), + make_print_message("dict end")); auto h6a = std::make_shared(); - m6["a"] = h6a; - auto& m6a = h6a->addDictHandlers(); + h6->addDictKeyHandler("a", h6a); + h6a->addDictHandlers( + make_print_message("dict begin"), + make_print_message("dict end")); auto h6ab = std::make_shared(); - m6a["b"] = h6ab; + h6a->addDictKeyHandler("b", h6ab); auto h6ax = std::make_shared(); h6ax->addAnyHandler(print_json); h6a->addFallbackDictHandler(h6ax); - m6["b"] = h6ab; // share + h6->addDictKeyHandler("b", h6ab); // share h6ab->addStringHandler(print_string); - m["six"] = h6; + h->addDictKeyHandler("six", h6); return h; } diff --git a/libtests/qtest/json_handler/json_handler.out b/libtests/qtest/json_handler/json_handler.out index 13554af3..368c94b5 100644 --- a/libtests/qtest/json_handler/json_handler.out +++ b/libtests/qtest/json_handler/json_handler.out @@ -1,22 +1,31 @@ -- scalar -- .: string: potato -- all -- +.: json: dict begin +.five: json: array begin .five[0]: string: x .five[1]: bool: false .five[2]: string: y .five[3]: null .five[4]: bool: true +.five: json: array end .four: json: [ "a", 1 ] .one: string: potato .phour: json: null +.six: json: dict begin +.six.a: json: dict begin .six.a.Q: json: "baaa" .six.a.b: string: quack +.six.a: json: dict end .six.b: string: moo +.six: json: dict end .three: bool: true .two: number: 3.14 +.: json: dict end -- errors -- bad type at top: JSON handler: value at . is not of expected type +.: json: dict begin unexpected key: JSON handler found unexpected key x in object at .