Commit 4c7cfd5cbc64c34b4532aad0d87e4c81e2277b02

Authored by Jay Berkenbilt
1 parent 2a2f7f1b

JSON reactor: improve handling of nested containers

Call the parent container's item method before calling the child
item's start method so we can easily know the current nesting level
when nested items are added.
include/qpdf/JSON.hh
... ... @@ -220,7 +220,10 @@ class JSON
220 220 // The start/end methods are called when parsing of a
221 221 // dictionary or array is started or ended. The item methods
222 222 // are called when an item is added to a dictionary or array.
223   - // See important notes in "Item methods" below.
  223 + // When adding a container to another container, the item
  224 + // method is called with an empty container before the lower
  225 + // container's start method is called. See important notes in
  226 + // "Item methods" below.
224 227  
225 228 // During parsing of a JSON string, the parser is operating on
226 229 // a single object at a time. When a dictionary or array is
... ... @@ -230,10 +233,10 @@ class JSON
230 233 // following method calls
231 234 //
232 235 // dictionaryStart -- current object is the top-level dictionary
  236 + // dictionaryItem -- called with "a" and an empty array
233 237 // arrayStart -- current object is the array
234 238 // arrayItem -- called with the "1" object
235 239 // containerEnd -- now current object is the dictionary again
236   - // dictionaryItem -- called with "a" and the just-completed array
237 240 // containerEnd -- current object is undefined
238 241 //
239 242 // If the top-level item in a JSON string is a scalar, the
... ... @@ -261,8 +264,12 @@ class JSON
261 264 // NOTE: When a dictionary or an array is added to a
262 265 // container, the dictionaryItem or arrayItem method is called
263 266 // when the child item's start delimiter is encountered, so
264   - // the JSON object passed in at that time will always be
265   - // in its initial, empty state.
  267 + // the JSON object passed in at that time will always be in
  268 + // its initial, empty state. Additionally, the child item's
  269 + // start method is not called until after the parent item's
  270 + // item method is called. This makes it possible to keep track
  271 + // of the current depth level by incrementing level on start
  272 + // methods and decrementing on end methods.
266 273  
267 274 QPDF_DLL
268 275 virtual bool
... ...
libqpdf/JSON.cc
... ... @@ -949,17 +949,11 @@ JSONParser::handleToken()
949 949 case '{':
950 950 item = std::make_shared<JSON>(JSON::makeDictionary());
951 951 item->setStart(offset - token.length());
952   - if (reactor) {
953   - reactor->dictionaryStart();
954   - }
955 952 break;
956 953  
957 954 case '[':
958 955 item = std::make_shared<JSON>(JSON::makeArray());
959 956 item->setStart(offset - token.length());
960   - if (reactor) {
961   - reactor->arrayStart();
962   - }
963 957 break;
964 958  
965 959 default:
... ... @@ -1187,6 +1181,18 @@ JSONParser::handleToken()
1187 1181 "JSONParser::handleToken: unexpected null item in transition");
1188 1182 }
1189 1183  
  1184 + if (reactor && item.get()) {
  1185 + // Calling container start method is postponed until after
  1186 + // adding the containers to their parent containers, if any.
  1187 + // This makes it much easier to keep track of the current
  1188 + // nesting level.
  1189 + if (item->isDictionary()) {
  1190 + reactor->dictionaryStart();
  1191 + } else if (item->isArray()) {
  1192 + reactor->arrayStart();
  1193 + }
  1194 + }
  1195 +
1190 1196 // Prepare for next token
1191 1197 if (item.get()) {
1192 1198 if (item->isDictionary()) {
... ...
libtests/qtest/json_parse/good-01-react.out
1 1 dictionary start
2 2 dictionary item: a -> [6, 11): "bcd"
3   -array start
4 3 dictionary item: e -> [18, 0): []
  4 +array start
5 5 array item: [19, 20): 1
6 6 array item: [41, 42): 2
7 7 array item: [44, 45): 3
8 8 array item: [46, 47): 4
9 9 array item: [48, 54): "five"
10   -dictionary start
11 10 array item: [56, 0): {}
  11 +dictionary start
12 12 dictionary item: six -> [64, 65): 7
13 13 dictionary item: 8 -> [72, 73): 9
14 14 container end: [56, 74): {}
... ...
libtests/qtest/json_parse/good-04-react.out
1 1 array start
2   -array start
3 2 array item: [1, 0): []
4 3 array start
5 4 array item: [2, 0): []
6   -dictionary start
  5 +array start
7 6 array item: [3, 0): {}
  7 +dictionary start
8 8 container end: [3, 5): {}
9 9 container end: [2, 6): []
10   -dictionary start
11 10 array item: [8, 0): {}
12 11 dictionary start
13 12 dictionary item: -> [13, 0): {}
  13 +dictionary start
14 14 container end: [13, 15): {}
15 15 container end: [8, 16): {}
16 16 container end: [1, 17): []
... ...
libtests/qtest/json_parse/good-10-react.out
1 1 dictionary start
2   -array start
3 2 dictionary item: a -> [9, 0): []
  3 +array start
4 4 array item: [10, 11): 1
5 5 array item: [13, 14): 2
6   -dictionary start
7 6 array item: [16, 0): {}
  7 +dictionary start
8 8 dictionary item: x -> [22, 25): "y"
9 9 container end: [16, 26): {}
10 10 array item: [28, 29): 3
11   -dictionary start
12 11 array item: [31, 0): {}
  12 +dictionary start
13 13 dictionary item: keep -> [40, 61): "not in final output"
14 14 container end: [31, 62): {
15 15 "keep": "not in final output"
16 16 }
17 17 container end: [9, 63): []
18   -array start
19 18 dictionary item: keep -> [75, 0): []
  19 +array start
20 20 array item: [76, 77): 1
21 21 array item: [79, 83): null
22 22 array item: [85, 86): 2
23 23 array item: [88, 93): false
24 24 array item: [95, 101): "keep"
25 25 array item: [103, 104): 3
26   -array start
27 26 array item: [106, 0): []
  27 +array start
28 28 array item: [107, 113): "this"
29 29 array item: [115, 121): "keep"
30 30 array item: [123, 128): "not"
... ...