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,7 +30,10 @@ | ||
| 30 | // create temporary JSON objects on the stack, add them to other | 30 | // create temporary JSON objects on the stack, add them to other |
| 31 | // objects, and let them go out of scope safely. It also means that if | 31 | // objects, and let them go out of scope safely. It also means that if |
| 32 | // the json JSON object is added in more than one place, all copies | 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 | #include <qpdf/DLL.h> | 38 | #include <qpdf/DLL.h> |
| 36 | #include <qpdf/PointerHolder.hh> | 39 | #include <qpdf/PointerHolder.hh> |
| @@ -38,6 +41,7 @@ | @@ -38,6 +41,7 @@ | ||
| 38 | #include <map> | 41 | #include <map> |
| 39 | #include <vector> | 42 | #include <vector> |
| 40 | #include <list> | 43 | #include <list> |
| 44 | +#include <functional> | ||
| 41 | 45 | ||
| 42 | class JSON | 46 | class JSON |
| 43 | { | 47 | { |
| @@ -77,6 +81,24 @@ class JSON | @@ -77,6 +81,24 @@ class JSON | ||
| 77 | QPDF_DLL | 81 | QPDF_DLL |
| 78 | bool isDictionary() const; | 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 | // Check this JSON object against a "schema". This is not a schema | 102 | // Check this JSON object against a "schema". This is not a schema |
| 81 | // according to any standard. It's just a template of what the | 103 | // according to any standard. It's just a template of what the |
| 82 | // JSON is supposed to contain. The checking does the following: | 104 | // JSON is supposed to contain. The checking does the following: |
| @@ -129,6 +151,7 @@ class JSON | @@ -129,6 +151,7 @@ class JSON | ||
| 129 | JSON_string(std::string const& utf8); | 151 | JSON_string(std::string const& utf8); |
| 130 | virtual ~JSON_string(); | 152 | virtual ~JSON_string(); |
| 131 | virtual std::string unparse(size_t depth) const; | 153 | virtual std::string unparse(size_t depth) const; |
| 154 | + std::string utf8; | ||
| 132 | std::string encoded; | 155 | std::string encoded; |
| 133 | }; | 156 | }; |
| 134 | struct JSON_number: public JSON_value | 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,6 +90,7 @@ std::string JSON::JSON_array::unparse(size_t depth) const | ||
| 90 | } | 90 | } |
| 91 | 91 | ||
| 92 | JSON::JSON_string::JSON_string(std::string const& utf8) : | 92 | JSON::JSON_string::JSON_string(std::string const& utf8) : |
| 93 | + utf8(utf8), | ||
| 93 | encoded(encode_string(utf8)) | 94 | encoded(encode_string(utf8)) |
| 94 | { | 95 | { |
| 95 | } | 96 | } |
| @@ -312,6 +313,83 @@ JSON::isDictionary() const | @@ -312,6 +313,83 @@ JSON::isDictionary() const | ||
| 312 | } | 313 | } |
| 313 | 314 | ||
| 314 | bool | 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 | JSON::checkSchema(JSON schema, std::list<std::string>& errors) | 393 | JSON::checkSchema(JSON schema, std::list<std::string>& errors) |
| 316 | { | 394 | { |
| 317 | return checkSchemaInternal(this->m->value.getPointer(), | 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
| @@ -38,6 +38,7 @@ SRCS_libqpdf = \ | @@ -38,6 +38,7 @@ SRCS_libqpdf = \ | ||
| 38 | libqpdf/InputSource.cc \ | 38 | libqpdf/InputSource.cc \ |
| 39 | libqpdf/InsecureRandomDataProvider.cc \ | 39 | libqpdf/InsecureRandomDataProvider.cc \ |
| 40 | libqpdf/JSON.cc \ | 40 | libqpdf/JSON.cc \ |
| 41 | + libqpdf/JSONHandler.cc \ | ||
| 41 | libqpdf/MD5.cc \ | 42 | libqpdf/MD5.cc \ |
| 42 | libqpdf/NNTree.cc \ | 43 | libqpdf/NNTree.cc \ |
| 43 | libqpdf/OffsetInputSource.cc \ | 44 | libqpdf/OffsetInputSource.cc \ |
libtests/build.mk
libtests/json.cc
| 1 | #include <qpdf/JSON.hh> | 1 | #include <qpdf/JSON.hh> |
| 2 | #include <qpdf/QPDFObjectHandle.hh> | 2 | #include <qpdf/QPDFObjectHandle.hh> |
| 3 | #include <iostream> | 3 | #include <iostream> |
| 4 | -#include <assert.h> | 4 | +#include <cassert> |
| 5 | 5 | ||
| 6 | static void check(JSON const& j, std::string const& exp) | 6 | static void check(JSON const& j, std::string const& exp) |
| 7 | { | 7 | { |
| @@ -20,12 +20,25 @@ static void test_main() | @@ -20,12 +20,25 @@ static void test_main() | ||
| 20 | "\\u0003\\t\\b\\r\\n<4>\""); | 20 | "\\u0003\\t\\b\\r\\n<4>\""); |
| 21 | JSON jnull = JSON::makeNull(); | 21 | JSON jnull = JSON::makeNull(); |
| 22 | check(jnull, "null"); | 22 | check(jnull, "null"); |
| 23 | + assert(jnull.isNull()); | ||
| 24 | + std::string value; | ||
| 25 | + assert(! jnull.getNumber(value)); | ||
| 23 | JSON jarr = JSON::makeArray(); | 26 | JSON jarr = JSON::makeArray(); |
| 24 | check(jarr, "[]"); | 27 | check(jarr, "[]"); |
| 25 | JSON jstr2 = JSON::makeString("a\tb"); | 28 | JSON jstr2 = JSON::makeString("a\tb"); |
| 29 | + assert(jstr2.getString(value)); | ||
| 30 | + assert(value == "a\tb"); | ||
| 31 | + assert(! jstr2.getNumber(value)); | ||
| 26 | JSON jint = JSON::makeInt(16059); | 32 | JSON jint = JSON::makeInt(16059); |
| 27 | JSON jdouble = JSON::makeReal(3.14159); | 33 | JSON jdouble = JSON::makeReal(3.14159); |
| 28 | JSON jexp = JSON::makeNumber("2.1e5"); | 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 | jarr.addArrayElement(jstr2); | 42 | jarr.addArrayElement(jstr2); |
| 30 | jarr.addArrayElement(jnull); | 43 | jarr.addArrayElement(jnull); |
| 31 | jarr.addArrayElement(jint); | 44 | jarr.addArrayElement(jint); |
| @@ -39,6 +52,18 @@ static void test_main() | @@ -39,6 +52,18 @@ static void test_main() | ||
| 39 | " 3.14159,\n" | 52 | " 3.14159,\n" |
| 40 | " 2.1e5\n" | 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 | JSON jmap = JSON::makeDictionary(); | 67 | JSON jmap = JSON::makeDictionary(); |
| 43 | check(jmap, "{}"); | 68 | check(jmap, "{}"); |
| 44 | jmap.addDictionaryMember("b", jstr2); | 69 | jmap.addDictionaryMember("b", jstr2); |
| @@ -73,6 +98,18 @@ static void test_main() | @@ -73,6 +98,18 @@ static void test_main() | ||
| 73 | check(QPDFObjectHandle::newReal(".34").getJSON(), "0.34"); | 98 | check(QPDFObjectHandle::newReal(".34").getJSON(), "0.34"); |
| 74 | check(QPDFObjectHandle::newReal("-0.56").getJSON(), "-0.56"); | 99 | check(QPDFObjectHandle::newReal("-0.56").getJSON(), "-0.56"); |
| 75 | check(QPDFObjectHandle::newReal("-.78").getJSON(), "-0.78"); | 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 | static void check_schema(JSON& obj, JSON& schema, bool exp, | 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
| @@ -87,3 +87,5 @@ JSON parse leading zero 0 | @@ -87,3 +87,5 @@ JSON parse leading zero 0 | ||
| 87 | JSON parse number no digits 0 | 87 | JSON parse number no digits 0 |
| 88 | JSON parse premature end of u 0 | 88 | JSON parse premature end of u 0 |
| 89 | JSON parse bad hex after u 0 | 89 | JSON parse bad hex after u 0 |
| 90 | +JSONHandler unhandled value 0 | ||
| 91 | +JSONHandler unexpected key 0 |
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 . |