-
Notifications
You must be signed in to change notification settings - Fork 6
/
ParseStream.js
279 lines (257 loc) · 8.17 KB
/
ParseStream.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
var Parser = require('parser');
var Tokenizer = require('./JsonTokenizer');
var util = require('util');
var assert = require('assert');
var expandPath = require('./json_path').expand;
/**
* The constuctor for Json parsing streams.
* @constructor
* @extends Parser
*/
function ParseStream (noRoot) {
// call the parent constructor that needs a tokenizer as parameter
Parser.call(this, new Tokenizer());
this._object;
this._noRoot = noRoot;
var self = this;
this.rootObjectPath = '$'
/**
* called when parsing the top level object of the JSON fragment
* sets the internal reference to what is parsed
* @param value the value at top level
* @return JSONPath to the root element
*/
function initialSet (value, dispose) {
if(!noRoot) {
self._object = value;
}
var handled = self.emitValue(self.rootObjectPath, value);
if(!handled && typeof dispose == 'function') {
dispose();
}
return self.rootObjectPath;
}
this.initialHandler(this.value(initialSet));
// TODO set a default handler which is stricter than `ignore`
this.defaultHandler(function(token, type, next) {
if(type !== 'eof') {
throw new SyntaxError("unexpected token "+token+". expected eof");
}
});
}
util.inherits(ParseStream, Parser);
/**
* emits values out of the parser according to their JSONPath
* @param path the path of the value to emit
* @param value value to emit
* @return boolean weather or not the event was handled
*/
ParseStream.prototype.emitValue = function emitValue(path, value) {
var type = null === value ? null : value.constructor.name;
var res = !this._noRoot;
expandPath(path).forEach(function(p) {
var emitted = this.emit(p, value, path);
res = res || emitted;
}.bind(this));
return res;
};
/**
* Factory. Returns a handler able to parse any JSON value
* @param set a function to be called when the value has to be set
* on its parent object or array.
* @return a handler expanding to the correct handlers depending on the
* token we get
*/
ParseStream.prototype.value = function(set) {
return function value(token, type, next) {
switch(type) {
case 'begin-object':
next(this.object(set));
break;
case 'begin-array':
next(this.array(set));
break;
case 'string':
case 'boolean':
case 'number':
case 'null':
next(this.native(set));
break;
default:
throw new SyntaxError("unexpected token "+token);
break;
}
return true;
};
}
/**
* Factory. Returns a handler able to parse any non-composed value
* (string, boolean, number, null)
* @param set the function to set the value on its parent
* @return a handler
*/
ParseStream.prototype.native = function Native(set) {
return function Native(token, type, next) {
switch(type) {
case 'boolean':
if(token[0] === 't') {
set(true);
}
else set(false);
break;
case 'null':
set(null);
break;
case 'number':
var int = (token.indexOf('.') === -1);
int &= (token.indexOf('e') === -1);
set(int ? parseInt(token) : parseFloat(token));
break;
case 'string':
set(JSON.parse(token));
break;
default:
throw new SyntaxError("unexpected token "+token+". expecting native");
}
}
};
/**
* Factory. Returns a handler able to parse an array
* @param set a function to set this array on its parent
* @return a handler expanding to the correct handlers
*/
ParseStream.prototype.array = function array(set) {
var a = [];
var self = this;
var path = set(a, function() { a = null });
var index = 0;
/**
* a function to set a value to the array being parsed
* @param value the value to push to the array
* @return the full JSONPath to this value
*/
function arraySet (value, dispose) {
if(a) {
a.push(value);
}
var newPath = self.makeArrayPath(path, index++);
var handled = self.emitValue(newPath, value);
if(!handled && typeof dispose == 'function') {
dispose();
}
return newPath;
}
return function array (token, type, next) {
next(
Parser.expect('begin-array'),
Parser.list(
'comma', // array
this.value(arraySet), // values
'end-array' // token ending the list
),
Parser.expect('end-array')
);
return true; //expand this
}
};
/**
* Factory. Returns a handler able to parse a javascript object
* @param set the function to set this object on its parent
* @return a handler expanding to the correct handler to parse an object
*/
ParseStream.prototype.object = function object(set) {
var o = {};
var path = set(o, function() { o = null });
var self = this;
/**
* a function to set a value to its label inside the object being parsed
* @param label the label to which to set the value
* @param value the value to set
* @return the full JSONPath of that value
*/
function objectSet (label, value) {
if(o) {
o[label] = value;
}
var newPath = self.makeObjectPath(path, label);
var handled = self.emitValue(newPath, value);
if(!handled && typeof dispose == 'function') {
dispose();
}
return newPath;
}
return function object (token, type, next) {
next(
Parser.expect('begin-object'),
Parser.list(
'comma', // separator
this.labeledValue(objectSet), // values
'end-object' // token ending the list
),
Parser.expect('end-object')
)
return true;
}
};
/**
* Factory. returns a handler able to parse labeled value (as in JS objects)
* @param objectSet the function to set the labeled value on the parent object
* @return a handler expanding to the correct handlers to parse a labeled value
*/
ParseStream.prototype.labeledValue = function labeledValue(objectSet) {
var label;
var self = this;
/**
* this handler reads the label and sets the closured var `label`
*/
function readLabel (token, type, next) {
assert.equal(type, 'string', "unexpected token "+token+". expected string");
label = JSON.parse(token);
}
/**
* this is the function that should be called when the value part has
* to be set
* @param value the value to set
* @return the full JSONPath to this value
*/
function set (value) {
return objectSet(label, value);
}
/**
* the actual handler
*/
return function labeledValue (token, type, next) {
next(
readLabel,
Parser.expect('end-label'),
this.value(set)
);
return true;
}
};
/**
* given a base JSONPath, gives the full JSONPath for the value at this label
* @param path base JSONPath
* @param label the label to get the JSONPath of
* @return full JSONPath
*/
ParseStream.prototype.makeObjectPath = function makeObjectPath(path, label) {
return path + '['+JSON.stringify(label)+']';
};
/**
* given a base JSONPath, gives the full JSONPath for the value at this index
* @param path base JSONPath
* @param index the index to get the JSONPath of
* @return full JSONPath
*/
ParseStream.prototype.makeArrayPath = function makeArrayPath(path, index) {
return path + '['+index+']';
};
ParseStream.prototype._reachedEnd = function _reachedEnd() {
this.emit('end', this._object);
};
ParseStream.prototype.writable = true;
ParseStream.prototype.destroy = function destroy() {
// do not emit anymore
};
module.exports = ParseStream;