Commit e8e8f6f43c760523520dfe7a5c76d88c959599f6

Authored by Jay Berkenbilt
1 parent b9af421e

Add JSON::parse

Showing 98 changed files with 1207 additions and 82 deletions
ChangeLog
@@ -4,8 +4,20 @@ @@ -4,8 +4,20 @@
4 to QPDFObjectHandle with corresponding functions added to the C 4 to QPDFObjectHandle with corresponding functions added to the C
5 API. Thanks to m-holger for the contribution. 5 API. Thanks to m-holger for the contribution.
6 6
  7 +2022-01-19 Jay Berkenbilt <ejb@ql.org>
  8 +
  9 + * Add a JSONHandler class that allows sax-like, recursive handling
  10 + of JSON objects.
  11 +
7 2022-01-17 Jay Berkenbilt <ejb@ql.org> 12 2022-01-17 Jay Berkenbilt <ejb@ql.org>
8 13
  14 + * Add JSON::parse. Now qpdf's JSON class implements a
  15 + general-purpose JSON parser and serializer, but there are better
  16 + options for general use. This is really designed for qpdf's
  17 + internal use and is set up to be compatible with qpdf's existing
  18 + API and to hook into a planned JSON-based API to the QPDFJob
  19 + class.
  20 +
9 * Add isDictionary and isArray to JSON 21 * Add isDictionary and isArray to JSON
10 22
11 2022-01-11 Jay Berkenbilt <ejb@ql.org> 23 2022-01-11 Jay Berkenbilt <ejb@ql.org>
@@ -335,9 +335,6 @@ I find it useful to make reference to them in this list. @@ -335,9 +335,6 @@ I find it useful to make reference to them in this list.
335 also be possible to create a widget annotation and a form field. 335 also be possible to create a widget annotation and a form field.
336 Update the pdf-attach-file.cc example with new APIs when ready. 336 Update the pdf-attach-file.cc example with new APIs when ready.
337 337
338 - * If I do more with json, take a look at this C++ header-only JSON  
339 - library: https://github.com/nlohmann/json/releases  
340 -  
341 * Flattening of form XObjects seems like something that would be 338 * Flattening of form XObjects seems like something that would be
342 useful in the library. We are seeing more cases of completely valid 339 useful in the library. We are seeing more cases of completely valid
343 PDF files with form XObjects that cause problems in other software. 340 PDF files with form XObjects that cause problems in other software.
include/qpdf/JSON.hh
@@ -22,13 +22,15 @@ @@ -22,13 +22,15 @@
22 #ifndef JSON_HH 22 #ifndef JSON_HH
23 #define JSON_HH 23 #define JSON_HH
24 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. 25 +// This is a simple JSON serializer and parser, primarily designed for
  26 +// serializing QPDF Objects as JSON. While it may work as a
  27 +// general-purpose JSON parser/serializer, there are better options.
  28 +// JSON objects contain their data as smart pointers. One JSON object
  29 +// is added to another, this pointer is copied. This means you can
  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
  32 +// the json JSON object is added in more than one place, all copies
  33 +// share underlying data.
32 34
33 #include <qpdf/DLL.h> 35 #include <qpdf/DLL.h>
34 #include <qpdf/PointerHolder.hh> 36 #include <qpdf/PointerHolder.hh>
@@ -98,6 +100,10 @@ class JSON @@ -98,6 +100,10 @@ class JSON
98 QPDF_DLL 100 QPDF_DLL
99 bool checkSchema(JSON schema, std::list<std::string>& errors); 101 bool checkSchema(JSON schema, std::list<std::string>& errors);
100 102
  103 + // Create a JSON object from a string.
  104 + QPDF_DLL
  105 + static JSON parse(std::string const&);
  106 +
101 private: 107 private:
102 static std::string encode_string(std::string const& utf8); 108 static std::string encode_string(std::string const& utf8);
103 109
libqpdf/JSON.cc
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 #include <qpdf/QUtil.hh> 2 #include <qpdf/QUtil.hh>
3 #include <qpdf/QTC.hh> 3 #include <qpdf/QTC.hh>
4 #include <stdexcept> 4 #include <stdexcept>
  5 +#include <cstring>
