Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/streams/jsonstreamparser.js
1865 views
1
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview the default JSON stream parser.
17
*
18
* The default JSON parser decodes the input stream (string) under the
19
* following rules:
20
* 1. The stream represents a valid JSON array (must start with a "[" and close
21
* with the corresponding "]"). Each element of this array is assumed to be
22
* either an array or an object, and will be decoded as a JS object and
23
* delivered. Compact array format that is not valid JSON is also supported,
24
* e.g. [1,,2].
25
* 2. All JSON elements in the buffer will be decoded and delivered in a batch.
26
* 3. If a high-level API does not support batch delivery (e.g. grpc), then
27
* a wrapper is expected to deliver individual elements separately
28
* and in order.
29
* 4. The parser is expected to drop any data (without breaking the
30
* specified MIME format) that is not visible to the client: e.g. new lines
31
* for pretty printing; no-op data for keep-alive support.
32
* 5. Fail-fast: any invalid content should abort the stream by setting the
33
* state of the parser to "invalid".
34
*
35
* The parser is a streamed JSON parser and is optimized in such a way
36
* that it only scans the message boundary and the actual decoding of JSON
37
* strings and construction of JS object are done by JSON.parse (native
38
* code).
39
*/
40
41
goog.provide('goog.net.streams.JsonStreamParser');
42
goog.provide('goog.net.streams.JsonStreamParser.Options');
43
44
goog.require('goog.asserts');
45
goog.require('goog.json');
46
goog.require('goog.net.streams.StreamParser');
47
goog.require('goog.net.streams.utils');
48
49
50
goog.scope(function() {
51
52
53
var utils = goog.module.get('goog.net.streams.utils');
54
55
56
/**
57
* The default JSON stream parser.
58
*
59
* @param {!goog.net.streams.JsonStreamParser.Options=} opt_options
60
* Configuration for the new JsonStreamParser instance.
61
* @constructor
62
* @struct
63
* @implements {goog.net.streams.StreamParser}
64
* @final
65
* @package
66
*/
67
goog.net.streams.JsonStreamParser = function(opt_options) {
68
/**
69
* The current error message, if any.
70
* @private {?string}
71
*/
72
this.errorMessage_ = null;
73
74
/**
75
* The currently buffered result (parsed JSON objects).
76
* @private {!Array<string|!Object>}
77
*/
78
this.result_ = [];
79
80
/**
81
* The currently buffered input.
82
* @private {string}
83
*/
84
this.buffer_ = '';
85
86
/**
87
* The current stack.
88
* @private {!Array<!Parser.State_>}
89
*/
90
this.stack_ = [];
91
92
/**
93
* The current depth of the nested JSON structure.
94
* @private {number}
95
*/
96
this.depth_ = 0;
97
98
/**
99
* The current position in the streamed data.
100
* @private {number}
101
*/
102
this.pos_ = 0;
103
104
/**
105
* The current state of whether the parser is decoding a '\' escaped string.
106
* @private {boolean}
107
*/
108
this.slashed_ = false;
109
110
/**
111
* The current unicode char count. 0 means no unicode, 1-4 otherwise.
112
* @private {number}
113
*/
114
this.unicodeCount_ = 0;
115
116
/**
117
* The regexp for parsing string input.
118
* @private {!RegExp}
119
*/
120
this.stringInputPattern_ = /[\\"]/g;
121
122
/**
123
* The current stream state.
124
* @private {goog.net.streams.JsonStreamParser.StreamState_}
125
*/
126
this.streamState_ = Parser.StreamState_.INIT;
127
128
/**
129
* The current parser state.
130
* @private {goog.net.streams.JsonStreamParser.State_}
131
*/
132
this.state_ = Parser.State_.INIT;
133
134
/**
135
* Whether allows compact JSON array format, e.g. "[1, ,2]".
136
* @private {boolean}
137
*/
138
this.allowCompactJsonArrayFormat_ =
139
!!(opt_options && opt_options.allowCompactJsonArrayFormat);
140
141
/**
142
* Whether to deliver the raw message string without decoding into JS object.
143
* @private {boolean}
144
*/
145
this.deliverMessageAsRawString_ =
146
!!(opt_options && opt_options.deliverMessageAsRawString);
147
};
148
149
150
/**
151
* Configuration spec for newly created JSON stream parser:
152
*
153
* allowCompactJsonArrayFormat: whether allows compact JSON array format, where
154
* null is represented as empty string, e.g. "[1, ,2]".
155
*
156
* deliverMessageAsRawString: whether to deliver the raw message string without
157
* decoding into JS object. Semantically insignificant whitespaces in the
158
* input may be kept or ignored.
159
*
160
* @typedef {{
161
* allowCompactJsonArrayFormat: (boolean|undefined),
162
* deliverMessageAsRawString: (boolean|undefined),
163
* }}
164
*/
165
goog.net.streams.JsonStreamParser.Options;
166
167
168
var Parser = goog.net.streams.JsonStreamParser;
169
170
171
/**
172
* The stream state.
173
* @private @enum {number}
174
*/
175
Parser.StreamState_ = {
176
INIT: 0,
177
ARRAY_OPEN: 1,
178
ARRAY_END: 2,
179
INVALID: 3
180
};
181
182
183
/**
184
* The parser state.
185
* @private @enum {number}
186
*/
187
Parser.State_ = {
188
INIT: 0,
189
VALUE: 1,
190
OBJECT_OPEN: 2,
191
OBJECT_END: 3,
192
ARRAY_OPEN: 4,
193
ARRAY_END: 5,
194
STRING: 6,
195
KEY_START: 7,
196
KEY_END: 8,
197
TRUE1: 9, // T and expecting RUE ...
198
TRUE2: 10,
199
TRUE3: 11,
200
FALSE1: 12, // F and expecting ALSE ...
201
FALSE2: 13,
202
FALSE3: 14,
203
FALSE4: 15,
204
NULL1: 16, // N and expecting ULL ...
205
NULL2: 17,
206
NULL3: 18,
207
NUM_DECIMAL_POINT: 19,
208
NUM_DIGIT: 20
209
};
210
211
212
/**
213
* @override
214
*/
215
Parser.prototype.isInputValid = function() {
216
return this.streamState_ != Parser.StreamState_.INVALID;
217
};
218
219
220
/**
221
* @override
222
*/
223
Parser.prototype.getErrorMessage = function() {
224
return this.errorMessage_;
225
};
226
227
228
/**
229
* @return {boolean} Whether the parser has reached the end of the stream
230
*
231
* TODO(updogliu): move this API to the base type.
232
*/
233
Parser.prototype.done = function() {
234
return this.streamState_ === Parser.StreamState_.ARRAY_END;
235
};
236
237
238
/**
239
* Get the part of input that is after the end of the stream. Call this only
240
* when {@code this.done()} is true.
241
*
242
* @return {string} The extra input
243
*
244
* TODO(updogliu): move this API to the base type.
245
*/
246
Parser.prototype.getExtraInput = function() {
247
return this.buffer_;
248
};
249
250
251
/**
252
* @param {string|!ArrayBuffer|!Array<number>} input
253
* The current input string (always)
254
* @param {number} pos The position in the current input that triggers the error
255
* @throws {!Error} Throws an error indicating where the stream is broken
256
* @private
257
*/
258
Parser.prototype.error_ = function(input, pos) {
259
this.streamState_ = Parser.StreamState_.INVALID;
260
this.errorMessage_ = 'The stream is broken @' + this.pos_ + '/' + pos +
261
'. With input:\n' + input;
262
throw Error(this.errorMessage_);
263
};
264
265
266
/**
267
* @throws {Error} Throws an error message if the input is invalid.
268
* @override
269
*/
270
Parser.prototype.parse = function(input) {
271
goog.asserts.assertString(input);
272
273
// captures
274
var parser = this;
275
var stack = parser.stack_;
276
var pattern = parser.stringInputPattern_;
277
var State = Parser.State_; // enums
278
279
var num = input.length;
280
281
var streamStart = 0;
282
283
var msgStart = -1;
284
285
var i = 0;
286
287
while (i < num) {
288
switch (parser.streamState_) {
289
case Parser.StreamState_.INVALID:
290
parser.error_(input, i);
291
return null;
292
293
case Parser.StreamState_.ARRAY_END:
294
if (readMore()) {
295
parser.error_(input, i);
296
}
297
return null;
298
299
case Parser.StreamState_.INIT:
300
if (readMore()) {
301
var current = input[i++];
302
parser.pos_++;
303
304
if (current === '[') {
305
parser.streamState_ = Parser.StreamState_.ARRAY_OPEN;
306
307
streamStart = i;
308
parser.state_ = State.ARRAY_OPEN;
309
310
continue;
311
} else {
312
parser.error_(input, i);
313
}
314
}
315
return null;
316
317
case Parser.StreamState_.ARRAY_OPEN:
318
parseData();
319
320
if (parser.depth_ === 0 && parser.state_ == State.ARRAY_END) {
321
parser.streamState_ = Parser.StreamState_.ARRAY_END;
322
parser.buffer_ = input.substring(i);
323
} else {
324
if (msgStart === -1) {
325
parser.buffer_ += input.substring(streamStart);
326
} else {
327
parser.buffer_ = input.substring(msgStart);
328
}
329
}
330
331
if (parser.result_.length > 0) {
332
var msgs = parser.result_;
333
parser.result_ = [];
334
return msgs;
335
}
336
return null;
337
}
338
}
339
340
return null;
341
342
/**
343
* @return {boolean} true if the parser needs parse more data
344
*/
345
function readMore() {
346
skipWhitespace();
347
return i < num;
348
}
349
350
/**
351
* Skip as many whitespaces as possible, and increments current index of
352
* stream to next available char.
353
*/
354
function skipWhitespace() {
355
while (i < input.length) {
356
if (utils.isJsonWhitespace(input[i])) {
357
i++;
358
parser.pos_++;
359
continue;
360
}
361
break;
362
}
363
}
364
365
/**
366
* Parse the input JSON elements with a streamed state machine.
367
*/
368
function parseData() {
369
var current;
370
371
while (true) {
372
current = input[i++];
373
if (!current) {
374
break;
375
}
376
377
parser.pos_++;
378
379
switch (parser.state_) {
380
case State.INIT:
381
if (current === '{') {
382
parser.state_ = State.OBJECT_OPEN;
383
} else if (current === '[') {
384
parser.state_ = State.ARRAY_OPEN;
385
} else if (!utils.isJsonWhitespace(current)) {
386
parser.error_(input, i);
387
}
388
continue;
389
390
case State.KEY_START:
391
case State.OBJECT_OPEN:
392
if (utils.isJsonWhitespace(current)) {
393
continue;
394
}
395
if (parser.state_ === State.KEY_START) {
396
stack.push(State.KEY_END);
397
} else {
398
if (current === '}') {
399
addMessage('{}');
400
parser.state_ = nextState();
401
continue;
402
} else {
403
stack.push(State.OBJECT_END);
404
}
405
}
406
if (current === '"') {
407
parser.state_ = State.STRING;
408
} else {
409
parser.error_(input, i);
410
}
411
continue;
412
413
414
case State.KEY_END:
415
case State.OBJECT_END:
416
if (utils.isJsonWhitespace(current)) {
417
continue;
418
}
419
if (current === ':') {
420
if (parser.state_ === State.OBJECT_END) {
421
stack.push(State.OBJECT_END);
422
parser.depth_++;
423
}
424
parser.state_ = State.VALUE;
425
} else if (current === '}') {
426
parser.depth_--;
427
addMessage();
428
parser.state_ = nextState();
429
} else if (current === ',') {
430
if (parser.state_ === State.OBJECT_END) {
431
stack.push(State.OBJECT_END);
432
}
433
parser.state_ = State.KEY_START;
434
} else {
435
parser.error_(input, i);
436
}
437
continue;
438
439
case State.ARRAY_OPEN:
440
case State.VALUE:
441
if (utils.isJsonWhitespace(current)) {
442
continue;
443
}
444
if (parser.state_ === State.ARRAY_OPEN) {
445
parser.depth_++;
446
parser.state_ = State.VALUE;
447
if (current === ']') {
448
parser.depth_--;
449
if (parser.depth_ === 0) {
450
parser.state_ = State.ARRAY_END;
451
return;
452
}
453
454
addMessage('[]');
455
456
parser.state_ = nextState();
457
continue;
458
} else {
459
stack.push(State.ARRAY_END);
460
}
461
}
462
if (current === '"')
463
parser.state_ = State.STRING;
464
else if (current === '{')
465
parser.state_ = State.OBJECT_OPEN;
466
else if (current === '[')
467
parser.state_ = State.ARRAY_OPEN;
468
else if (current === 't')
469
parser.state_ = State.TRUE1;
470
else if (current === 'f')
471
parser.state_ = State.FALSE1;
472
else if (current === 'n')
473
parser.state_ = State.NULL1;
474
else if (current === '-') {
475
// continue
476
} else if ('0123456789'.indexOf(current) !== -1) {
477
parser.state_ = State.NUM_DIGIT;
478
} else if (current === ',' && parser.allowCompactJsonArrayFormat_) {
479
parser.state_ = State.VALUE;
480
} else if (current === ']' && parser.allowCompactJsonArrayFormat_) {
481
i--;
482
parser.pos_--;
483
parser.state_ = nextState();
484
} else {
485
parser.error_(input, i);
486
}
487
continue;
488
489
case State.ARRAY_END:
490
if (current === ',') {
491
stack.push(State.ARRAY_END);
492
parser.state_ = State.VALUE;
493
494
if (parser.depth_ === 1) {
495
msgStart = i; // skip ',', including a leading one
496
}
497
} else if (current === ']') {
498
parser.depth_--;
499
if (parser.depth_ === 0) {
500
return;
501
}
502
503
addMessage();
504
parser.state_ = nextState();
505
} else if (utils.isJsonWhitespace(current)) {
506
continue;
507
} else {
508
parser.error_(input, i);
509
}
510
continue;
511
512
case State.STRING:
513
var old = i;
514
515
STRING_LOOP: while (true) {
516
while (parser.unicodeCount_ > 0) {
517
current = input[i++];
518
if (parser.unicodeCount_ === 4) {
519
parser.unicodeCount_ = 0;
520
} else {
521
parser.unicodeCount_++;
522
}
523
if (!current) {
524
break STRING_LOOP;
525
}
526
}
527
528
if (current === '"' && !parser.slashed_) {
529
parser.state_ = nextState();
530
break;
531
}
532
if (current === '\\' && !parser.slashed_) {
533
parser.slashed_ = true;
534
current = input[i++];
535
if (!current) {
536
break;
537
}
538
}
539
if (parser.slashed_) {
540
parser.slashed_ = false;
541
if (current === 'u') {
542
parser.unicodeCount_ = 1;
543
}
544
current = input[i++];
545
if (!current) {
546
break;
547
} else {
548
continue;
549
}
550
}
551
552
pattern.lastIndex = i;
553
var patternResult = pattern.exec(input);
554
if (!patternResult) {
555
i = input.length + 1;
556
break;
557
}
558
i = patternResult.index + 1;
559
current = input[patternResult.index];
560
if (!current) {
561
break;
562
}
563
}
564
565
parser.pos_ += (i - old);
566
567
continue;
568
569
case State.TRUE1:
570
if (!current) {
571
continue;
572
}
573
if (current === 'r') {
574
parser.state_ = State.TRUE2;
575
} else {
576
parser.error_(input, i);
577
}
578
continue;
579
580
case State.TRUE2:
581
if (!current) {
582
continue;
583
}
584
if (current === 'u') {
585
parser.state_ = State.TRUE3;
586
} else {
587
parser.error_(input, i);
588
}
589
continue;
590
591
case State.TRUE3:
592
if (!current) {
593
continue;
594
}
595
if (current === 'e') {
596
parser.state_ = nextState();
597
} else {
598
parser.error_(input, i);
599
}
600
continue;
601
602
case State.FALSE1:
603
if (!current) {
604
continue;
605
}
606
if (current === 'a') {
607
parser.state_ = State.FALSE2;
608
} else {
609
parser.error_(input, i);
610
}
611
continue;
612
613
case State.FALSE2:
614
if (!current) {
615
continue;
616
}
617
if (current === 'l') {
618
parser.state_ = State.FALSE3;
619
} else {
620
parser.error_(input, i);
621
}
622
continue;
623
624
case State.FALSE3:
625
if (!current) {
626
continue;
627
}
628
if (current === 's') {
629
parser.state_ = State.FALSE4;
630
} else {
631
parser.error_(input, i);
632
}
633
continue;
634
635
case State.FALSE4:
636
if (!current) {
637
continue;
638
}
639
if (current === 'e') {
640
parser.state_ = nextState();
641
} else {
642
parser.error_(input, i);
643
}
644
continue;
645
646
case State.NULL1:
647
if (!current) {
648
continue;
649
}
650
if (current === 'u') {
651
parser.state_ = State.NULL2;
652
} else {
653
parser.error_(input, i);
654
}
655
continue;
656
657
case State.NULL2:
658
if (!current) {
659
continue;
660
}
661
if (current === 'l') {
662
parser.state_ = State.NULL3;
663
} else {
664
parser.error_(input, i);
665
}
666
continue;
667
668
case State.NULL3:
669
if (!current) {
670
continue;
671
}
672
if (current === 'l') {
673
parser.state_ = nextState();
674
} else {
675
parser.error_(input, i);
676
}
677
continue;
678
679
case State.NUM_DECIMAL_POINT:
680
if (current === '.') {
681
parser.state_ = State.NUM_DIGIT;
682
} else {
683
parser.error_(input, i);
684
}
685
continue;
686
687
case State.NUM_DIGIT: // no need for a full validation here
688
if ('0123456789.eE+-'.indexOf(current) !== -1) {
689
continue;
690
} else {
691
i--;
692
parser.pos_--;
693
parser.state_ = nextState();
694
}
695
continue;
696
697
default:
698
parser.error_(input, i);
699
}
700
}
701
}
702
703
/**
704
* @return {!goog.net.streams.JsonStreamParser.State_} the next state
705
* from the stack, or the general VALUE state.
706
*/
707
function nextState() {
708
var state = stack.pop();
709
if (state != null) {
710
return state;
711
} else {
712
return State.VALUE;
713
}
714
}
715
716
/**
717
* @param {(string)=} opt_data The message to add
718
*/
719
function addMessage(opt_data) {
720
if (parser.depth_ > 1) {
721
return;
722
}
723
724
goog.asserts.assert(opt_data !== ''); // '' not possible
725
726
if (!opt_data) {
727
if (msgStart === -1) {
728
opt_data = parser.buffer_ + input.substring(streamStart, i);
729
} else {
730
opt_data = input.substring(msgStart, i);
731
}
732
}
733
734
if (parser.deliverMessageAsRawString_) {
735
parser.result_.push(opt_data);
736
} else {
737
parser.result_.push(
738
goog.asserts.assertInstanceof(goog.json.parse(opt_data), Object));
739
}
740
msgStart = i;
741
}
742
};
743
744
}); // goog.scope
745
746