Commit 651179b5da0777f861e427f96fd8560bf1516ae5
1 parent
0776c001
Add simple JSON serializer
Showing
9 changed files
with
773 additions
and
0 deletions
ChangeLog
| 1 | 2018-12-18 Jay Berkenbilt <ejb@ql.org> | 1 | 2018-12-18 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * Add a simple JSON serializer. This is not a complete or | ||
| 4 | + general-purpose JSON library. It allows assembly and serialization | ||
| 5 | + of JSON structures with some restrictions, which are described in | ||
| 6 | + the header file. | ||
| 7 | + | ||
| 3 | * Add QPDFNameTreeObjectHelper class. This class provides useful | 8 | * Add QPDFNameTreeObjectHelper class. This class provides useful |
| 4 | methods for dealing with name trees, which are discussed in | 9 | methods for dealing with name trees, which are discussed in |
| 5 | section 7.9.6 of the PDF spec (ISO-32000). | 10 | section 7.9.6 of the PDF spec (ISO-32000). |
include/qpdf/JSON.hh
0 โ 100644
| 1 | +// Copyright (c) 2005-2018 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 JSON_HH | ||
| 23 | +#define JSON_HH | ||
| 24 | + | ||
| 25 | +// This is a simple JSON serializer, primarily designed for | ||
| 26 | +// serializing QPDF Objects as JSON. JSON objects contain their data | ||
| 27 | +// as smart pointers. One JSON object is added to another, this | ||
| 28 | +// pointer is copied. This means you can create temporary JSON objects | ||
| 29 | +// on the stack, add them to other objects, and let them go out of | ||
| 30 | +// scope safely. It also means that if the json JSON object is added | ||
| 31 | +// in more than one place, all copies share underlying data. | ||
| 32 | + | ||
| 33 | +#include <qpdf/DLL.h> | ||
| 34 | +#include <qpdf/PointerHolder.hh> | ||
| 35 | +#include <string> | ||
| 36 | +#include <map> | ||
| 37 | +#include <vector> | ||
| 38 | +#include <list> | ||
| 39 | + | ||
| 40 | +class JSON | ||
| 41 | +{ | ||
| 42 | + public: | ||
| 43 | + QPDF_DLL | ||
| 44 | + std::string serialize() const; | ||
| 45 | + | ||
| 46 | + // The JSON spec calls dictionaries "objects", but that creates | ||
| 47 | + // too much confusion when referring to instances of the JSON | ||
| 48 | + // class. | ||
| 49 | + QPDF_DLL | ||
| 50 | + static JSON makeDictionary(); | ||
| 51 | + // addDictionaryMember returns the newly added item. | ||
| 52 | + QPDF_DLL | ||
| 53 | + JSON addDictionaryMember(std::string const& key, JSON const&); | ||
| 54 | + QPDF_DLL | ||
| 55 | + static JSON makeArray(); | ||
| 56 | + // addArrayElement returns the newly added item. | ||
| 57 | + QPDF_DLL | ||
| 58 | + JSON addArrayElement(JSON const&); | ||
| 59 | + QPDF_DLL | ||
| 60 | + static JSON makeString(std::string const& utf8); | ||
| 61 | + QPDF_DLL | ||
| 62 | + static JSON makeInt(long long int value); | ||
| 63 | + QPDF_DLL | ||
| 64 | + static JSON makeReal(double value); | ||
| 65 | + QPDF_DLL | ||
| 66 | + static JSON makeNumber(std::string const& encoded); | ||
| 67 | + QPDF_DLL | ||
| 68 | + static JSON makeBool(bool value); | ||
| 69 | + QPDF_DLL | ||
| 70 | + static JSON makeNull(); | ||
| 71 | + | ||
| 72 | + // Check this JSON object against a "schema". This is not a schema | ||
| 73 | + // according to any standard. It's just a template of what the | ||
| 74 | + // JSON is supposed to contain. The checking does the following: | ||
| 75 | + // | ||
| 76 | + // * The schema is a nested structure containing dictionaries, | ||
| 77 | + // single-element arrays, and strings only. | ||
| 78 | + // * Recursively walk the schema | ||
| 79 | + // * If the current value is a dictionary, this object must have | ||
| 80 | + // a dictionary in the same place with the same keys | ||
| 81 | + // * If the current value is an array, this object must have an | ||
| 82 | + // array in the same place. The schema's array must contain a | ||
| 83 | + // single element, which is used as a schema to validate each | ||
| 84 | + // element of this object's corresponding array. | ||
| 85 | + // * Otherwise, the value is ignored. | ||
| 86 | + // | ||
| 87 | + // QPDF's JSON output conforms to certain strict compatability | ||
| 88 | + // rules as discussed in the manual. The idea is that a JSON | ||
| 89 | + // structure created manually in qpdf.cc doubles as both JSON help | ||
| 90 | + // information and a schema for validating the JSON that qpdf | ||
| 91 | + // generates. Any discrepancies are a bug in qpdf. | ||
| 92 | + QPDF_DLL | ||
| 93 | + bool checkSchema(JSON schema, std::list<std::string>& errors); | ||
| 94 | + | ||
| 95 | + private: | ||
| 96 | + static std::string encode_string(std::string const& utf8); | ||
| 97 | + | ||
| 98 | + struct JSON_value | ||
| 99 | + { | ||
| 100 | + virtual ~JSON_value(); | ||
| 101 | + virtual std::string unparse(size_t depth) const = 0; | ||
| 102 | + }; | ||
| 103 | + struct JSON_dictionary: public JSON_value | ||
| 104 | + { | ||
| 105 | + virtual ~JSON_dictionary(); | ||
| 106 | + virtual std::string unparse(size_t depth) const; | ||
| 107 | + std::map<std::string, PointerHolder<JSON_value> > members; | ||
| 108 | + }; | ||
| 109 | + struct JSON_array: public JSON_value | ||
| 110 | + { | ||
| 111 | + virtual ~JSON_array(); | ||
| 112 | + virtual std::string unparse(size_t depth) const; | ||
| 113 | + std::vector<PointerHolder<JSON_value> > elements; | ||
| 114 | + }; | ||
| 115 | + struct JSON_string: public JSON_value | ||
| 116 | + { | ||
| 117 | + JSON_string(std::string const& utf8); | ||
| 118 | + virtual ~JSON_string(); | ||
| 119 | + virtual std::string unparse(size_t depth) const; | ||
| 120 | + std::string encoded; | ||
| 121 | + }; | ||
| 122 | + struct JSON_number: public JSON_value | ||
| 123 | + { | ||
| 124 | + JSON_number(long long val); | ||
| 125 | + JSON_number(double val); | ||
| 126 | + JSON_number(std::string const& val); | ||
| 127 | + virtual ~JSON_number(); | ||
| 128 | + virtual std::string unparse(size_t depth) const; | ||
| 129 | + std::string encoded; | ||
| 130 | + }; | ||
| 131 | + struct JSON_bool: public JSON_value | ||
| 132 | + { | ||
| 133 | + JSON_bool(bool val); | ||
| 134 | + virtual ~JSON_bool(); | ||
| 135 | + virtual std::string unparse(size_t depth) const; | ||
| 136 | + bool value; | ||
| 137 | + }; | ||
| 138 | + struct JSON_null: public JSON_value | ||
| 139 | + { | ||
| 140 | + virtual ~JSON_null(); | ||
| 141 | + virtual std::string unparse(size_t depth) const; | ||
| 142 | + }; | ||
| 143 | + | ||
| 144 | + JSON(PointerHolder<JSON_value>); | ||
| 145 | + | ||
| 146 | + static bool | ||
| 147 | + checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v, | ||
| 148 | + std::list<std::string>& errors, | ||
| 149 | + std::string prefix); | ||
| 150 | + | ||
| 151 | + class Members | ||
| 152 | + { | ||
| 153 | + friend class JSON; | ||
| 154 | + | ||
| 155 | + public: | ||
| 156 | + QPDF_DLL | ||
| 157 | + ~Members(); | ||
| 158 | + | ||
| 159 | + private: | ||
| 160 | + Members(PointerHolder<JSON_value>); | ||
| 161 | + Members(Members const&); | ||
| 162 | + | ||
| 163 | + PointerHolder<JSON_value> value; | ||
| 164 | + }; | ||
| 165 | + | ||
| 166 | + PointerHolder<Members> m; | ||
| 167 | +}; | ||
| 168 | + | ||
| 169 | + | ||
| 170 | +#endif // JSON_HH |
libqpdf/JSON.cc
0 โ 100644
| 1 | +#include <qpdf/JSON.hh> | ||
| 2 | +#include <qpdf/QUtil.hh> | ||
| 3 | +#include <qpdf/QTC.hh> | ||
| 4 | +#include <stdexcept> | ||
| 5 | + | ||
| 6 | +JSON::Members::~Members() | ||
| 7 | +{ | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +JSON::Members::Members(PointerHolder<JSON_value> value) : | ||
| 11 | + value(value) | ||
| 12 | +{ | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +JSON::JSON(PointerHolder<JSON_value> value) : | ||
| 16 | + m(new Members(value)) | ||
| 17 | +{ | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +JSON::JSON_value::~JSON_value() | ||
| 21 | +{ | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +JSON::JSON_dictionary::~JSON_dictionary() | ||
| 25 | +{ | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +std::string JSON::JSON_dictionary::unparse(size_t depth) const | ||
| 29 | +{ | ||
| 30 | + std::string result = "{"; | ||
| 31 | + bool first = true; | ||
| 32 | + for (std::map<std::string, PointerHolder<JSON_value> >::const_iterator | ||
| 33 | + iter = members.begin(); | ||
| 34 | + iter != members.end(); ++iter) | ||
| 35 | + { | ||
| 36 | + if (first) | ||
| 37 | + { | ||
| 38 | + first = false; | ||
| 39 | + } | ||
| 40 | + else | ||
| 41 | + { | ||
| 42 | + result.append(1, ','); | ||
| 43 | + } | ||
| 44 | + result.append(1, '\n'); | ||
| 45 | + result.append(2 * (1 + depth), ' '); | ||
| 46 | + result += ("\"" + (*iter).first + "\": " + | ||
| 47 | + (*iter).second->unparse(1 + depth)); | ||
| 48 | + } | ||
| 49 | + if (! first) | ||
| 50 | + { | ||
| 51 | + result.append(1, '\n'); | ||
| 52 | + result.append(2 * depth, ' '); | ||
| 53 | + } | ||
| 54 | + result.append(1, '}'); | ||
| 55 | + return result; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +JSON::JSON_array::~JSON_array() | ||
| 59 | +{ | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +std::string JSON::JSON_array::unparse(size_t depth) const | ||
| 63 | +{ | ||
| 64 | + std::string result = "["; | ||
| 65 | + bool first = true; | ||
| 66 | + for (std::vector<PointerHolder<JSON_value> >::const_iterator iter = | ||
| 67 | + elements.begin(); | ||
| 68 | + iter != elements.end(); ++iter) | ||
| 69 | + { | ||
| 70 | + if (first) | ||
| 71 | + { | ||
| 72 | + first = false; | ||
| 73 | + } | ||
| 74 | + else | ||
| 75 | + { | ||
| 76 | + result.append(1, ','); | ||
| 77 | + } | ||
| 78 | + result.append(1, '\n'); | ||
| 79 | + result.append(2 * (1 + depth), ' '); | ||
| 80 | + result += (*iter)->unparse(1 + depth); | ||
| 81 | + } | ||
| 82 | + if (! first) | ||
| 83 | + { | ||
| 84 | + result.append(1, '\n'); | ||
| 85 | + result.append(2 * depth, ' '); | ||
| 86 | + } | ||
| 87 | + result.append(1, ']'); | ||
| 88 | + return result; | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +JSON::JSON_string::JSON_string(std::string const& utf8) : | ||
| 92 | + encoded(encode_string(utf8)) | ||
| 93 | +{ | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +JSON::JSON_string::~JSON_string() | ||
| 97 | +{ | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +std::string JSON::JSON_string::unparse(size_t) const | ||
| 101 | +{ | ||
| 102 | + return "\"" + encoded + "\""; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +JSON::JSON_number::JSON_number(long long value) : | ||
| 106 | + encoded(QUtil::int_to_string(value)) | ||
| 107 | +{ | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +JSON::JSON_number::JSON_number(double value) : | ||
| 111 | + encoded(QUtil::double_to_string(value, 6)) | ||
| 112 | +{ | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +JSON::JSON_number::JSON_number(std::string const& value) : | ||
| 116 | + encoded(value) | ||
| 117 | +{ | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +JSON::JSON_number::~JSON_number() | ||
| 121 | +{ | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +std::string JSON::JSON_number::unparse(size_t) const | ||
| 125 | +{ | ||
| 126 | + return encoded; | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +JSON::JSON_bool::JSON_bool(bool val) : | ||
| 130 | + value(val) | ||
| 131 | +{ | ||
| 132 | +} | ||
| 133 | + | ||
| 134 | +JSON::JSON_bool::~JSON_bool() | ||
| 135 | +{ | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +std::string JSON::JSON_bool::unparse(size_t) const | ||
| 139 | +{ | ||
| 140 | + return value ? "true" : "false"; | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +JSON::JSON_null::~JSON_null() | ||
| 144 | +{ | ||
| 145 | +} | ||
| 146 | + | ||
| 147 | +std::string JSON::JSON_null::unparse(size_t) const | ||
| 148 | +{ | ||
| 149 | + return "null"; | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +std::string | ||
| 153 | +JSON::serialize() const | ||
| 154 | +{ | ||
| 155 | + if (0 == this->m->value.getPointer()) | ||
| 156 | + { | ||
| 157 | + return "null"; | ||
| 158 | + } | ||
| 159 | + else | ||
| 160 | + { | ||
| 161 | + return this->m->value->unparse(0); | ||
| 162 | + } | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +std::string | ||
| 166 | +JSON::encode_string(std::string const& str) | ||
| 167 | +{ | ||
| 168 | + std::string result; | ||
| 169 | + size_t len = str.length(); | ||
| 170 | + for (size_t i = 0; i < len; ++i) | ||
| 171 | + { | ||
| 172 | + unsigned char ch = static_cast<unsigned char>(str.at(i)); | ||
| 173 | + switch (ch) | ||
| 174 | + { | ||
| 175 | + case '\\': | ||
| 176 | + result += "\\\\"; | ||
| 177 | + break; | ||
| 178 | + case '\"': | ||
| 179 | + result += "\\\""; | ||
| 180 | + break; | ||
| 181 | + case '\b': | ||
| 182 | + result += "\\b"; | ||
| 183 | + break; | ||
| 184 | + case '\n': | ||
| 185 | + result += "\\n"; | ||
| 186 | + break; | ||
| 187 | + case '\r': | ||
| 188 | + result += "\\r"; | ||
| 189 | + break; | ||
| 190 | + case '\t': | ||
| 191 | + result += "\\t"; | ||
| 192 | + break; | ||
| 193 | + default: | ||
| 194 | + if (ch < 32) | ||
| 195 | + { | ||
| 196 | + result += "\\u" + QUtil::int_to_string_base(ch, 16, 4); | ||
| 197 | + } | ||
| 198 | + else | ||
| 199 | + { | ||
| 200 | + result.append(1, ch); | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + return result; | ||
| 205 | +} | ||
| 206 | + | ||
| 207 | +JSON | ||
| 208 | +JSON::makeDictionary() | ||
| 209 | +{ | ||
| 210 | + return JSON(new JSON_dictionary()); | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +JSON | ||
| 214 | +JSON::addDictionaryMember(std::string const& key, JSON const& val) | ||
| 215 | +{ | ||
| 216 | + JSON_dictionary* obj = dynamic_cast<JSON_dictionary*>( | ||
| 217 | + this->m->value.getPointer()); | ||
| 218 | + if (0 == obj) | ||
| 219 | + { | ||
| 220 | + throw std::runtime_error( | ||
| 221 | + "JSON::addDictionaryMember called on non-dictionary"); | ||
| 222 | + } | ||
| 223 | + if (val.m->value.getPointer()) | ||
| 224 | + { | ||
| 225 | + obj->members[encode_string(key)] = val.m->value; | ||
| 226 | + } | ||
| 227 | + else | ||
| 228 | + { | ||
| 229 | + obj->members[encode_string(key)] = new JSON_null(); | ||
| 230 | + } | ||
| 231 | + return obj->members[encode_string(key)]; | ||
| 232 | +} | ||
| 233 | + | ||
| 234 | +JSON | ||
| 235 | +JSON::makeArray() | ||
| 236 | +{ | ||
| 237 | + return JSON(new JSON_array()); | ||
| 238 | +} | ||
| 239 | + | ||
| 240 | +JSON | ||
| 241 | +JSON::addArrayElement(JSON const& val) | ||
| 242 | +{ | ||
| 243 | + JSON_array* arr = dynamic_cast<JSON_array*>( | ||
| 244 | + this->m->value.getPointer()); | ||
| 245 | + if (0 == arr) | ||
| 246 | + { | ||
| 247 | + throw std::runtime_error("JSON::addArrayElement called on non-array"); | ||
| 248 | + } | ||
| 249 | + if (val.m->value.getPointer()) | ||
| 250 | + { | ||
| 251 | + arr->elements.push_back(val.m->value); | ||
| 252 | + } | ||
| 253 | + else | ||
| 254 | + { | ||
| 255 | + arr->elements.push_back(new JSON_null()); | ||
| 256 | + } | ||
| 257 | + return arr->elements.back(); | ||
| 258 | +} | ||
| 259 | + | ||
| 260 | +JSON | ||
| 261 | +JSON::makeString(std::string const& utf8) | ||
| 262 | +{ | ||
| 263 | + return JSON(new JSON_string(utf8)); | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +JSON | ||
| 267 | +JSON::makeInt(long long int value) | ||
| 268 | +{ | ||
| 269 | + return JSON(new JSON_number(value)); | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +JSON | ||
| 273 | +JSON::makeReal(double value) | ||
| 274 | +{ | ||
| 275 | + return JSON(new JSON_number(value)); | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +JSON | ||
| 279 | +JSON::makeNumber(std::string const& encoded) | ||
| 280 | +{ | ||
| 281 | + return JSON(new JSON_number(encoded)); | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +JSON | ||
| 285 | +JSON::makeBool(bool value) | ||
| 286 | +{ | ||
| 287 | + return JSON(new JSON_bool(value)); | ||
| 288 | +} | ||
| 289 | + | ||
| 290 | +JSON | ||
| 291 | +JSON::makeNull() | ||
| 292 | +{ | ||
| 293 | + return JSON(new JSON_null()); | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +bool | ||
| 297 | +JSON::checkSchema(JSON schema, std::list<std::string>& errors) | ||
| 298 | +{ | ||
| 299 | + return checkSchemaInternal(this->m->value.getPointer(), | ||
| 300 | + schema.m->value.getPointer(), | ||
| 301 | + errors, ""); | ||
| 302 | +} | ||
| 303 | + | ||
| 304 | + | ||
| 305 | +bool | ||
| 306 | +JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v, | ||
| 307 | + std::list<std::string>& errors, | ||
| 308 | + std::string prefix) | ||
| 309 | +{ | ||
| 310 | + JSON_array* this_arr = dynamic_cast<JSON_array*>(this_v); | ||
| 311 | + JSON_dictionary* this_dict = dynamic_cast<JSON_dictionary*>(this_v); | ||
| 312 | + | ||
| 313 | + JSON_array* sch_arr = dynamic_cast<JSON_array*>(sch_v); | ||
| 314 | + JSON_dictionary* sch_dict = dynamic_cast<JSON_dictionary*>(sch_v); | ||
| 315 | + | ||
| 316 | + std::string err_prefix; | ||
| 317 | + if (prefix.empty()) | ||
| 318 | + { | ||
| 319 | + err_prefix = "top-level object"; | ||
| 320 | + } | ||
| 321 | + else | ||
| 322 | + { | ||
| 323 | + err_prefix = "json key \"" + prefix + "\""; | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + if (sch_dict) | ||
| 327 | + { | ||
| 328 | + if (! this_dict) | ||
| 329 | + { | ||
| 330 | + QTC::TC("libtests", "JSON wanted dictionary"); | ||
| 331 | + errors.push_back(err_prefix + " is supposed to be a dictionary"); | ||
| 332 | + return false; | ||
| 333 | + } | ||
| 334 | + for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter = | ||
| 335 | + sch_dict->members.begin(); | ||
| 336 | + iter != sch_dict->members.end(); ++iter) | ||
| 337 | + { | ||
| 338 | + std::string const& key = (*iter).first; | ||
| 339 | + if (this_dict->members.count(key)) | ||
| 340 | + { | ||
| 341 | + checkSchemaInternal( | ||
| 342 | + this_dict->members[key].getPointer(), | ||
| 343 | + (*iter).second.getPointer(), | ||
| 344 | + errors, prefix + "." + key); | ||
| 345 | + } | ||
| 346 | + else | ||
| 347 | + { | ||
| 348 | + QTC::TC("libtests", "JSON key missing in object"); | ||
| 349 | + errors.push_back( | ||
| 350 | + err_prefix + ": key \"" + key + | ||
| 351 | + "\" is present in schema but missing in object"); | ||
| 352 | + } | ||
| 353 | + } | ||
| 354 | + for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter = | ||
| 355 | + this_dict->members.begin(); | ||
| 356 | + iter != this_dict->members.end(); ++iter) | ||
| 357 | + { | ||
| 358 | + std::string const& key = (*iter).first; | ||
| 359 | + if (sch_dict->members.count(key) == 0) | ||
| 360 | + { | ||
| 361 | + QTC::TC("libtests", "JSON key extra in object"); | ||
| 362 | + errors.push_back( | ||
| 363 | + err_prefix + ": key \"" + key + | ||
| 364 | + "\" is not present in schema but appears in object"); | ||
| 365 | + } | ||
| 366 | + } | ||
| 367 | + } | ||
| 368 | + else if (sch_arr) | ||
| 369 | + { | ||
| 370 | + if (! this_arr) | ||
| 371 | + { | ||
| 372 | + QTC::TC("libtests", "JSON wanted array"); | ||
| 373 | + errors.push_back(err_prefix + " is supposed to be an array"); | ||
| 374 | + return false; | ||
| 375 | + } | ||
| 376 | + if (sch_arr->elements.size() != 1) | ||
| 377 | + { | ||
| 378 | + QTC::TC("libtests", "JSON schema array error"); | ||
| 379 | + errors.push_back(err_prefix + | ||
| 380 | + " schema array contains other than one item"); | ||
| 381 | + return false; | ||
| 382 | + } | ||
| 383 | + int i = 0; | ||
| 384 | + for (std::vector<PointerHolder<JSON_value> >::iterator iter = | ||
| 385 | + this_arr->elements.begin(); | ||
| 386 | + iter != this_arr->elements.end(); ++iter, ++i) | ||
| 387 | + { | ||
| 388 | + checkSchemaInternal( | ||
| 389 | + (*iter).getPointer(), | ||
| 390 | + sch_arr->elements.at(0).getPointer(), | ||
| 391 | + errors, prefix + "." + QUtil::int_to_string(i)); | ||
| 392 | + } | ||
| 393 | + } | ||
| 394 | + | ||
| 395 | + return errors.empty(); | ||
| 396 | +} |
libqpdf/build.mk
| @@ -14,6 +14,7 @@ SRCS_libqpdf = \ | @@ -14,6 +14,7 @@ SRCS_libqpdf = \ | ||
| 14 | libqpdf/FileInputSource.cc \ | 14 | libqpdf/FileInputSource.cc \ |
| 15 | libqpdf/InputSource.cc \ | 15 | libqpdf/InputSource.cc \ |
| 16 | libqpdf/InsecureRandomDataProvider.cc \ | 16 | libqpdf/InsecureRandomDataProvider.cc \ |
| 17 | + libqpdf/JSON.cc \ | ||
| 17 | libqpdf/MD5.cc \ | 18 | libqpdf/MD5.cc \ |
| 18 | libqpdf/OffsetInputSource.cc \ | 19 | libqpdf/OffsetInputSource.cc \ |
| 19 | libqpdf/Pipeline.cc \ | 20 | libqpdf/Pipeline.cc \ |
libtests/build.mk
libtests/json.cc
0 โ 100644
| 1 | +#include <qpdf/JSON.hh> | ||
| 2 | +#include <qpdf/QPDFObjectHandle.hh> | ||
| 3 | +#include <iostream> | ||
| 4 | +#include <assert.h> | ||
| 5 | + | ||
| 6 | +static void check(JSON& j, std::string const& exp) | ||
| 7 | +{ | ||
| 8 | + if (exp != j.serialize()) | ||
| 9 | + { | ||
| 10 | + std::cout << "Got " << j.serialize() << "; wanted " << exp << "\n"; | ||
| 11 | + } | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +static void test_main() | ||
| 15 | +{ | ||
| 16 | + JSON jstr = JSON::makeString( | ||
| 17 | + "<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\"<3>\x03\t\b\r\n<4>"); | ||
| 18 | + check(jstr, | ||
| 19 | + "\"<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\\\\\"<3>" | ||
| 20 | + "\\u0003\\t\\b\\r\\n<4>\""); | ||
| 21 | + JSON jnull = JSON::makeNull(); | ||
| 22 | + check(jnull, "null"); | ||
| 23 | + JSON jarr = JSON::makeArray(); | ||
| 24 | + check(jarr, "[]"); | ||
| 25 | + JSON jstr2 = JSON::makeString("a\tb"); | ||
| 26 | + JSON jint = JSON::makeInt(16059); | ||
| 27 | + JSON jdouble = JSON::makeReal(3.14159); | ||
| 28 | + JSON jexp = JSON::makeNumber("2.1e5"); | ||
| 29 | + jarr.addArrayElement(jstr2); | ||
| 30 | + jarr.addArrayElement(jnull); | ||
| 31 | + jarr.addArrayElement(jint); | ||
| 32 | + jarr.addArrayElement(jdouble); | ||
| 33 | + jarr.addArrayElement(jexp); | ||
| 34 | + check(jarr, | ||
| 35 | + "[\n" | ||
| 36 | + " \"a\\tb\",\n" | ||
| 37 | + " null,\n" | ||
| 38 | + " 16059,\n" | ||
| 39 | + " 3.141590,\n" | ||
| 40 | + " 2.1e5\n" | ||
| 41 | + "]"); | ||
| 42 | + JSON jmap = JSON::makeDictionary(); | ||
| 43 | + check(jmap, "{}"); | ||
| 44 | + jmap.addDictionaryMember("b", jstr2); | ||
| 45 | + jmap.addDictionaryMember("a", jarr); | ||
| 46 | + jmap.addDictionaryMember("c\r\nd", jnull); | ||
| 47 | + jmap.addDictionaryMember("yes", JSON::makeBool(false)); | ||
| 48 | + jmap.addDictionaryMember("no", JSON::makeBool(true)); | ||
| 49 | + jmap.addDictionaryMember("empty_dict", JSON::makeDictionary()); | ||
| 50 | + jmap.addDictionaryMember("empty_list", JSON::makeArray()); | ||
| 51 | + jmap.addDictionaryMember("single", JSON::makeArray()). | ||
| 52 | + addArrayElement(JSON::makeInt(12)); | ||
| 53 | + check(jmap, | ||
| 54 | + "{\n" | ||
| 55 | + " \"a\": [\n" | ||
| 56 | + " \"a\\tb\",\n" | ||
| 57 | + " null,\n" | ||
| 58 | + " 16059,\n" | ||
| 59 | + " 3.141590,\n" | ||
| 60 | + " 2.1e5\n" | ||
| 61 | + " ],\n" | ||
| 62 | + " \"b\": \"a\\tb\",\n" | ||
| 63 | + " \"c\\r\\nd\": null,\n" | ||
| 64 | + " \"empty_dict\": {},\n" | ||
| 65 | + " \"empty_list\": [],\n" | ||
| 66 | + " \"no\": true,\n" | ||
| 67 | + " \"single\": [\n" | ||
| 68 | + " 12\n" | ||
| 69 | + " ],\n" | ||
| 70 | + " \"yes\": false\n" | ||
| 71 | + "}"); | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +static void check_schema(JSON& obj, JSON& schema, bool exp, | ||
| 75 | + std::string const& description) | ||
| 76 | +{ | ||
| 77 | + std::list<std::string> errors; | ||
| 78 | + std::cout << "--- " << description << std::endl; | ||
| 79 | + assert(exp == obj.checkSchema(schema, errors)); | ||
| 80 | + for (std::list<std::string>::iterator iter = errors.begin(); | ||
| 81 | + iter != errors.end(); ++iter) | ||
| 82 | + { | ||
| 83 | + std::cout << *iter << std::endl; | ||
| 84 | + } | ||
| 85 | + std::cout << "---" << std::endl; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +static void test_schema() | ||
| 89 | +{ | ||
| 90 | + // Since we don't have a JSON parser, use the PDF parser as a | ||
| 91 | + // shortcut for creating a complex JSON structure. | ||
| 92 | + JSON schema = QPDFObjectHandle::parse( | ||
| 93 | + "<<" | ||
| 94 | + " /one <<" | ||
| 95 | + " /a <<" | ||
| 96 | + " /q (queue)" | ||
| 97 | + " /r <<" | ||
| 98 | + " /x (ecks)" | ||
| 99 | + " /y (why)" | ||
| 100 | + " >>" | ||
| 101 | + " /s [ (esses) ]" | ||
| 102 | + " >>" | ||
| 103 | + " >>" | ||
| 104 | + " /two [" | ||
| 105 | + " <<" | ||
| 106 | + " /goose (gander)" | ||
| 107 | + " /glarp (enspliel)" | ||
| 108 | + " >>" | ||
| 109 | + " ]" | ||
| 110 | + ">>").getJSON(); | ||
| 111 | + JSON a = QPDFObjectHandle::parse("[(not a) (dictionary)]").getJSON(); | ||
| 112 | + check_schema(a, schema, false, "top-level type mismatch"); | ||
| 113 | + JSON b = QPDFObjectHandle::parse( | ||
| 114 | + "<<" | ||
| 115 | + " /one <<" | ||
| 116 | + " /a <<" | ||
| 117 | + " /t (oops)" | ||
| 118 | + " /r [" | ||
| 119 | + " /x (ecks)" | ||
| 120 | + " /y (why)" | ||
| 121 | + " ]" | ||
| 122 | + " /s << /z (esses) >>" | ||
| 123 | + " >>" | ||
| 124 | + " >>" | ||
| 125 | + " /two [" | ||
| 126 | + " <<" | ||
| 127 | + " /goose (0 gander)" | ||
| 128 | + " /glarp (0 enspliel)" | ||
| 129 | + " >>" | ||
| 130 | + " <<" | ||
| 131 | + " /goose (1 gander)" | ||
| 132 | + " /flarp (1 enspliel)" | ||
| 133 | + " >>" | ||
| 134 | + " 2" | ||
| 135 | + " [ (three) ]" | ||
| 136 | + " <<" | ||
| 137 | + " /goose (4 gander)" | ||
| 138 | + " /glarp (4 enspliel)" | ||
| 139 | + " >>" | ||
| 140 | + " ]" | ||
| 141 | + ">>").getJSON(); | ||
| 142 | + check_schema(b, schema, false, "top-level type mismatch"); | ||
| 143 | + check_schema(a, a, false, "top-level schema array error"); | ||
| 144 | + check_schema(b, b, false, "lower-level schema array error"); | ||
| 145 | + check_schema(schema, schema, true, "pass"); | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +int main() | ||
| 149 | +{ | ||
| 150 | + test_main(); | ||
| 151 | + test_schema(); | ||
| 152 | + | ||
| 153 | + std::cout << "end of json tests\n"; | ||
| 154 | + return 0; | ||
| 155 | +} |
libtests/libtests.testcov
| @@ -34,3 +34,8 @@ Pl_PNGFilter decodeUp 0 | @@ -34,3 +34,8 @@ Pl_PNGFilter decodeUp 0 | ||
| 34 | Pl_PNGFilter decodeAverage 0 | 34 | Pl_PNGFilter decodeAverage 0 |
| 35 | Pl_PNGFilter decodePaeth 0 | 35 | Pl_PNGFilter decodePaeth 0 |
| 36 | Pl_TIFFPredictor processRow 1 | 36 | Pl_TIFFPredictor processRow 1 |
| 37 | +JSON wanted dictionary 0 | ||
| 38 | +JSON key missing in object 0 | ||
| 39 | +JSON wanted array 0 | ||
| 40 | +JSON schema array error 0 | ||
| 41 | +JSON key extra in object 0 |
libtests/qtest/json.test
0 โ 100644
| 1 | +#!/usr/bin/env perl | ||
| 2 | +require 5.008; | ||
| 3 | +use warnings; | ||
| 4 | +use strict; | ||
| 5 | + | ||
| 6 | +chdir("json") or die "chdir testdir failed: $!\n"; | ||
| 7 | + | ||
| 8 | +require TestDriver; | ||
| 9 | + | ||
| 10 | +my $td = new TestDriver('json'); | ||
| 11 | + | ||
| 12 | +$td->runtest("json", | ||
| 13 | + {$td->COMMAND => "json"}, | ||
| 14 | + {$td->FILE => "json.out", $td->EXIT_STATUS => 0}, | ||
| 15 | + $td->NORMALIZE_NEWLINES); | ||
| 16 | + | ||
| 17 | +$td->report(1); |
libtests/qtest/json/json.out
0 โ 100644
| 1 | +--- top-level type mismatch | ||
| 2 | +top-level object is supposed to be a dictionary | ||
| 3 | +--- | ||
| 4 | +--- top-level type mismatch | ||
| 5 | +json key "./one./a": key "/q" is present in schema but missing in object | ||
| 6 | +json key "./one./a./r" is supposed to be a dictionary | ||
| 7 | +json key "./one./a./s" is supposed to be an array | ||
| 8 | +json key "./one./a": key "/t" is not present in schema but appears in object | ||
| 9 | +json key "./two.1": key "/glarp" is present in schema but missing in object | ||
| 10 | +json key "./two.1": key "/flarp" is not present in schema but appears in object | ||
| 11 | +json key "./two.2" is supposed to be a dictionary | ||
| 12 | +json key "./two.3" is supposed to be a dictionary | ||
| 13 | +--- | ||
| 14 | +--- top-level schema array error | ||
| 15 | +top-level object schema array contains other than one item | ||
| 16 | +--- | ||
| 17 | +--- lower-level schema array error | ||
| 18 | +json key "./one./a./r" schema array contains other than one item | ||
| 19 | +json key "./two" schema array contains other than one item | ||
| 20 | +--- | ||
| 21 | +--- pass | ||
| 22 | +--- | ||
| 23 | +end of json tests |