5 6
6 JSON::Members::~Members() 7 JSON::Members::~Members()
7 { 8 {
@@ -437,3 +438,774 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v, @@ -437,3 +438,774 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
437 438
438 return errors.empty(); 439 return errors.empty();
439 } 440 }
  441 +
  442 +namespace {
  443 + class JSONParser
  444 + {
  445 + public:
  446 + JSONParser() :
  447 + lex_state(ls_top),
  448 + number_before_point(0),
  449 + number_after_point(0),
  450 + number_after_e(0),
  451 + number_saw_point(false),
  452 + number_saw_e(false),
  453 + cstr(nullptr),
  454 + end(nullptr),
  455 + tok_start(nullptr),
  456 + tok_end(nullptr),
  457 + p(nullptr),
  458 + parser_state(ps_top)
  459 + {
  460 + }
  461 +
  462 + PointerHolder<JSON> parse(std::string const& s);
  463 +
  464 + private:
  465 + void getToken();
  466 + void handleToken();
  467 + static std::string decode_string(std::string const& json);
  468 +
  469 + enum parser_state_e {
  470 + ps_top,
  471 + ps_dict_begin,
  472 + ps_dict_after_key,
  473 + ps_dict_after_colon,
  474 + ps_dict_after_item,
  475 + ps_dict_after_comma,
  476 + ps_array_begin,
  477 + ps_array_after_item,
  478 + ps_array_after_comma,
  479 + ps_done,
  480 + };
  481 +
  482 + enum lex_state_e {
  483 + ls_top,
  484 + ls_number,
  485 + ls_alpha,
  486 + ls_string,
  487 + ls_backslash,
  488 + };
  489 +
  490 + lex_state_e lex_state;
  491 + size_t number_before_point;
  492 + size_t number_after_point;
  493 + size_t number_after_e;
  494 + bool number_saw_point;
  495 + bool number_saw_e;
  496 + char const* cstr;
  497 + char const* end;
  498 + char const* tok_start;
  499 + char const* tok_end;
  500 + char const* p;
  501 + parser_state_e parser_state;
  502 + std::vector<PointerHolder<JSON>> stack;
  503 + std::vector<parser_state_e> ps_stack;
  504 + std::string dict_key;
  505 + };
  506 +}
  507 +
  508 +std::string
  509 +JSONParser::decode_string(std::string const& str)
  510 +{
  511 + // The string has already been validated when this private method
  512 + // is called, so errors are logic errors instead of runtime
  513 + // errors.
  514 + size_t len = str.length();
  515 + if ((len < 2) || (str.at(0) != '"') || (str.at(len-1) != '"'))
  516 + {
  517 + throw std::logic_error(
  518 + "JSON Parse: decode_string called with other than \"...\"");
  519 + }
  520 + char const* s = str.c_str();
  521 + // Move inside the quotation marks
  522 + ++s;
  523 + len -= 2;
  524 + std::string result;
  525 + for (size_t i = 0; i < len; ++i)
  526 + {
  527 + if (s[i] == '\\')
  528 + {
  529 + if (i + 1 >= len)
  530 + {
  531 + throw std::logic_error("JSON parse: nothing after \\");
  532 + }
  533 + char ch = s[++i];
  534 + switch (ch)
  535 + {
  536 + case '\\':
  537 + case '\"':
  538 + result.append(1, ch);
  539 + break;
  540 + case 'b':
  541 + result.append(1, '\b');
  542 + break;
  543 + case 'f':
  544 + result.append(1, '\f');
  545 + break;
  546 + case 'n':
  547 + result.append(1, '\n');
  548 + break;
  549 + case 'r':
  550 + result.append(1, '\r');
  551 + break;
  552 + case 't':
  553 + result.append(1, '\t');
  554 + break;
  555 + case 'u':
  556 + if (i + 4 >= len)
  557 + {
  558 + throw std::logic_error(
  559 + "JSON parse: not enough characters after \\u");
  560 + }
  561 + {
  562 + std::string hex =
  563 + QUtil::hex_decode(std::string(s+i+1, s+i+5));
  564 + i += 4;
  565 + unsigned char high = static_cast<unsigned char>(hex.at(0));
  566 + unsigned char low = static_cast<unsigned char>(hex.at(1));
  567 + unsigned long codepoint = high;
  568 + codepoint <<= 8;
  569 + codepoint += low;
  570 + result += QUtil::toUTF8(codepoint);
  571 + }
  572 + break;
  573 + default:
  574 + throw std::logic_error(
  575 + "JSON parse: bad character after \\");
  576 + break;
  577 + }
  578 + }
  579 + else
  580 + {
  581 + result.append(1, s[i]);
  582 + }
  583 + }
  584 + return result;
  585 +}
  586 +
  587 +void JSONParser::getToken()
  588 +{
  589 + while (p < end)
  590 + {
  591 + if (*p == 0)
  592 + {
  593 + QTC::TC("libtests", "JSON parse null character");
  594 + throw std::runtime_error(
  595 + "JSON: null character at offset " +
  596 + QUtil::int_to_string(p - cstr));
  597 + }
  598 + switch (lex_state)
  599 + {
  600 + case ls_top:
  601 + if (*p == '"')
  602 + {
  603 + tok_start = p;
  604 + tok_end = nullptr;
  605 + lex_state = ls_string;
  606 + }
  607 + else if (QUtil::is_space(*p))
  608 + {
  609 + // ignore
  610 + }
  611 + else if ((*p >= 'a') && (*p <= 'z'))
  612 + {
  613 + tok_start = p;
  614 + tok_end = nullptr;
  615 + lex_state = ls_alpha;
  616 + }
  617 + else if (*p == '-')
  618 + {
  619 + tok_start = p;
  620 + tok_end = nullptr;
  621 + lex_state = ls_number;
  622 + number_before_point = 0;
  623 + number_after_point = 0;
  624 + number_after_e = 0;
  625 + number_saw_point = false;
  626 + number_saw_e = false;
  627 + }
  628 + else if ((*p >= '0') && (*p <= '9'))
  629 + {
  630 + tok_start = p;
  631 + tok_end = nullptr;
  632 + lex_state = ls_number;
  633 + number_before_point = 1;
  634 + number_after_point = 0;
  635 + number_after_e = 0;
  636 + number_saw_point = false;
  637 + number_saw_e = false;
  638 + }
  639 + else if (*p == '.')
  640 + {
  641 + tok_start = p;
  642 + tok_end = nullptr;
  643 + lex_state = ls_number;
  644 + number_before_point = 0;
  645 + number_after_point = 0;
  646 + number_after_e = 0;
  647 + number_saw_point = true;
  648 + number_saw_e = false;
  649 + }
  650 + else if (strchr("{}[]:,", *p))
  651 + {
  652 + tok_start = p;
  653 + tok_end = p + 1;
  654 + }
  655 + else
  656 + {
  657 + QTC::TC("libtests", "JSON parse bad character");
  658 + throw std::runtime_error(
  659 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  660 + ": unexpected character " + std::string(p, 1));
  661 + }
  662 + break;
  663 +
  664 + case ls_number:
  665 + if ((*p >= '0') && (*p <= '9'))
  666 + {
  667 + if (number_saw_e)
  668 + {
  669 + ++number_after_e;
  670 + }
  671 + else if (number_saw_point)
  672 + {
  673 + ++number_after_point;
  674 + }
  675 + else
  676 + {
  677 + ++number_before_point;
  678 + }
  679 + }
  680 + else if (*p == '.')
  681 + {
  682 + if (number_saw_e)
  683 + {
  684 + QTC::TC("libtests", "JSON parse point after e");
  685 + throw std::runtime_error(
  686 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  687 + ": numeric literal: decimal point after e");
  688 + }
  689 + else if (number_saw_point)
  690 + {
  691 + QTC::TC("libtests", "JSON parse duplicate point");
  692 + throw std::runtime_error(
  693 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  694 + ": numeric literal: decimal point already seen");
  695 + }
  696 + else
  697 + {
  698 + number_saw_point = true;
  699 + }
  700 + }
  701 + else if (*p == 'e')
  702 + {
  703 + if (number_saw_e)
  704 + {
  705 + QTC::TC("libtests", "JSON parse duplicate e");
  706 + throw std::runtime_error(
  707 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  708 + ": numeric literal: e already seen");
  709 + }
  710 + else
  711 + {
  712 + number_saw_e = true;
  713 + }
  714 + }
  715 + else if ((*p == '+') || (*p == '-'))
  716 + {
  717 + if (number_saw_e && (number_after_e == 0))
  718 + {
  719 + // okay
  720 + }
  721 + else
  722 + {
  723 + QTC::TC("libtests", "JSON parse unexpected sign");
  724 + throw std::runtime_error(
  725 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  726 + ": numeric literal: unexpected sign");
  727 + }
  728 + }
  729 + else if (QUtil::is_space(*p))
  730 + {
  731 + tok_end = p;
  732 + }
  733 + else if (strchr("{}[]:,", *p))
  734 + {
  735 + tok_end = p;
  736 + --p;
  737 + }
  738 + else
  739 + {
  740 + QTC::TC("libtests", "JSON parse numeric bad character");
  741 + throw std::runtime_error(
  742 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  743 + ": numeric literal: unexpected character " +
  744 + std::string(p, 1));
  745 + }
  746 + break;
  747 +
  748 + case ls_alpha:
  749 + if ((*p >= 'a') && (*p <= 'z'))
  750 + {
  751 + // okay
  752 + }
  753 + else if (QUtil::is_space(*p))
  754 + {
  755 + tok_end = p;
  756 + }
  757 + else if (strchr("{}[]:,", *p))
  758 + {
  759 + tok_end = p;
  760 + --p;
  761 + }
  762 + else
  763 + {
  764 + QTC::TC("libtests", "JSON parse keyword bad character");
  765 + throw std::runtime_error(
  766 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  767 + ": keyword: unexpected character " + std::string(p, 1));
  768 + }
  769 + break;
  770 +
  771 + case ls_string:
  772 + if (*p == '"')
  773 + {
  774 + tok_end = p + 1;
  775 + }
  776 + else if (*p == '\\')
  777 + {
  778 + lex_state = ls_backslash;
  779 + }
  780 + break;
  781 +
  782 + case ls_backslash:
  783 + if (strchr("\\\"bfnrt", *p))
  784 + {
  785 + lex_state = ls_string;
  786 + }
  787 + else if (*p == 'u')
  788 + {
  789 + if (p + 4 >= end)
  790 + {
  791 + QTC::TC("libtests", "JSON parse premature end of u");
  792 + throw std::runtime_error(
  793 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  794 + ": \\u must be followed by four characters");
  795 + }
  796 + for (size_t i = 1; i <= 4; ++i)
  797 + {
  798 + if (! QUtil::is_hex_digit(p[i]))
  799 + {
  800 + QTC::TC("libtests", "JSON parse bad hex after u");
  801 + throw std::runtime_error(
  802 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  803 + ": \\u must be followed by four hex digits");
  804 + }
  805 + }
  806 + p += 4;
  807 + lex_state = ls_string;
  808 + }
  809 + else
  810 + {
  811 + QTC::TC("libtests", "JSON parse backslash bad character");
  812 + throw std::runtime_error(
  813 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  814 + ": invalid character after backslash: " +
  815 + std::string(p, 1));
  816 + }
  817 + break;
  818 + }
  819 + ++p;
  820 + if (tok_start && tok_end)
  821 + {
  822 + break;
  823 + }
  824 + }
  825 + if (p == end)
  826 + {
  827 + if (tok_start && (! tok_end))
  828 + {
  829 + switch (lex_state)
  830 + {
  831 + case ls_top:
  832 + // Can't happen
  833 + throw std::logic_error(
  834 + "tok_start set in ls_top while parsing " +
  835 + std::string(cstr));
  836 + break;
  837 +
  838 + case ls_number:
  839 + case ls_alpha:
  840 + tok_end = p;
  841 + break;
  842 +
  843 + case ls_string:
  844 + case ls_backslash:
  845 + QTC::TC("libtests", "JSON parse unterminated string");
  846 + throw std::runtime_error(
  847 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  848 + ": unterminated string");
  849 + break;
  850 + }
  851 + }
  852 + }
  853 +}
  854 +
  855 +void
  856 +JSONParser::handleToken()
  857 +{
  858 + if (! (tok_start && tok_end))
  859 + {
  860 + return;
  861 + }
  862 +
  863 + // Get token value.
  864 + std::string value(tok_start, tok_end);
  865 +
  866 + if (parser_state == ps_done)
  867 + {
  868 + QTC::TC("libtests", "JSON parse junk after object");
  869 + throw std::runtime_error(
  870 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  871 + ": material follows end of object: " + value);
  872 + }
  873 +
  874 + // Git string value
  875 + std::string svalue;
  876 + if (lex_state == ls_string)
  877 + {
  878 + // Token includes the quotation marks
  879 + if (tok_end - tok_start < 2)
  880 + {
  881 + throw std::logic_error("JSON string length < 2");
  882 + }
  883 + svalue = decode_string(value);
  884 + }
  885 + // Based on the lexical state and value, figure out whether we are
  886 + // looking at an item or a delimiter. It will always be exactly
  887 + // one of those two or an error condition.
  888 +
  889 + PointerHolder<JSON> item;
  890 + char delimiter = '\0';
  891 + switch (lex_state)
  892 + {
  893 + case ls_top:
  894 + switch (*tok_start)
  895 + {
  896 + case '{':
  897 + item = new JSON(JSON::makeDictionary());
  898 + break;
  899 +
  900 + case '[':
  901 + item = new JSON(JSON::makeArray());
  902 + break;
  903 +
  904 + default:
  905 + delimiter = *tok_start;
  906 + break;
  907 + }
  908 + break;
  909 +
  910 + case ls_number:
  911 + if (number_saw_point && (number_after_point == 0))
  912 + {
  913 + QTC::TC("libtests", "JSON parse decimal with no digits");
  914 + throw std::runtime_error(
  915 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  916 + ": decimal point with no digits");
  917 + }
  918 + if ((number_before_point > 1) &&
  919 + ((tok_start[0] == '0') ||
  920 + ((tok_start[0] == '-') && (tok_start[1] == '0'))))
  921 + {
  922 + QTC::TC("libtests", "JSON parse leading zero");
  923 + throw std::runtime_error(
  924 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  925 + ": number with leading zero");
  926 + }
  927 + if ((number_before_point == 0) && (number_after_point == 0))
  928 + {
  929 + QTC::TC("libtests", "JSON parse number no digits");
  930 + throw std::runtime_error(
  931 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  932 + ": number with no digits");
  933 + }
  934 + item = new JSON(JSON::makeNumber(value));
  935 + break;
  936 +
  937 + case ls_alpha:
  938 + if (value == "true")
  939 + {
  940 + item = new JSON(JSON::makeBool(true));
  941 + }
  942 + else if (value == "false")
  943 + {
  944 + item = new JSON(JSON::makeBool(false));
  945 + }
  946 + else if (value == "null")
  947 + {
  948 + item = new JSON(JSON::makeNull());
  949 + }
  950 + else
  951 + {
  952 + QTC::TC("libtests", "JSON parse invalid keyword");
  953 + throw std::runtime_error(
  954 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  955 + ": invalid keyword " + value);
  956 + }
  957 + break;
  958 +
  959 + case ls_string:
  960 + item = new JSON(JSON::makeString(svalue));
  961 + break;
  962 +
  963 + case ls_backslash:
  964 + throw std::logic_error(
  965 + "tok_end is set while state = ls_backslash");
  966 + break;
  967 + }
  968 +
  969 + if ((item.getPointer() == nullptr) == (delimiter == '\0'))
  970 + {
  971 + throw std::logic_error(
  972 + "JSONParser::handleToken: logic error: exactly one of item"
  973 + " or delimiter must be set");
  974 + }
  975 +
  976 + // See whether what we have is allowed at this point.
  977 +
  978 + if (item.getPointer())
  979 + {
  980 + switch (parser_state)
  981 + {
  982 + case ps_done:
  983 + throw std::logic_error("can't happen; ps_done already handled");
  984 + break;
  985 +
  986 + case ps_dict_after_key:
  987 + QTC::TC("libtests", "JSON parse expected colon");
  988 + throw std::runtime_error(
  989 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  990 + ": expected ':'");
  991 + break;
  992 +
  993 + case ps_dict_after_item:
  994 + QTC::TC("libtests", "JSON parse expected , or }");
  995 + throw std::runtime_error(
  996 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  997 + ": expected ',' or '}'");
  998 + break;
  999 +
  1000 + case ps_array_after_item:
  1001 + QTC::TC("libtests", "JSON parse expected, or ]");
  1002 + throw std::runtime_error(
  1003 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1004 + ": expected ',' or ']'");
  1005 + break;
  1006 +
  1007 + case ps_dict_begin:
  1008 + case ps_dict_after_comma:
  1009 + if (lex_state != ls_string)
  1010 + {
  1011 + QTC::TC("libtests", "JSON parse string as dict key");
  1012 + throw std::runtime_error(
  1013 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1014 + ": expect string as dictionary key");
  1015 + }
  1016 + break;
  1017 +
  1018 + case ps_top:
  1019 + case ps_dict_after_colon:
  1020 + case ps_array_begin:
  1021 + case ps_array_after_comma:
  1022 + break;
  1023 + // okay
  1024 + }
  1025 + }
  1026 + else if (delimiter == '}')
  1027 + {
  1028 + if (! ((parser_state == ps_dict_begin) ||
  1029 + (parser_state == ps_dict_after_item)))
  1030 +
  1031 + {
  1032 + QTC::TC("libtests", "JSON parse unexpected }");
  1033 + throw std::runtime_error(
  1034 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1035 + ": unexpected dictionary end delimiter");
  1036 + }
  1037 + }
  1038 + else if (delimiter == ']')
  1039 + {
  1040 + if (! ((parser_state == ps_array_begin) ||
  1041 + (parser_state == ps_array_after_item)))
  1042 +
  1043 + {
  1044 + QTC::TC("libtests", "JSON parse unexpected ]");
  1045 + throw std::runtime_error(
  1046 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1047 + ": unexpected array end delimiter");
  1048 + }
  1049 + }
  1050 + else if (delimiter == ':')
  1051 + {
  1052 + if (parser_state != ps_dict_after_key)
  1053 + {
  1054 + QTC::TC("libtests", "JSON parse unexpected :");
  1055 + throw std::runtime_error(
  1056 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1057 + ": unexpected colon");
  1058 + }
  1059 + }
  1060 + else if (delimiter == ',')
  1061 + {
  1062 + if (! ((parser_state == ps_dict_after_item) ||
  1063 + (parser_state == ps_array_after_item)))
  1064 + {
  1065 + QTC::TC("libtests", "JSON parse unexpected ,");
  1066 + throw std::runtime_error(
  1067 + "JSON: offset " + QUtil::int_to_string(p - cstr) +
  1068 + ": unexpected comma");
  1069 + }
  1070 + }
  1071 + else if (delimiter != '\0')
  1072 + {
  1073 + throw std::logic_error("JSONParser::handleToken: bad delimiter");
  1074 + }
  1075 +
  1076 + // Now we know we have a delimiter or item that is allowed. Do
  1077 + // whatever we need to do with it.
  1078 +
  1079 + parser_state_e next_state = ps_top;
  1080 + if (delimiter == ':')
  1081 + {
  1082 + next_state = ps_dict_after_colon;
  1083 + }
  1084 + else if (delimiter == ',')
  1085 + {
  1086 + if (parser_state == ps_dict_after_item)
  1087 + {
  1088 + next_state = ps_dict_after_comma;
  1089 + }
  1090 + else if (parser_state == ps_array_after_item)
  1091 + {
  1092 + next_state = ps_array_after_comma;
  1093 + }
  1094 + else
  1095 + {
  1096 + throw std::logic_error(
  1097 + "JSONParser::handleToken: unexpected parser"
  1098 + " state for comma");
  1099 + }
  1100 + }
  1101 + else if ((delimiter == '}') || (delimiter == ']'))
  1102 + {
  1103 + next_state = ps_stack.back();
  1104 + ps_stack.pop_back();
  1105 + if (next_state != ps_done)
  1106 + {
  1107 + stack.pop_back();
  1108 + }
  1109 + }
  1110 + else if (delimiter != '\0')
  1111 + {
  1112 + throw std::logic_error(
  1113 + "JSONParser::handleToken: unexpected delimiter in transition");
  1114 + }
  1115 + else if (item.getPointer())
  1116 + {
  1117 + PointerHolder<JSON> tos;
  1118 + if (! stack.empty())
  1119 + {
  1120 + tos = stack.back();
  1121 + }
  1122 + switch (parser_state)
  1123 + {
  1124 + case ps_dict_begin:
  1125 + case ps_dict_after_comma:
  1126 + this->dict_key = svalue;
  1127 + item = nullptr;
  1128 + next_state = ps_dict_after_key;
  1129 + break;
  1130 +
  1131 + case ps_dict_after_colon:
  1132 + tos->addDictionaryMember(dict_key, *item);
  1133 + next_state = ps_dict_after_item;
  1134 + break;
  1135 +
  1136 + case ps_array_begin:
  1137 + case ps_array_after_comma:
  1138 + next_state = ps_array_after_item;
  1139 + tos->addArrayElement(*item);
  1140 + break;
  1141 +
  1142 + case ps_top:
  1143 + next_state = ps_done;
  1144 + break;
  1145 +
  1146 + case ps_dict_after_key:
  1147 + case ps_dict_after_item:
  1148 + case ps_array_after_item:
  1149 + case ps_done:
  1150 + throw std::logic_error(
  1151 + "JSONParser::handleToken: unexpected parser state");
  1152 + }
  1153 + }
  1154 + else
  1155 + {
  1156 + throw std::logic_error(
  1157 + "JSONParser::handleToken: unexpected null item in transition");
  1158 + }
  1159 +
  1160 + // Prepare for next token
  1161 + if (item.getPointer())
  1162 + {
  1163 + if (item->isDictionary())
  1164 + {
  1165 + stack.push_back(item);
  1166 + ps_stack.push_back(next_state);
  1167 + next_state = ps_dict_begin;
  1168 + }
  1169 + else if (item->isArray())
  1170 + {
  1171 + stack.push_back(item);
  1172 + ps_stack.push_back(next_state);
  1173 + next_state = ps_array_begin;
  1174 + }
  1175 + else if (parser_state == ps_top)
  1176 + {
  1177 + stack.push_back(item);
  1178 + }
  1179 + }
  1180 + parser_state = next_state;
  1181 + tok_start = nullptr;
  1182 + tok_end = nullptr;
  1183 + lex_state = ls_top;
  1184 +}
  1185 +
  1186 +PointerHolder<JSON>
  1187 +JSONParser::parse(std::string const& s)
  1188 +{
  1189 + cstr = s.c_str();
  1190 + end = cstr + s.length();
  1191 + p = cstr;
  1192 +
  1193 + while (p < end)
  1194 + {
  1195 + getToken();
  1196 + handleToken();
  1197 + }
  1198 + if (parser_state != ps_done)
  1199 + {
  1200 + QTC::TC("libtests", "JSON parse preature EOF");
  1201 + throw std::runtime_error("JSON: premature end of input");
  1202 + }
  1203 + return stack.back();
  1204 +}
  1205 +
  1206 +JSON
  1207 +JSON::parse(std::string const& s)
  1208 +{
  1209 + JSONParser jp;
  1210 + return *jp.parse(s);
  1211 +}
