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,7 +220,10 @@ class JSON
220 // The start/end methods are called when parsing of a 220 // The start/end methods are called when parsing of a
221 // dictionary or array is started or ended. The item methods 221 // dictionary or array is started or ended. The item methods
222 // are called when an item is added to a dictionary or array. 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 // During parsing of a JSON string, the parser is operating on 228 // During parsing of a JSON string, the parser is operating on
226 // a single object at a time. When a dictionary or array is 229 // a single object at a time. When a dictionary or array is
@@ -230,10 +233,10 @@ class JSON @@ -230,10 +233,10 @@ class JSON
230 // following method calls 233 // following method calls
231 // 234 //
232 // dictionaryStart -- current object is the top-level dictionary 235 // dictionaryStart -- current object is the top-level dictionary
  236 + // dictionaryItem -- called with "a" and an empty array
233 // arrayStart -- current object is the array 237 // arrayStart -- current object is the array
234 // arrayItem -- called with the "1" object 238 // arrayItem -- called with the "1" object
235 // containerEnd -- now current object is the dictionary again 239 // containerEnd -- now current object is the dictionary again
236 - // dictionaryItem -- called with "a" and the just-completed array  
237 // containerEnd -- current object is undefined 240 // containerEnd -- current object is undefined
238 // 241 //
239 // If the top-level item in a JSON string is a scalar, the 242 // If the top-level item in a JSON string is a scalar, the
@@ -261,8 +264,12 @@ class JSON @@ -261,8 +264,12 @@ class JSON
261 // NOTE: When a dictionary or an array is added to a 264 // NOTE: When a dictionary or an array is added to a
262 // container, the dictionaryItem or arrayItem method is called 265 // container, the dictionaryItem or arrayItem method is called
263 // when the child item's start delimiter is encountered, so 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 QPDF_DLL 274 QPDF_DLL
268 virtual bool 275 virtual bool
libqpdf/JSON.cc
@@ -949,17 +949,11 @@ JSONParser::handleToken() @@ -949,17 +949,11 @@ JSONParser::handleToken()
949 case '{': 949 case '{':
950 item = std::make_shared<JSON>(JSON::makeDictionary()); 950 item = std::make_shared<JSON>(JSON::makeDictionary());
951 item->setStart(offset - token.length()); 951 item->setStart(offset - token.length());
952 - if (reactor) {  
953 - reactor->dictionaryStart();  
954 - }  
955 break; 952 break;
956 953
957 case '[': 954 case '[':
958 item = std::make_shared<JSON>(JSON::makeArray()); 955 item = std::make_shared<JSON>(JSON::makeArray());
959 item->setStart(offset - token.length()); 956 item->setStart(offset - token.length());
960 - if (reactor) {  
961 - reactor->arrayStart();  
962 - }  
963 break; 957 break;
964 958
965 default: 959 default:
@@ -1187,6 +1181,18 @@ JSONParser::handleToken() @@ -1187,6 +1181,18 @@ JSONParser::handleToken()
1187 "JSONParser::handleToken: unexpected null item in transition"); 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 // Prepare for next token 1196 // Prepare for next token
1191 if (item.get()) { 1197 if (item.get()) {
1192 if (item->isDictionary()) { 1198 if (item->isDictionary()) {
libtests/qtest/json_parse/good-01-react.out
1 dictionary start 1 dictionary start
2 dictionary item: a -> [6, 11): "bcd" 2 dictionary item: a -> [6, 11): "bcd"
3 -array start  
4 dictionary item: e -> [18, 0): [] 3 dictionary item: e -> [18, 0): []
  4 +array start
5 array item: [19, 20): 1 5 array item: [19, 20): 1
6 array item: [41, 42): 2 6 array item: [41, 42): 2
7 array item: [44, 45): 3 7 array item: [44, 45): 3
8 array item: [46, 47): 4 8 array item: [46, 47): 4
9 array item: [48, 54): "five" 9 array item: [48, 54): "five"
10 -dictionary start  
11 array item: [56, 0): {} 10 array item: [56, 0): {}
  11 +dictionary start
12 dictionary item: six -> [64, 65): 7 12 dictionary item: six -> [64, 65): 7
13 dictionary item: 8 -> [72, 73): 9 13 dictionary item: 8 -> [72, 73): 9
14 container end: [56, 74): {} 14 container end: [56, 74): {}
libtests/qtest/json_parse/good-04-react.out
1 array start 1 array start
2 -array start  
3 array item: [1, 0): [] 2 array item: [1, 0): []
4 array start 3 array start
5 array item: [2, 0): [] 4 array item: [2, 0): []
6 -dictionary start 5 +array start
7 array item: [3, 0): {} 6 array item: [3, 0): {}
  7 +dictionary start
8 container end: [3, 5): {} 8 container end: [3, 5): {}
9 container end: [2, 6): [] 9 container end: [2, 6): []
10 -dictionary start  
11 array item: [8, 0): {} 10 array item: [8, 0): {}
12 dictionary start 11 dictionary start
13 dictionary item: -> [13, 0): {} 12 dictionary item: -> [13, 0): {}
  13 +dictionary start
14 container end: [13, 15): {} 14 container end: [13, 15): {}
15 container end: [8, 16): {} 15 container end: [8, 16): {}
16 container end: [1, 17): [] 16 container end: [1, 17): []
libtests/qtest/json_parse/good-10-react.out
1 dictionary start 1 dictionary start
2 -array start  
3 dictionary item: a -> [9, 0): [] 2 dictionary item: a -> [9, 0): []
  3 +array start
4 array item: [10, 11): 1 4 array item: [10, 11): 1
5 array item: [13, 14): 2 5 array item: [13, 14): 2
6 -dictionary start  
7 array item: [16, 0): {} 6 array item: [16, 0): {}
  7 +dictionary start
8 dictionary item: x -> [22, 25): "y" 8 dictionary item: x -> [22, 25): "y"
9 container end: [16, 26): {} 9 container end: [16, 26): {}
10 array item: [28, 29): 3 10 array item: [28, 29): 3
11 -dictionary start  
12 array item: [31, 0): {} 11 array item: [31, 0): {}
  12 +dictionary start
13 dictionary item: keep -> [40, 61): "not in final output" 13 dictionary item: keep -> [40, 61): "not in final output"
14 container end: [31, 62): { 14 container end: [31, 62): {
15 "keep": "not in final output" 15 "keep": "not in final output"
16 } 16 }
17 container end: [9, 63): [] 17 container end: [9, 63): []
18 -array start  
19 dictionary item: keep -> [75, 0): [] 18 dictionary item: keep -> [75, 0): []
  19 +array start
20 array item: [76, 77): 1 20 array item: [76, 77): 1
21 array item: [79, 83): null 21 array item: [79, 83): null
22 array item: [85, 86): 2 22 array item: [85, 86): 2
23 array item: [88, 93): false 23 array item: [88, 93): false
24 array item: [95, 101): "keep" 24 array item: [95, 101): "keep"
25 array item: [103, 104): 3 25 array item: [103, 104): 3
26 -array start  
27 array item: [106, 0): [] 26 array item: [106, 0): []
  27 +array start
28 array item: [107, 113): "this" 28 array item: [107, 113): "this"
29 array item: [115, 121): "keep" 29 array item: [115, 121): "keep"
30 array item: [123, 128): "not" 30 array item: [123, 128): "not"