Commit 37105710ee0b332a3020d4b3220c95b8f4267555
1 parent
a6df6fda
Implement JSONHandler for recursively processing JSON
Showing
11 changed files
with
613 additions
and
2 deletions
include/qpdf/JSON.hh
| ... | ... | @@ -30,7 +30,10 @@ |
| 30 | 30 | // create temporary JSON objects on the stack, add them to other |
| 31 | 31 | // objects, and let them go out of scope safely. It also means that if |
| 32 | 32 | // the json JSON object is added in more than one place, all copies |
| 33 | -// share underlying data. | |
| 33 | +// share underlying data. This makes them similar in structure and | |
| 34 | +// behavior to QPDFObjectHandle and may feel natural within the QPDF | |
| 35 | +// codebase, but it is also a good reason not to use this as a | |
| 36 | +// general-purpose JSON package. | |
| 34 | 37 | |
| 35 | 38 | #include <qpdf/DLL.h> |
| 36 | 39 | #include <qpdf/PointerHolder.hh> |
| ... | ... | @@ -38,6 +41,7 @@ |
| 38 | 41 | #include <map> |
| 39 | 42 | #include <vector> |
| 40 | 43 | #include <list> |
| 44 | +#include <functional> | |
| 41 | 45 | |
| 42 | 46 | class JSON |
| 43 | 47 | { |
| ... | ... | @@ -77,6 +81,24 @@ class JSON |
| 77 | 81 | QPDF_DLL |
| 78 | 82 | bool isDictionary() const; |
| 79 | 83 | |
| 84 | + // Accessors. Accessor behavior: | |
| 85 | + // | |
| 86 | + // - If argument is wrong type, including null, return false | |
| 87 | + // - If argument is right type, return true and initialize the value | |
| 88 | + QPDF_DLL | |
| 89 | + bool getString(std::string& utf8) const; | |
| 90 | + QPDF_DLL | |
| 91 | + bool getNumber(std::string& value) const; | |
| 92 | + QPDF_DLL | |
| 93 | + bool getBool(bool& value) const; | |
| 94 | + QPDF_DLL | |
| 95 | + bool isNull() const; | |
| 96 | + QPDF_DLL | |
| 97 | + bool forEachDictItem( | |
| 98 | + std::function<void(std::string const& key, JSON value)> fn) const; | |
| 99 | + QPDF_DLL | |
| 100 | + bool forEachArrayItem(std::function<void(JSON value)> fn) const; | |
| 101 | + | |
| 80 | 102 | // Check this JSON object against a "schema". This is not a schema |
| 81 | 103 | // according to any standard. It's just a template of what the |
| 82 | 104 | // JSON is supposed to contain. The checking does the following: |
| ... | ... | @@ -129,6 +151,7 @@ class JSON |
| 129 | 151 | JSON_string(std::string const& utf8); |
| 130 | 152 | virtual ~JSON_string(); |
| 131 | 153 | virtual std::string unparse(size_t depth) const; |
| 154 | + std::string utf8; | |
| 132 | 155 | std::string encoded; |
| 133 | 156 | }; |
| 134 | 157 | struct JSON_number: public JSON_value | ... | ... |
include/qpdf/JSONHandler.hh
0 โ 100644
| 1 | +// Copyright (c) 2005-2021 Jay Berkenbilt | |
| 2 | +// | |
| 3 | +// This file is part of qpdf. | |
| 4 | +// | |
| 5 | +// Licensed under the Apache License, Version 2.0 (the "License"); | |
| 6 | +// you may not use this file except in compliance with the License. | |
| 7 | +// You may obtain a copy of the License at | |
| 8 | +// | |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | +// | |
| 11 | +// Unless required by applicable law or agreed to in writing, software | |
| 12 | +// distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | +// See the License for the specific language governing permissions and | |
| 15 | +// limitations under the License. | |
| 16 | +// | |
| 17 | +// Versions of qpdf prior to version 7 were released under the terms | |
| 18 | +// of version 2.0 of the Artistic License. At your option, you may | |
| 19 | +// continue to consider qpdf to be licensed under those terms. Please | |
| 20 | +// see the manual for additional information. | |
| 21 | + | |
| 22 | +#ifndef JSONHANDLER_HH | |
| 23 | +#define JSONHANDLER_HH | |
| 24 | + | |
| 25 | +#include <qpdf/DLL.h> | |
| 26 | +#include <qpdf/PointerHolder.hh> | |
| 27 | +#include <qpdf/JSON.hh> | |
| 28 | +#include <string> | |
| 29 | +#include <map> | |
| 30 | +#include <functional> | |
| 31 | +#include <stdexcept> | |
| 32 | +#include <memory> | |
| 33 | + | |
| 34 | +class JSONHandler | |
| 35 | +{ | |
| 36 | + public: | |
| 37 | + // Error exception is thrown if there are any errors validating | |
| 38 | + // the JSON object. | |
| 39 | + class QPDF_DLL_CLASS Error: public std::runtime_error | |
| 40 | + { | |
| 41 | + public: | |
| 42 | + QPDF_DLL | |
| 43 | + Error(std::string const&); | |
| 44 | + }; | |
| 45 | + | |
| 46 | + QPDF_DLL | |
| 47 | + JSONHandler(); | |
| 48 | + | |
| 49 | + QPDF_DLL | |
| 50 | + ~JSONHandler() = default; | |
| 51 | + | |
| 52 | + // Based on the type of handler, expect the object to be of a | |
| 53 | + // certain type. JSONHandler::Error is thrown otherwise. Multiple | |
| 54 | + // handlers may be registered, which allows the object to be of | |
| 55 | + // various types. If an anyHandler is added, no other handler will | |
| 56 | + // be called. | |
| 57 | + | |
| 58 | + typedef std::function<void( | |
| 59 | + std::string const& path, JSON value)> json_handler_t; | |
| 60 | + typedef std::function<void( | |
| 61 | + std::string const& path)> void_handler_t; | |
| 62 | + typedef std::function<void( | |
| 63 | + std::string const& path, std::string const& value)> string_handler_t; | |
| 64 | + typedef std::function<void( | |
| 65 | + std::string const& path, bool value)> bool_handler_t; | |
| 66 | + | |
| 67 | + // If an any handler is added, it will be called for any value | |
| 68 | + // including null, and no other handler will be called. | |
| 69 | + QPDF_DLL | |
| 70 | + void addAnyHandler(json_handler_t fn); | |
| 71 | + | |
| 72 | + // If any of the remaining handlers are registered, each | |
| 73 | + // registered handle will be called. | |
| 74 | + QPDF_DLL | |
| 75 | + void addNullHandler(void_handler_t fn); | |
| 76 | + QPDF_DLL | |
| 77 | + void addStringHandler(string_handler_t fn); | |
| 78 | + QPDF_DLL | |
| 79 | + void addNumberHandler(string_handler_t fn); | |
| 80 | + QPDF_DLL | |
| 81 | + void addBoolHandler(bool_handler_t fn); | |
| 82 | + | |
| 83 | + // Returns a reference to a map: keys are expected object keys, | |
| 84 | + // and values are handlers for that object. | |
| 85 | + 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 | + QPDF_DLL | |
| 91 | + void addFallbackDictHandler(std::shared_ptr<JSONHandler>); | |
| 92 | + | |
| 93 | + // Apply the given handler to each element of the array. | |
| 94 | + QPDF_DLL | |
| 95 | + void addArrayHandler(std::shared_ptr<JSONHandler>); | |
| 96 | + | |
| 97 | + // Apply handlers recursively to a JSON object. | |
| 98 | + QPDF_DLL | |
| 99 | + void handle(std::string const& path, JSON j); | |
| 100 | + | |
| 101 | + private: | |
| 102 | + JSONHandler(JSONHandler const&) = delete; | |
| 103 | + | |
| 104 | + struct Handlers | |
| 105 | + { | |
| 106 | + Handlers() : | |
| 107 | + any_handler(nullptr), | |
| 108 | + null_handler(nullptr), | |
| 109 | + string_handler(nullptr), | |
| 110 | + number_handler(nullptr), | |
| 111 | + bool_handler(nullptr) | |
| 112 | + { | |
| 113 | + } | |
| 114 | + | |
| 115 | + json_handler_t any_handler; | |
| 116 | + void_handler_t null_handler; | |
| 117 | + string_handler_t string_handler; | |
| 118 | + string_handler_t number_handler; | |
| 119 | + bool_handler_t bool_handler; | |
| 120 | + std::map<std::string, std::shared_ptr<JSONHandler>> dict_handlers; | |
| 121 | + std::shared_ptr<JSONHandler> fallback_dict_handler; | |
| 122 | + std::shared_ptr<JSONHandler> array_handler; | |
| 123 | + }; | |
| 124 | + | |
| 125 | + class Members | |
| 126 | + { | |
| 127 | + friend class JSONHandler; | |
| 128 | + | |
| 129 | + public: | |
| 130 | + QPDF_DLL | |
| 131 | + ~Members() = default; | |
| 132 | + | |
| 133 | + private: | |
| 134 | + Members(); | |
| 135 | + Members(Members const&) = delete; | |
| 136 | + | |
| 137 | + Handlers h; | |
| 138 | + }; | |
| 139 | + PointerHolder<Members> m; | |
| 140 | +}; | |
| 141 | + | |
| 142 | +#endif // JSONHANDLER_HH | ... | ... |
libqpdf/JSON.cc
| ... | ... | @@ -90,6 +90,7 @@ std::string JSON::JSON_array::unparse(size_t depth) const |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | 92 | JSON::JSON_string::JSON_string(std::string const& utf8) : |
| 93 | + utf8(utf8), | |
| 93 | 94 | encoded(encode_string(utf8)) |
| 94 | 95 | { |
| 95 | 96 | } |
| ... | ... | @@ -312,6 +313,83 @@ JSON::isDictionary() const |
| 312 | 313 | } |
| 313 | 314 | |
| 314 | 315 | bool |
| 316 | +JSON::getString(std::string& utf8) const | |
| 317 | +{ | |
| 318 | + auto v = dynamic_cast<JSON_string const*>(this->m->value.getPointer()); | |
| 319 | + if (v == nullptr) | |
| 320 | + { | |
| 321 | + return false; | |
| 322 | + } | |
| 323 | + utf8 = v->utf8; | |
| 324 | + return true; | |
| 325 | +} | |
| 326 | + | |
| 327 | +bool | |
| 328 | +JSON::getNumber(std::string& value) const | |
| 329 | +{ | |
| 330 | + auto v = dynamic_cast<JSON_number const*>(this->m->value.getPointer()); | |
| 331 | + if (v == nullptr) | |
| 332 | + { | |
| 333 | + return false; | |
| 334 | + } | |
| 335 | + value = v->encoded; | |
| 336 | + return true; | |
| 337 | +} | |
| 338 | + | |
| 339 | +bool | |
| 340 | +JSON::getBool(bool& value) const | |
| 341 | +{ | |
| 342 | + auto v = dynamic_cast<JSON_bool const*>(this->m->value.getPointer()); | |
| 343 | + if (v == nullptr) | |
| 344 | + { | |
| 345 | + return false; | |
| 346 | + } | |
| 347 | + value = v->value; | |
| 348 | + return true; | |
| 349 | +} | |
| 350 | + | |
| 351 | +bool | |
| 352 | +JSON::isNull() const | |
| 353 | +{ | |
| 354 | + if (dynamic_cast<JSON_null const*>(this->m->value.getPointer())) | |
| 355 | + { | |
| 356 | + return true; | |
| 357 | + } | |
| 358 | + return false; | |
| 359 | +} | |
| 360 | + | |
| 361 | +bool | |
| 362 | +JSON::forEachDictItem( | |
| 363 | + std::function<void(std::string const& key, JSON value)> fn) const | |
| 364 | +{ | |
| 365 | + auto v = dynamic_cast<JSON_dictionary const*>(this->m->value.getPointer()); | |
| 366 | + if (v == nullptr) | |
| 367 | + { | |
| 368 | + return false; | |
| 369 | + } | |
| 370 | + for (auto const& k: v->members) | |
| 371 | + { | |
| 372 | + fn(k.first, JSON(k.second)); | |
| 373 | + } | |
| 374 | + return true; | |
| 375 | +} | |
| 376 | + | |
| 377 | +bool | |
| 378 | +JSON::forEachArrayItem(std::function<void(JSON value)> fn) const | |
| 379 | +{ | |
| 380 | + auto v = dynamic_cast<JSON_array const*>(this->m->value.getPointer()); | |
| 381 | + if (v == nullptr) | |
| 382 | + { | |
| 383 | + return false; | |
| 384 | + } | |
| 385 | + for (auto const& i: v->elements) | |
| 386 | + { | |
| 387 | + fn(JSON(i)); | |
| 388 | + } | |
| 389 | + return true; | |
| 390 | +} | |
| 391 | + | |
| 392 | +bool | |
| 315 | 393 | JSON::checkSchema(JSON schema, std::list<std::string>& errors) |
| 316 | 394 | { |
| 317 | 395 | return checkSchemaInternal(this->m->value.getPointer(), | ... | ... |
libqpdf/JSONHandler.cc
0 โ 100644
| 1 | +#include <qpdf/JSONHandler.hh> | |
| 2 | +#include <qpdf/QUtil.hh> | |
| 3 | +#include <qpdf/QTC.hh> | |
| 4 | + | |
| 5 | +JSONHandler::Error::Error(std::string const& msg) : | |
| 6 | + std::runtime_error(msg) | |
| 7 | +{ | |
| 8 | +} | |
| 9 | + | |
| 10 | +JSONHandler::JSONHandler() : | |
| 11 | + m(new Members()) | |
| 12 | +{ | |
| 13 | +} | |
| 14 | + | |
| 15 | +JSONHandler::Members::Members() | |
| 16 | +{ | |
| 17 | +} | |
| 18 | + | |
| 19 | +void | |
| 20 | +JSONHandler::addAnyHandler(json_handler_t fn) | |
| 21 | +{ | |
| 22 | + this->m->h.any_handler = fn; | |
| 23 | +} | |
| 24 | + | |
| 25 | +void | |
| 26 | +JSONHandler::addNullHandler(void_handler_t fn) | |
| 27 | +{ | |
| 28 | + this->m->h.null_handler = fn; | |
| 29 | +} | |
| 30 | + | |
| 31 | +void | |
| 32 | +JSONHandler::addStringHandler(string_handler_t fn) | |
| 33 | +{ | |
| 34 | + this->m->h.string_handler = fn; | |
| 35 | +} | |
| 36 | + | |
| 37 | +void | |
| 38 | +JSONHandler::addNumberHandler(string_handler_t fn) | |
| 39 | +{ | |
| 40 | + this->m->h.number_handler = fn; | |
| 41 | +} | |
| 42 | + | |
| 43 | +void | |
| 44 | +JSONHandler::addBoolHandler(bool_handler_t fn) | |
| 45 | +{ | |
| 46 | + this->m->h.bool_handler = fn; | |
| 47 | +} | |
| 48 | + | |
| 49 | +std::map<std::string, std::shared_ptr<JSONHandler>>& | |
| 50 | +JSONHandler::addDictHandlers() | |
| 51 | +{ | |
| 52 | + return this->m->h.dict_handlers; | |
| 53 | +} | |
| 54 | + | |
| 55 | +void | |
| 56 | +JSONHandler::addFallbackDictHandler(std::shared_ptr<JSONHandler> fdh) | |
| 57 | +{ | |
| 58 | + this->m->h.fallback_dict_handler = fdh; | |
| 59 | +} | |
| 60 | + | |
| 61 | +void | |
| 62 | +JSONHandler::addArrayHandler(std::shared_ptr<JSONHandler> ah) | |
| 63 | +{ | |
| 64 | + this->m->h.array_handler = ah; | |
| 65 | +} | |
| 66 | + | |
| 67 | +void | |
| 68 | +JSONHandler::handle(std::string const& path, JSON j) | |
| 69 | +{ | |
| 70 | + if (this->m->h.any_handler) | |
| 71 | + { | |
| 72 | + this->m->h.any_handler(path, j); | |
| 73 | + return; | |
| 74 | + } | |
| 75 | + bool handled = false; | |
| 76 | + bool bvalue = false; | |
| 77 | + std::string svalue; | |
| 78 | + if (this->m->h.null_handler && j.isNull()) | |
| 79 | + { | |
| 80 | + this->m->h.null_handler(path); | |
| 81 | + handled = true; | |
| 82 | + } | |
| 83 | + if (this->m->h.string_handler && j.getString(svalue)) | |
| 84 | + { | |
| 85 | + this->m->h.string_handler(path, svalue); | |
| 86 | + handled = true; | |
| 87 | + } | |
| 88 | + if (this->m->h.number_handler && j.getNumber(svalue)) | |
| 89 | + { | |
| 90 | + this->m->h.number_handler(path, svalue); | |
| 91 | + handled = true; | |
| 92 | + } | |
| 93 | + if (this->m->h.bool_handler && j.getBool(bvalue)) | |
| 94 | + { | |
| 95 | + this->m->h.bool_handler(path, bvalue); | |
| 96 | + handled = true; | |
| 97 | + } | |
| 98 | + if ((this->m->h.fallback_dict_handler.get() || | |
| 99 | + (! this->m->h.dict_handlers.empty())) && j.isDictionary()) | |
| 100 | + { | |
| 101 | + std::string path_base = path; | |
| 102 | + if (path_base != ".") | |
| 103 | + { | |
| 104 | + path_base += "."; | |
| 105 | + } | |
| 106 | + j.forEachDictItem([&path, &path_base, this]( | |
| 107 | + std::string const& k, JSON v) { | |
| 108 | + auto i = this->m->h.dict_handlers.find(k); | |
| 109 | + if (i == this->m->h.dict_handlers.end()) | |
| 110 | + { | |
| 111 | + if (this->m->h.fallback_dict_handler.get()) | |
| 112 | + { | |
| 113 | + this->m->h.fallback_dict_handler->handle( | |
| 114 | + path_base + k, v); | |
| 115 | + } | |
| 116 | + else | |
| 117 | + { | |
| 118 | + QTC::TC("libtests", "JSONHandler unexpected key"); | |
| 119 | + throw Error( | |
| 120 | + "JSON handler found unexpected key " + k + | |
| 121 | + " in object at " + path); | |
| 122 | + } | |
| 123 | + } | |
| 124 | + else | |
| 125 | + { | |
| 126 | + i->second->handle(path_base + k, v); | |
| 127 | + } | |
| 128 | + }); | |
| 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. | |
| 133 | + handled = true; | |
| 134 | + } | |
| 135 | + if (this->m->h.array_handler.get()) | |
| 136 | + { | |
| 137 | + size_t i = 0; | |
| 138 | + j.forEachArrayItem([&i, &path, this](JSON v) { | |
| 139 | + this->m->h.array_handler->handle( | |
| 140 | + path + "[" + QUtil::uint_to_string(i) + "]", v); | |
| 141 | + ++i; | |
| 142 | + }); | |
| 143 | + // Set handled = true even if we didn't call any handlers. | |
| 144 | + // This could have been an empty array. | |
| 145 | + handled = true; | |
| 146 | + } | |
| 147 | + | |
| 148 | + if (! handled) | |
| 149 | + { | |
| 150 | + // It would be nice to include information about what type the | |
| 151 | + // object was and what types were allowed, but we're relying | |
| 152 | + // on schema validation to make sure input is properly | |
| 153 | + // structured before calling the handlers. It would be | |
| 154 | + // different if this code were trying to be part of a | |
| 155 | + // general-purpose JSON package. | |
| 156 | + QTC::TC("libtests", "JSONHandler unhandled value"); | |
| 157 | + throw Error("JSON handler: value at " + path + | |
| 158 | + " is not of expected type"); | |
| 159 | + } | |
| 160 | +} | ... | ... |
libqpdf/build.mk
libtests/build.mk
libtests/json.cc
| 1 | 1 | #include <qpdf/JSON.hh> |
| 2 | 2 | #include <qpdf/QPDFObjectHandle.hh> |
| 3 | 3 | #include <iostream> |
| 4 | -#include <assert.h> | |
| 4 | +#include <cassert> | |
| 5 | 5 | |
| 6 | 6 | static void check(JSON const& j, std::string const& exp) |
| 7 | 7 | { |
| ... | ... | @@ -20,12 +20,25 @@ static void test_main() |
| 20 | 20 | "\\u0003\\t\\b\\r\\n<4>\""); |
| 21 | 21 | JSON jnull = JSON::makeNull(); |
| 22 | 22 | check(jnull, "null"); |
| 23 | + assert(jnull.isNull()); | |
| 24 | + std::string value; | |
| 25 | + assert(! jnull.getNumber(value)); | |
| 23 | 26 | JSON jarr = JSON::makeArray(); |
| 24 | 27 | check(jarr, "[]"); |
| 25 | 28 | JSON jstr2 = JSON::makeString("a\tb"); |
| 29 | + assert(jstr2.getString(value)); | |
| 30 | + assert(value == "a\tb"); | |
| 31 | + assert(! jstr2.getNumber(value)); | |
| 26 | 32 | JSON jint = JSON::makeInt(16059); |
| 27 | 33 | JSON jdouble = JSON::makeReal(3.14159); |
| 28 | 34 | JSON jexp = JSON::makeNumber("2.1e5"); |
| 35 | + JSON jbool1 = JSON::makeBool(true); | |
| 36 | + JSON jbool2 = JSON::makeBool(false); | |
| 37 | + bool bvalue = false; | |
| 38 | + assert(jbool1.getBool(bvalue)); | |
| 39 | + assert(bvalue); | |
| 40 | + assert(jbool2.getBool(bvalue)); | |
| 41 | + assert(! bvalue); | |
| 29 | 42 | jarr.addArrayElement(jstr2); |
| 30 | 43 | jarr.addArrayElement(jnull); |
| 31 | 44 | jarr.addArrayElement(jint); |
| ... | ... | @@ -39,6 +52,18 @@ static void test_main() |
| 39 | 52 | " 3.14159,\n" |
| 40 | 53 | " 2.1e5\n" |
| 41 | 54 | "]"); |
| 55 | + std::vector<std::string> avalue; | |
| 56 | + assert(jarr.forEachArrayItem([&avalue](JSON j) { | |
| 57 | + avalue.push_back(j.unparse()); | |
| 58 | + })); | |
| 59 | + std::vector<std::string> xavalue = { | |
| 60 | + "\"a\\tb\"", | |
| 61 | + "null", | |
| 62 | + "16059", | |
| 63 | + "3.14159", | |
| 64 | + "2.1e5", | |
| 65 | + }; | |
| 66 | + assert(avalue == xavalue); | |
| 42 | 67 | JSON jmap = JSON::makeDictionary(); |
| 43 | 68 | check(jmap, "{}"); |
| 44 | 69 | jmap.addDictionaryMember("b", jstr2); |
| ... | ... | @@ -73,6 +98,18 @@ static void test_main() |
| 73 | 98 | check(QPDFObjectHandle::newReal(".34").getJSON(), "0.34"); |
| 74 | 99 | check(QPDFObjectHandle::newReal("-0.56").getJSON(), "-0.56"); |
| 75 | 100 | check(QPDFObjectHandle::newReal("-.78").getJSON(), "-0.78"); |
| 101 | + JSON jmap2 = JSON::parse(R"({"a": 1, "b": "two", "c": [true]})"); | |
| 102 | + std::map<std::string, std::string> dvalue; | |
| 103 | + assert(jmap2.forEachDictItem([&dvalue] | |
| 104 | + (std::string const& k, JSON j) { | |
| 105 | + dvalue[k] = j.unparse(); | |
| 106 | + })); | |
| 107 | + std::map<std::string, std::string> xdvalue = { | |
| 108 | + {"a", "1"}, | |
| 109 | + {"b", "\"two\""}, | |
| 110 | + {"c", "[\n true\n]"}, | |
| 111 | + }; | |
| 112 | + assert(dvalue == xdvalue); | |
| 76 | 113 | } |
| 77 | 114 | |
| 78 | 115 | static void check_schema(JSON& obj, JSON& schema, bool exp, | ... | ... |
libtests/json_handler.cc
0 โ 100644
| 1 | +#include <qpdf/JSONHandler.hh> | |
| 2 | +#include <qpdf/QUtil.hh> | |
| 3 | +#include <iostream> | |
| 4 | +#include <cassert> | |
| 5 | + | |
| 6 | +static void print_null(std::string const& path) | |
| 7 | +{ | |
| 8 | + std::cout << path << ": null" << std::endl; | |
| 9 | +} | |
| 10 | + | |
| 11 | +static void print_string(std::string const& path, std::string const& value) | |
| 12 | +{ | |
| 13 | + std::cout << path << ": string: " << value << std::endl; | |
| 14 | +} | |
| 15 | + | |
| 16 | +static void print_number(std::string const& path, std::string const& value) | |
| 17 | +{ | |
| 18 | + std::cout << path << ": number: " << value << std::endl; | |
| 19 | +} | |
| 20 | + | |
| 21 | +static void print_bool(std::string const& path, bool value) | |
| 22 | +{ | |
| 23 | + std::cout << path << ": bool: " << (value ? "true" : "false") << std::endl; | |
| 24 | +} | |
| 25 | + | |
| 26 | +static void print_json(std::string const& path, JSON value) | |
| 27 | +{ | |
| 28 | + std::cout << path << ": json: " << value.unparse() << std::endl; | |
| 29 | +} | |
| 30 | + | |
| 31 | +static void test_scalar() | |
| 32 | +{ | |
| 33 | + std::cout << "-- scalar --" << std::endl; | |
| 34 | + JSONHandler h; | |
| 35 | + h.addStringHandler(print_string); | |
| 36 | + JSON j = JSON::parse("\"potato\""); | |
| 37 | + h.handle(".", j); | |
| 38 | +} | |
| 39 | + | |
| 40 | +static std::shared_ptr<JSONHandler> make_all_handler() | |
| 41 | +{ | |
| 42 | + auto h = std::make_shared<JSONHandler>(); | |
| 43 | + auto& m = h->addDictHandlers(); | |
| 44 | + auto h1 = std::make_shared<JSONHandler>(); | |
| 45 | + h1->addStringHandler(print_string); | |
| 46 | + m["one"] = h1; | |
| 47 | + auto h2 = std::make_shared<JSONHandler>(); | |
| 48 | + h2->addNumberHandler(print_number); | |
| 49 | + m["two"] = h2; | |
| 50 | + auto h3 = std::make_shared<JSONHandler>(); | |
| 51 | + h3->addBoolHandler(print_bool); | |
| 52 | + m["three"] = h3; | |
| 53 | + auto h4 = std::make_shared<JSONHandler>(); | |
| 54 | + h4->addAnyHandler(print_json); | |
| 55 | + m["four"] = h4; | |
| 56 | + m["phour"] = h4; // share h4 | |
| 57 | + auto h5 = std::make_shared<JSONHandler>(); | |
| 58 | + // Allow to be either string or bool | |
| 59 | + h5->addBoolHandler(print_bool); | |
| 60 | + h5->addStringHandler(print_string); | |
| 61 | + h5->addNullHandler(print_null); | |
| 62 | + auto h5s = std::make_shared<JSONHandler>(); | |
| 63 | + m["five"] = h5s; | |
| 64 | + h5s->addArrayHandler(h5); | |
| 65 | + auto h6 = std::make_shared<JSONHandler>(); | |
| 66 | + auto& m6 = h6->addDictHandlers(); | |
| 67 | + auto h6a = std::make_shared<JSONHandler>(); | |
| 68 | + m6["a"] = h6a; | |
| 69 | + auto& m6a = h6a->addDictHandlers(); | |
| 70 | + auto h6ab = std::make_shared<JSONHandler>(); | |
| 71 | + m6a["b"] = h6ab; | |
| 72 | + auto h6ax = std::make_shared<JSONHandler>(); | |
| 73 | + h6ax->addAnyHandler(print_json); | |
| 74 | + h6a->addFallbackDictHandler(h6ax); | |
| 75 | + m6["b"] = h6ab; // share | |
| 76 | + h6ab->addStringHandler(print_string); | |
| 77 | + m["six"] = h6; | |
| 78 | + return h; | |
| 79 | +} | |
| 80 | + | |
| 81 | +static void test_all() | |
| 82 | +{ | |
| 83 | + std::cout << "-- all --" << std::endl; | |
| 84 | + auto h = make_all_handler(); | |
| 85 | + JSON j = JSON::parse(R"({ | |
| 86 | + "one": "potato", | |
| 87 | + "two": 3.14, | |
| 88 | + "three": true, | |
| 89 | + "four": ["a", 1], | |
| 90 | + "five": ["x", false, "y", null, true], | |
| 91 | + "phour": null, | |
| 92 | + "six": {"a": {"b": "quack", "Q": "baaa"}, "b": "moo"} | |
| 93 | +})"); | |
| 94 | + h->handle(".", j); | |
| 95 | +} | |
| 96 | + | |
| 97 | +static void test_errors() | |
| 98 | +{ | |
| 99 | + std::cout << "-- errors --" << std::endl; | |
| 100 | + auto h = make_all_handler(); | |
| 101 | + auto t = [h](std::string const& msg, std::function<void()> fn) { | |
| 102 | + try | |
| 103 | + { | |
| 104 | + fn(); | |
| 105 | + assert(false); | |
| 106 | + } | |
| 107 | + catch (JSONHandler::Error& e) | |
| 108 | + { | |
| 109 | + std::cout << msg << ": " << e.what() << std::endl; | |
| 110 | + } | |
| 111 | + }; | |
| 112 | + | |
| 113 | + t("bad type at top", [&h](){ | |
| 114 | + h->handle(".", JSON::makeString("oops")); | |
| 115 | + }); | |
| 116 | + t("unexpected key", [&h](){ | |
| 117 | + JSON j = JSON::parse(R"({"x": "y"})"); | |
| 118 | + h->handle(".", j); | |
| 119 | + }); | |
| 120 | +} | |
| 121 | + | |
| 122 | +int main(int argc, char* argv[]) | |
| 123 | +{ | |
| 124 | + test_scalar(); | |
| 125 | + test_all(); | |
| 126 | + test_errors(); | |
| 127 | + return 0; | |
| 128 | +} | ... | ... |
libtests/libtests.testcov
libtests/qtest/json_handler.test
0 โ 100644
| 1 | +#!/usr/bin/env perl | |
| 2 | +require 5.008; | |
| 3 | +use warnings; | |
| 4 | +use strict; | |
| 5 | + | |
| 6 | +chdir("json_handler") or die "chdir testdir failed: $!\n"; | |
| 7 | + | |
| 8 | +require TestDriver; | |
| 9 | + | |
| 10 | +my $td = new TestDriver('json_handler'); | |
| 11 | + | |
| 12 | +$td->runtest("JSON handler", | |
| 13 | + {$td->COMMAND => "json_handler"}, | |
| 14 | + {$td->FILE => "json_handler.out", $td->EXIT_STATUS => 0}, | |
| 15 | + $td->NORMALIZE_NEWLINES); | |
| 16 | + | |
| 17 | +$td->report(1); | ... | ... |
libtests/qtest/json_handler/json_handler.out
0 โ 100644
| 1 | +-- scalar -- | |
| 2 | +.: string: potato | |
| 3 | +-- all -- | |
| 4 | +.five[0]: string: x | |
| 5 | +.five[1]: bool: false | |
| 6 | +.five[2]: string: y | |
| 7 | +.five[3]: null | |
| 8 | +.five[4]: bool: true | |
| 9 | +.four: json: [ | |
| 10 | + "a", | |
| 11 | + 1 | |
| 12 | +] | |
| 13 | +.one: string: potato | |
| 14 | +.phour: json: null | |
| 15 | +.six.a.Q: json: "baaa" | |
| 16 | +.six.a.b: string: quack | |
| 17 | +.six.b: string: moo | |
| 18 | +.three: bool: true | |
| 19 | +.two: number: 3.14 | |
| 20 | +-- errors -- | |
| 21 | +bad type at top: JSON handler: value at . is not of expected type | |
| 22 | +unexpected key: JSON handler found unexpected key x in object at . | ... | ... |