libtests/build.mk
@@ -13,6 +13,7 @@ BINS_libtests = \ @@ -13,6 +13,7 @@ BINS_libtests = \
13 hex \ 13 hex \
14 input_source \ 14 input_source \
15 json \ 15 json \
  16 + json_parse \
16 lzw \ 17 lzw \
17 main_from_wmain \ 18 main_from_wmain \
18 matrix \ 19 matrix \
libtests/json.cc
@@ -91,67 +91,83 @@ static void check_schema(JSON&amp; obj, JSON&amp; schema, bool exp, @@ -91,67 +91,83 @@ static void check_schema(JSON&amp; obj, JSON&amp; schema, bool exp,
91 91
92 static void test_schema() 92 static void test_schema()
93 { 93 {
94 - // Since we don't have a JSON parser, use the PDF parser as a  
95 - // shortcut for creating a complex JSON structure.  
96 - JSON schema = QPDFObjectHandle::parse(  
97 - "<<"  
98 - " /one <<"  
99 - " /a <<"  
100 - " /q (queue)"  
101 - " /r <<"  
102 - " /x (ecks)"  
103 - " /y (why)"  
104 - " >>"  
105 - " /s [ (esses) ]"  
106 - " >>"  
107 - " >>"  
108 - " /two ["  
109 - " <<"  
110 - " /goose (gander)"  
111 - " /glarp (enspliel)"  
112 - " >>"  
113 - " ]"  
114 - ">>").getJSON();  
115 - JSON three = JSON::makeDictionary();  
116 - three.addDictionaryMember(  
117 - "<objid>",  
118 - QPDFObjectHandle::parse("<< /z (ebra) >>").getJSON());  
119 - schema.addDictionaryMember("/three", three);  
120 - JSON a = QPDFObjectHandle::parse("[(not a) (dictionary)]").getJSON(); 94 + JSON schema = JSON::parse(R"(
  95 +{
  96 + "one": {
  97 + "a": {
  98 + "q": "queue",
  99 + "r": {
  100 + "x": "ecks",
  101 + "y": "(bool) why"
  102 + },
  103 + "s": [
  104 + "esses"
  105 + ]
  106 + }
  107 + },
  108 + "two": [
  109 + {
  110 + "goose": "gander",
  111 + "glarp": "enspliel"
  112 + }
  113 + ],
  114 + "three": {
  115 + "<objid>": {
  116 + "z": "ebra",
  117 + "o": "(optional, string) optional"
  118 + }
  119 + }
  120 +}
  121 +)");
  122 +
  123 + JSON a = JSON::parse(R"(["not a", "dictionary"])");
121 check_schema(a, schema, false, "top-level type mismatch"); 124 check_schema(a, schema, false, "top-level type mismatch");
122 - JSON b = QPDFObjectHandle::parse(  
123 - "<<"  
124 - " /one <<"  
125 - " /a <<"  
126 - " /t (oops)"  
127 - " /r ["  
128 - " /x (ecks)"  
129 - " /y (why)"  
130 - " ]"  
131 - " /s << /z (esses) >>"  
132 - " >>"  
133 - " >>"  
134 - " /two ["  
135 - " <<"  
136 - " /goose (0 gander)"  
137 - " /glarp (0 enspliel)"  
138 - " >>"  
139 - " <<"  
140 - " /goose (1 gander)"  
141 - " /flarp (1 enspliel)"  
142 - " >>"  
143 - " 2"  
144 - " [ (three) ]"  
145 - " <<"  
146 - " /goose (4 gander)"  
147 - " /glarp (4 enspliel)"  
148 - " >>"  
149 - " ]"  
150 - " /three <<"  
151 - " /anything << /x (oops) >>"  
152 - " /else << /z (okay) >>"  
153 - " >>"  
154 - ">>").getJSON(); 125 + JSON b = JSON::parse(R"(
  126 +{
  127 + "one": {
  128 + "a": {
  129 + "t": "oops",
  130 + "r": [
  131 + "x",
  132 + "ecks",
  133 + "y",
  134 + "why"
  135 + ],
  136 + "s": {
  137 + "z": "esses"
  138 + }
  139 + }
  140 + },
  141 + "two": [
  142 + {
  143 + "goose": "0 gander",
  144 + "glarp": "0 enspliel"
  145 + },
  146 + {
  147 + "goose": "1 gander",
  148 + "flarp": "1 enspliel"
  149 + },
  150 + 2,
  151 + [
  152 + "three"
  153 + ],
  154 + {
  155 + "goose": "4 gander",
  156 + "glarp": 4
  157 + }
  158 + ],
  159 + "three": {
  160 + "anything": {
  161 + "x": "oops",
  162 + "o": "okay"
  163 + },
  164 + "else": {
  165 + "z": "okay"
  166 + }
  167 + }
  168 +}
  169 +)");
  170 +
155 check_schema(b, schema, false, "missing items"); 171 check_schema(b, schema, false, "missing items");
156 check_schema(a, a, false, "top-level schema array error"); 172 check_schema(a, a, false, "top-level schema array error");
157 check_schema(b, b, false, "lower-level schema array error"); 173 check_schema(b, b, false, "lower-level schema array error");
libtests/json_parse.cc 0 → 100644
  1 +#include <qpdf/JSON.hh>
  2 +#include <qpdf/QUtil.hh>
  3 +#include <iostream>
  4 +
  5 +int main(int argc, char* argv[])
  6 +{
  7 + if (argc != 2)
  8 + {
  9 + std::cerr << "Usage: json_parse file" << std::endl;
  10 + return 2;
  11 + }
  12 + char const* filename = argv[1];
  13 + try
  14 + {
  15 + PointerHolder<char> buf;
  16 + size_t size;
  17 + QUtil::read_file_into_memory(filename, buf, size);
  18 + std::string s(buf.getPointer(), size);
  19 + std::cout << JSON::parse(s).unparse() << std::endl;
  20 + }
  21 + catch (std::exception& e)
  22 + {
  23 + std::cerr << "exception: " << filename<< ": " << e.what() << std::endl;
  24 + return 2;
  25 + }
  26 + return 0;
  27 +}
libtests/libtests.testcov
@@ -61,3 +61,29 @@ QPDFArgParser duplicate option help 0 @@ -61,3 +61,29 @@ QPDFArgParser duplicate option help 0
61 QPDFArgParser bad option for help 0 61 QPDFArgParser bad option for help 0
62 QPDFArgParser bad topic for help 0 62 QPDFArgParser bad topic for help 0
63 QPDFArgParser invalid choice handler to unknown 0 63 QPDFArgParser invalid choice handler to unknown 0
  64 +JSON parse junk after object 0
  65 +JSON parse decimal with no digits 0
  66 +JSON parse invalid keyword 0
  67 +JSON parse expected colon 0
  68 +JSON parse expected , or } 0
  69 +JSON parse expected, or ] 0
  70 +JSON parse string as dict key 0
  71 +JSON parse unexpected } 0
  72 +JSON parse unexpected ] 0
  73 +JSON parse unexpected : 0
  74 +JSON parse unexpected , 0
  75 +JSON parse preature EOF 0
  76 +JSON parse null character 0
  77 +JSON parse bad character 0
  78 +JSON parse point after e 0
  79 +JSON parse duplicate point 0
  80 +JSON parse duplicate e 0
  81 +JSON parse unexpected sign 0
  82 +JSON parse numeric bad character 0
  83 +JSON parse keyword bad character 0
  84 +JSON parse backslash bad character 0
  85 +JSON parse unterminated string 0
  86 +JSON parse leading zero 0
  87 +JSON parse number no digits 0
  88 +JSON parse premature end of u 0
  89 +JSON parse bad hex after u 0
libtests/qtest/json/json.out
@@ -2,23 +2,24 @@ @@ -2,23 +2,24 @@
2 top-level object is supposed to be a dictionary 2 top-level object is supposed to be a dictionary
3 --- 3 ---
4 --- missing items 4 --- missing items
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 "./three./anything": key "/z" is present in schema but missing in object  
10 -json key "./three./anything": key "/x" is not present in schema but appears in object  
11 -json key "./two.1": key "/glarp" is present in schema but missing in object  
12 -json key "./two.1": key "/flarp" is not present in schema but appears in object  
13 -json key "./two.2" is supposed to be a dictionary  
14 -json key "./two.3" is supposed to be a dictionary 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 ".three.anything": key "z" is present in schema but missing in object
  10 +json key ".three.anything": key "x" is not present in schema but appears in object
  11 +json key ".three.else": key "o" is present in schema but missing in object
  12 +json key ".two.1": key "glarp" is present in schema but missing in object
  13 +json key ".two.1": key "flarp" is not present in schema but appears in object
  14 +json key ".two.2" is supposed to be a dictionary
  15 +json key ".two.3" is supposed to be a dictionary
15 --- 16 ---
16 --- top-level schema array error 17 --- top-level schema array error
17 top-level object schema array contains other than one item 18 top-level object schema array contains other than one item
18 --- 19 ---
19 --- lower-level schema array error 20 --- lower-level schema array error
20 -json key "./one./a./r" schema array contains other than one item  
21 -json key "./two" schema array contains other than one item 21 +json key ".one.a.r" schema array contains other than one item
  22 +json key ".two" schema array contains other than one item
22 --- 23 ---
23 --- pass 24 --- pass
24 --- 25 ---
libtests/qtest/json_parse.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +use File::Copy;
  6 +use File::Compare;
  7 +
  8 +chdir("json_parse") or die "chdir testdir failed: $!\n";
  9 +
  10 +require TestDriver;
  11 +
  12 +my $td = new TestDriver('json_parse');
  13 +
  14 +my $json_mod = 0;
  15 +eval {
  16 + require JSON;
  17 + $json_mod = 1;
  18 +};
  19 +if ($@)
  20 +{
  21 + $td->emphasize("JSON.pm not found -- using stored actual outputs");
  22 +}
  23 +
  24 +cleanup();
  25 +
  26 +my $good = 9;
  27 +
  28 +for (my $i = 1; $i <= $good; ++$i)
  29 +{
  30 + my $n = sprintf("%02d", $i);
  31 +
  32 + unlink "out.json";
  33 + my $r = system("json_parse good-$n.json > out.json 2>&1");
  34 + if ($td->runtest("json_parse accepted $n",
  35 + {$td->STRING => "$r\n"},
  36 + {$td->STRING => "0\n"},
  37 + $td->NORMALIZE_NEWLINES))
  38 + {
  39 + if ($json_mod)
  40 + {
  41 + if ($td->runtest("check output $n",
  42 + {$td->STRING => normalize_json("out.json")},
  43 + {$td->STRING => normalize_json("good-$n.json")},
  44 + $td->NORMALIZE_NEWLINES))
  45 + {
  46 + if (compare("out.json", "save-$n.json"))
  47 + {
  48 + copy("out.json", "save-$n.json");
  49 + $td->emphasize("updated save-$n.json from out.json");
  50 + }
  51 + }
  52 + }
  53 + else
  54 + {
  55 + $td->runtest("check output $n against saved",
  56 + {$td->FILE => "out.json"},
  57 + {$td->FILE => "save-$n.json"},
  58 + $td->NORMALIZE_NEWLINES);
  59 + }
  60 + }
  61 + else
  62 + {
  63 + $td->runtest("skip checking output $n",
  64 + {$td->FILE => "out.json"},
  65 + {$td->STRING => ""});
  66 + }
  67 +}
  68 +
  69 +my @bad = (
  70 + "junk after string", # 1
  71 + "junk after array", # 2
  72 + "junk after dictionary", # 3
  73 + "bad number", # 4
  74 + "invalid keyword", # 5
  75 + "missing colon", # 6
  76 + "missing comma in dict", # 7
  77 + "missing comma in array", # 8
  78 + "dict key not string", # 9
  79 + "unexpected } in array", # 10
  80 + "unexpected } at top", # 11
  81 + "unexpected } in dict", # 12
  82 + "unexpected ] in dict", # 13
  83 + "unexpected ] at top", # 14
  84 + "unexpected :", # 15
  85 + "unexpected ,", # 16
  86 + "premature end array", # 17
  87 + "null character", # 18
  88 + "unexpected character", # 19
  89 + "point in exponent", # 20
  90 + "duplicate point", # 21
  91 + "duplicate e", # 22
  92 + "stray +", # 23
  93 + "bad character in number", # 24
  94 + "bad character in keyword", # 25
  95 + "bad backslash character", # 26
  96 + "unterminated string", # 27
  97 + "unterminated after \\", # 28
  98 + "leading +", # 29
  99 + "decimal with no digits", # 30
  100 + "minus with no digits", # 31
  101 + "leading zero", # 32
  102 + "leading zero negative", # 33
  103 + "premature end after u", # 34
  104 + "bad hex digit", # 35
  105 + );
  106 +
  107 +my $i = 0;
  108 +foreach my $d (@bad)
  109 +{
  110 + ++$i;
  111 + my $n = sprintf("%02d", $i);
  112 + $td->runtest("$n: $d",
  113 + {$td->COMMAND => "json_parse bad-$n.json"},
  114 + {$td->FILE => "bad-$n.out", $td->EXIT_STATUS => 2},
  115 + $td->NORMALIZE_NEWLINES);
  116 +}
  117 +
  118 +cleanup();
  119 +
  120 +$td->report((2 * $good) + scalar(@bad));
  121 +
  122 +sub cleanup
  123 +{
  124 + unlink "out.json";
  125 +}
  126 +
  127 +sub normalize_json
  128 +{
  129 + my $file = shift;
  130 + open(F, "<$file") or die "can't open $file: $file: $!\n";
  131 + $/ = undef;
  132 + my $encoded = scalar(<F>);
  133 + close(F);
  134 + my $j = JSON->new->allow_nonref;
  135 + $j->canonical();
  136 + $j->utf8->pretty->encode($j->utf8->decode($encoded));
  137 +}
libtests/qtest/json_parse/bad-01.json 0 → 100644
  1 +"a" junk
libtests/qtest/json_parse/bad-01.out 0 → 100644
  1 +exception: bad-01.json: JSON: offset 9: material follows end of object: junk
libtests/qtest/json_parse/bad-02.json 0 → 100644
  1 +["a"] junk
libtests/qtest/json_parse/bad-02.out 0 → 100644
  1 +exception: bad-02.json: JSON: offset 11: material follows end of object: junk
libtests/qtest/json_parse/bad-03.json 0 → 100644
  1 +{"a": "b"} junk
libtests/qtest/json_parse/bad-03.out 0 → 100644
  1 +exception: bad-03.json: JSON: offset 16: material follows end of object: junk
libtests/qtest/json_parse/bad-04.json 0 → 100644
  1 +[1, .]
  2 +
libtests/qtest/json_parse/bad-04.out 0 → 100644
  1 +exception: bad-04.json: JSON: offset 5: decimal point with no digits
libtests/qtest/json_parse/bad-05.json 0 → 100644
  1 +[true, potato]
libtests/qtest/json_parse/bad-05.out 0 → 100644
  1 +exception: bad-05.json: JSON: offset 13: invalid keyword potato
libtests/qtest/json_parse/bad-06.json 0 → 100644
  1 +{"x" "y"}
libtests/qtest/json_parse/bad-06.out 0 → 100644
  1 +exception: bad-06.json: JSON: offset 8: expected ':'
libtests/qtest/json_parse/bad-07.json 0 → 100644
  1 +{"x": 3 "y"}
libtests/qtest/json_parse/bad-07.out 0 → 100644
  1 +exception: bad-07.json: JSON: offset 11: expected ',' or '}'
libtests/qtest/json_parse/bad-08.json 0 → 100644
  1 +["x" "y"]
libtests/qtest/json_parse/bad-08.out 0 → 100644
  1 +exception: bad-08.json: JSON: offset 8: expected ',' or ']'
libtests/qtest/json_parse/bad-09.json 0 → 100644
  1 +{5 : 5}
libtests/qtest/json_parse/bad-09.out 0 → 100644
  1 +exception: bad-09.json: JSON: offset 3: expect string as dictionary key
libtests/qtest/json_parse/bad-10.json 0 → 100644
  1 +["a"}
libtests/qtest/json_parse/bad-10.out 0 → 100644
  1 +exception: bad-10.json: JSON: offset 5: unexpected dictionary end delimiter
libtests/qtest/json_parse/bad-11.json 0 → 100644
  1 +}
libtests/qtest/json_parse/bad-11.out 0 → 100644
  1 +exception: bad-11.json: JSON: offset 1: unexpected dictionary end delimiter
libtests/qtest/json_parse/bad-12.json 0 → 100644
  1 +{""}
libtests/qtest/json_parse/bad-12.out 0 → 100644
  1 +exception: bad-12.json: JSON: offset 4: unexpected dictionary end delimiter
libtests/qtest/json_parse/bad-13.json 0 → 100644
  1 +{"": "x"]
libtests/qtest/json_parse/bad-13.out 0 → 100644
  1 +exception: bad-13.json: JSON: offset 9: unexpected array end delimiter
libtests/qtest/json_parse/bad-14.json 0 → 100644
  1 +]
libtests/qtest/json_parse/bad-14.out 0 → 100644
  1 +exception: bad-14.json: JSON: offset 1: unexpected array end delimiter
libtests/qtest/json_parse/bad-15.json 0 → 100644
  1 +["a": ]
libtests/qtest/json_parse/bad-15.out 0 → 100644
  1 +exception: bad-15.json: JSON: offset 5: unexpected colon
libtests/qtest/json_parse/bad-16.json 0 → 100644
  1 +[,]
libtests/qtest/json_parse/bad-16.out 0 → 100644
  1 +exception: bad-16.json: JSON: offset 2: unexpected comma
libtests/qtest/json_parse/bad-17.json 0 → 100644
  1 +[1, 2,
libtests/qtest/json_parse/bad-17.out 0 → 100644
  1 +exception: bad-17.json: JSON: premature end of input
libtests/qtest/json_parse/bad-18.json 0 → 100644
No preview for this file type
libtests/qtest/json_parse/bad-18.out 0 → 100644
  1 +exception: bad-18.json: JSON: null character at offset 5
libtests/qtest/json_parse/bad-19.json 0 → 100644
  1 +/
libtests/qtest/json_parse/bad-19.out 0 → 100644
  1 +exception: bad-19.json: JSON: offset 0: unexpected character /
libtests/qtest/json_parse/bad-20.json 0 → 100644
  1 +3.14e5.6
libtests/qtest/json_parse/bad-20.out 0 → 100644
  1 +exception: bad-20.json: JSON: offset 6: numeric literal: decimal point after e
libtests/qtest/json_parse/bad-21.json 0 → 100644
  1 +3.14.159
libtests/qtest/json_parse/bad-21.out 0 → 100644
  1 +exception: bad-21.json: JSON: offset 4: numeric literal: decimal point already seen
libtests/qtest/json_parse/bad-22.json 0 → 100644
  1 +3e4e5
libtests/qtest/json_parse/bad-22.out 0 → 100644
  1 +exception: bad-22.json: JSON: offset 3: numeric literal: e already seen
libtests/qtest/json_parse/bad-23.json 0 → 100644
  1 +3+4
libtests/qtest/json_parse/bad-23.out 0 → 100644
  1 +exception: bad-23.json: JSON: offset 1: numeric literal: unexpected sign
libtests/qtest/json_parse/bad-24.json 0 → 100644
  1 +12x
libtests/qtest/json_parse/bad-24.out 0 → 100644
  1 +exception: bad-24.json: JSON: offset 2: numeric literal: unexpected character x
libtests/qtest/json_parse/bad-25.json 0 → 100644
  1 +abc1
libtests/qtest/json_parse/bad-25.out 0 → 100644
  1 +exception: bad-25.json: JSON: offset 3: keyword: unexpected character 1
libtests/qtest/json_parse/bad-26.json 0 → 100644
  1 +"abc\yd"
libtests/qtest/json_parse/bad-26.out 0 → 100644
  1 +exception: bad-26.json: JSON: offset 5: invalid character after backslash: y
libtests/qtest/json_parse/bad-27.json 0 → 100644
  1 +"abcd
libtests/qtest/json_parse/bad-27.out 0 → 100644
  1 +exception: bad-27.json: JSON: offset 6: unterminated string
libtests/qtest/json_parse/bad-28.json 0 → 100644
  1 +"abc-no-newline\
0 \ No newline at end of file 2 \ No newline at end of file
libtests/qtest/json_parse/bad-28.out 0 → 100644
  1 +exception: bad-28.json: JSON: offset 16: unterminated string
libtests/qtest/json_parse/bad-29.json 0 → 100644
  1 ++123.
libtests/qtest/json_parse/bad-29.out 0 → 100644
  1 +exception: bad-29.json: JSON: offset 0: unexpected character +
libtests/qtest/json_parse/bad-30.json 0 → 100644
  1 +123.
libtests/qtest/json_parse/bad-30.out 0 → 100644
  1 +exception: bad-30.json: JSON: offset 5: decimal point with no digits
libtests/qtest/json_parse/bad-31.json 0 → 100644
  1 +-
libtests/qtest/json_parse/bad-31.out 0 → 100644
  1 +exception: bad-31.json: JSON: offset 2: number with no digits
libtests/qtest/json_parse/bad-32.json 0 → 100644
  1 +0123
libtests/qtest/json_parse/bad-32.out 0 → 100644
  1 +exception: bad-32.json: JSON: offset 5: number with leading zero
libtests/qtest/json_parse/bad-33.json 0 → 100644
  1 +-0123
libtests/qtest/json_parse/bad-33.out 0 → 100644
  1 +exception: bad-33.json: JSON: offset 6: number with leading zero
libtests/qtest/json_parse/bad-34.json 0 → 100644
  1 +"a\u123
0 \ No newline at end of file 2 \ No newline at end of file
libtests/qtest/json_parse/bad-34.out 0 → 100644
  1 +exception: bad-34.json: JSON: offset 3: \u must be followed by four characters
libtests/qtest/json_parse/bad-35.json 0 → 100644
  1 +"a\u123qx"
libtests/qtest/json_parse/bad-35.out 0 → 100644
  1 +exception: bad-35.json: JSON: offset 3: \u must be followed by four hex digits
libtests/qtest/json_parse/good-01.json 0 → 100644
  1 +{"a": "bcd", "e": [1,
  2 + 2, 3,4,"five", {"six": 7, "8": 9}, null, true,
  3 + false]}
libtests/qtest/json_parse/good-02.json 0 → 100644
  1 +{}
libtests/qtest/json_parse/good-03.json 0 → 100644
  1 +[]
libtests/qtest/json_parse/good-04.json 0 → 100644
  1 +[[[{}], {"": {}}]]
libtests/qtest/json_parse/good-05.json 0 → 100644
  1 +"x"
libtests/qtest/json_parse/good-06.json 0 → 100644
  1 +123
libtests/qtest/json_parse/good-07.json 0 → 100644
  1 +-123
libtests/qtest/json_parse/good-08.json 0 → 100644
  1 +[1, -2, 3.4, -5.6, -9e1, 10e2, 12.3e5, 12.6e-7]
libtests/qtest/json_parse/good-09.json 0 → 100644
  1 +["aπb", "a\b\f\n\r\tc", "a\u03c0b\u03C0c", "\u03c0", "a\u0018b\u02acc"]
libtests/qtest/json_parse/save-01.json 0 → 100644
  1 +{
  2 + "a": "bcd",
  3 + "e": [
  4 + 1,
  5 + 2,
  6 + 3,
  7 + 4,
  8 + "five",
  9 + {
  10 + "8": 9,
  11 + "six": 7
  12 + },
  13 + null,
  14 + true,
  15 + false
  16 + ]
  17 +}
libtests/qtest/json_parse/save-02.json 0 → 100644
  1 +{}
libtests/qtest/json_parse/save-03.json 0 → 100644
  1 +[]
libtests/qtest/json_parse/save-04.json 0 → 100644
  1 +[
  2 + [
  3 + [
  4 + {}
  5 + ],
  6 + {
  7 + "": {}
  8 + }
  9 + ]
  10 +]
libtests/qtest/json_parse/save-05.json 0 → 100644
  1 +"x"
libtests/qtest/json_parse/save-06.json 0 → 100644
  1 +123
libtests/qtest/json_parse/save-07.json 0 → 100644
  1 +-123
libtests/qtest/json_parse/save-08.json 0 → 100644
  1 +[
  2 + 1,
  3 + -2,
  4 + 3.4,
  5 + -5.6,
  6 + -9e1,
  7 + 10e2,
  8 + 12.3e5,
  9 + 12.6e-7
  10 +]
libtests/qtest/json_parse/save-09.json 0 → 100644
  1 +[
  2 + "aπb",
  3 + "a\b\f\n\r\tc",
  4 + "aπbπc",
  5 + "π",
  6 + "a\u0018bʬc"
  7 +]