1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 | function parseWithLocation(content, filename, locationKeyName) {
|
6 | return _parse(content, filename, locationKeyName);
|
7 | }
|
8 | exports.parseWithLocation = parseWithLocation;
|
9 |
|
10 |
|
11 |
|
12 | function parse(content) {
|
13 | return _parse(content, null, null);
|
14 | }
|
15 | exports.parse = parse;
|
16 | function _parse(content, filename, locationKeyName) {
|
17 | var len = content.length;
|
18 | var pos = 0;
|
19 | var line = 1;
|
20 | var char = 0;
|
21 |
|
22 | if (len > 0 && content.charCodeAt(0) === 65279 ) {
|
23 | pos = 1;
|
24 | }
|
25 | function advancePosBy(by) {
|
26 | if (locationKeyName === null) {
|
27 | pos = pos + by;
|
28 | }
|
29 | else {
|
30 | while (by > 0) {
|
31 | var chCode = content.charCodeAt(pos);
|
32 | if (chCode === 10 ) {
|
33 | pos++;
|
34 | line++;
|
35 | char = 0;
|
36 | }
|
37 | else {
|
38 | pos++;
|
39 | char++;
|
40 | }
|
41 | by--;
|
42 | }
|
43 | }
|
44 | }
|
45 | function advancePosTo(to) {
|
46 | if (locationKeyName === null) {
|
47 | pos = to;
|
48 | }
|
49 | else {
|
50 | advancePosBy(to - pos);
|
51 | }
|
52 | }
|
53 | function skipWhitespace() {
|
54 | while (pos < len) {
|
55 | var chCode = content.charCodeAt(pos);
|
56 | if (chCode !== 32 && chCode !== 9 && chCode !== 13 && chCode !== 10 ) {
|
57 | break;
|
58 | }
|
59 | advancePosBy(1);
|
60 | }
|
61 | }
|
62 | function advanceIfStartsWith(str) {
|
63 | if (content.substr(pos, str.length) === str) {
|
64 | advancePosBy(str.length);
|
65 | return true;
|
66 | }
|
67 | return false;
|
68 | }
|
69 | function advanceUntil(str) {
|
70 | var nextOccurence = content.indexOf(str, pos);
|
71 | if (nextOccurence !== -1) {
|
72 | advancePosTo(nextOccurence + str.length);
|
73 | }
|
74 | else {
|
75 |
|
76 | advancePosTo(len);
|
77 | }
|
78 | }
|
79 | function captureUntil(str) {
|
80 | var nextOccurence = content.indexOf(str, pos);
|
81 | if (nextOccurence !== -1) {
|
82 | var r = content.substring(pos, nextOccurence);
|
83 | advancePosTo(nextOccurence + str.length);
|
84 | return r;
|
85 | }
|
86 | else {
|
87 |
|
88 | var r = content.substr(pos);
|
89 | advancePosTo(len);
|
90 | return r;
|
91 | }
|
92 | }
|
93 | var state = 0 ;
|
94 | var cur = null;
|
95 | var stateStack = [];
|
96 | var objStack = [];
|
97 | var curKey = null;
|
98 | function pushState(newState, newCur) {
|
99 | stateStack.push(state);
|
100 | objStack.push(cur);
|
101 | state = newState;
|
102 | cur = newCur;
|
103 | }
|
104 | function popState() {
|
105 | state = stateStack.pop();
|
106 | cur = objStack.pop();
|
107 | }
|
108 | function fail(msg) {
|
109 | throw new Error('Near offset ' + pos + ': ' + msg + ' ~~~' + content.substr(pos, 50) + '~~~');
|
110 | }
|
111 | var dictState = {
|
112 | enterDict: function () {
|
113 | if (curKey === null) {
|
114 | fail('missing <key>');
|
115 | }
|
116 | var newDict = {};
|
117 | if (locationKeyName !== null) {
|
118 | newDict[locationKeyName] = {
|
119 | filename: filename,
|
120 | line: line,
|
121 | char: char
|
122 | };
|
123 | }
|
124 | cur[curKey] = newDict;
|
125 | curKey = null;
|
126 | pushState(1 , newDict);
|
127 | },
|
128 | enterArray: function () {
|
129 | if (curKey === null) {
|
130 | fail('missing <key>');
|
131 | }
|
132 | var newArr = [];
|
133 | cur[curKey] = newArr;
|
134 | curKey = null;
|
135 | pushState(2 , newArr);
|
136 | }
|
137 | };
|
138 | var arrState = {
|
139 | enterDict: function () {
|
140 | var newDict = {};
|
141 | if (locationKeyName !== null) {
|
142 | newDict[locationKeyName] = {
|
143 | filename: filename,
|
144 | line: line,
|
145 | char: char
|
146 | };
|
147 | }
|
148 | cur.push(newDict);
|
149 | pushState(1 , newDict);
|
150 | },
|
151 | enterArray: function () {
|
152 | var newArr = [];
|
153 | cur.push(newArr);
|
154 | pushState(2 , newArr);
|
155 | }
|
156 | };
|
157 | function enterDict() {
|
158 | if (state === 1 ) {
|
159 | dictState.enterDict();
|
160 | }
|
161 | else if (state === 2 ) {
|
162 | arrState.enterDict();
|
163 | }
|
164 | else {
|
165 | cur = {};
|
166 | if (locationKeyName !== null) {
|
167 | cur[locationKeyName] = {
|
168 | filename: filename,
|
169 | line: line,
|
170 | char: char
|
171 | };
|
172 | }
|
173 | pushState(1 , cur);
|
174 | }
|
175 | }
|
176 | function leaveDict() {
|
177 | if (state === 1 ) {
|
178 | popState();
|
179 | }
|
180 | else if (state === 2 ) {
|
181 | fail('unexpected </dict>');
|
182 | }
|
183 | else {
|
184 | fail('unexpected </dict>');
|
185 | }
|
186 | }
|
187 | function enterArray() {
|
188 | if (state === 1 ) {
|
189 | dictState.enterArray();
|
190 | }
|
191 | else if (state === 2 ) {
|
192 | arrState.enterArray();
|
193 | }
|
194 | else {
|
195 | cur = [];
|
196 | pushState(2 , cur);
|
197 | }
|
198 | }
|
199 | function leaveArray() {
|
200 | if (state === 1 ) {
|
201 | fail('unexpected </array>');
|
202 | }
|
203 | else if (state === 2 ) {
|
204 | popState();
|
205 | }
|
206 | else {
|
207 | fail('unexpected </array>');
|
208 | }
|
209 | }
|
210 | function acceptKey(val) {
|
211 | if (state === 1 ) {
|
212 | if (curKey !== null) {
|
213 | fail('too many <key>');
|
214 | }
|
215 | curKey = val;
|
216 | }
|
217 | else if (state === 2 ) {
|
218 | fail('unexpected <key>');
|
219 | }
|
220 | else {
|
221 | fail('unexpected <key>');
|
222 | }
|
223 | }
|
224 | function acceptString(val) {
|
225 | if (state === 1 ) {
|
226 | if (curKey === null) {
|
227 | fail('missing <key>');
|
228 | }
|
229 | cur[curKey] = val;
|
230 | curKey = null;
|
231 | }
|
232 | else if (state === 2 ) {
|
233 | cur.push(val);
|
234 | }
|
235 | else {
|
236 | cur = val;
|
237 | }
|
238 | }
|
239 | function acceptReal(val) {
|
240 | if (isNaN(val)) {
|
241 | fail('cannot parse float');
|
242 | }
|
243 | if (state === 1 ) {
|
244 | if (curKey === null) {
|
245 | fail('missing <key>');
|
246 | }
|
247 | cur[curKey] = val;
|
248 | curKey = null;
|
249 | }
|
250 | else if (state === 2 ) {
|
251 | cur.push(val);
|
252 | }
|
253 | else {
|
254 | cur = val;
|
255 | }
|
256 | }
|
257 | function acceptInteger(val) {
|
258 | if (isNaN(val)) {
|
259 | fail('cannot parse integer');
|
260 | }
|
261 | if (state === 1 ) {
|
262 | if (curKey === null) {
|
263 | fail('missing <key>');
|
264 | }
|
265 | cur[curKey] = val;
|
266 | curKey = null;
|
267 | }
|
268 | else if (state === 2 ) {
|
269 | cur.push(val);
|
270 | }
|
271 | else {
|
272 | cur = val;
|
273 | }
|
274 | }
|
275 | function acceptDate(val) {
|
276 | if (state === 1 ) {
|
277 | if (curKey === null) {
|
278 | fail('missing <key>');
|
279 | }
|
280 | cur[curKey] = val;
|
281 | curKey = null;
|
282 | }
|
283 | else if (state === 2 ) {
|
284 | cur.push(val);
|
285 | }
|
286 | else {
|
287 | cur = val;
|
288 | }
|
289 | }
|
290 | function acceptData(val) {
|
291 | if (state === 1 ) {
|
292 | if (curKey === null) {
|
293 | fail('missing <key>');
|
294 | }
|
295 | cur[curKey] = val;
|
296 | curKey = null;
|
297 | }
|
298 | else if (state === 2 ) {
|
299 | cur.push(val);
|
300 | }
|
301 | else {
|
302 | cur = val;
|
303 | }
|
304 | }
|
305 | function acceptBool(val) {
|
306 | if (state === 1 ) {
|
307 | if (curKey === null) {
|
308 | fail('missing <key>');
|
309 | }
|
310 | cur[curKey] = val;
|
311 | curKey = null;
|
312 | }
|
313 | else if (state === 2 ) {
|
314 | cur.push(val);
|
315 | }
|
316 | else {
|
317 | cur = val;
|
318 | }
|
319 | }
|
320 | function escapeVal(str) {
|
321 | return str.replace(/&#([0-9]+);/g, function (_, m0) {
|
322 | return String.fromCodePoint(parseInt(m0, 10));
|
323 | }).replace(/&#x([0-9a-f]+);/g, function (_, m0) {
|
324 | return String.fromCodePoint(parseInt(m0, 16));
|
325 | }).replace(/&|<|>|"|'/g, function (_) {
|
326 | switch (_) {
|
327 | case '&': return '&';
|
328 | case '<': return '<';
|
329 | case '>': return '>';
|
330 | case '"': return '"';
|
331 | case ''': return '\'';
|
332 | }
|
333 | return _;
|
334 | });
|
335 | }
|
336 | function parseOpenTag() {
|
337 | var r = captureUntil('>');
|
338 | var isClosed = false;
|
339 | if (r.charCodeAt(r.length - 1) === 47 ) {
|
340 | isClosed = true;
|
341 | r = r.substring(0, r.length - 1);
|
342 | }
|
343 | return {
|
344 | name: r.trim(),
|
345 | isClosed: isClosed
|
346 | };
|
347 | }
|
348 | function parseTagValue(tag) {
|
349 | if (tag.isClosed) {
|
350 | return '';
|
351 | }
|
352 | var val = captureUntil('</');
|
353 | advanceUntil('>');
|
354 | return escapeVal(val);
|
355 | }
|
356 | while (pos < len) {
|
357 | skipWhitespace();
|
358 | if (pos >= len) {
|
359 | break;
|
360 | }
|
361 | var chCode = content.charCodeAt(pos);
|
362 | advancePosBy(1);
|
363 | if (chCode !== 60 ) {
|
364 | fail('expected <');
|
365 | }
|
366 | if (pos >= len) {
|
367 | fail('unexpected end of input');
|
368 | }
|
369 | var peekChCode = content.charCodeAt(pos);
|
370 | if (peekChCode === 63 ) {
|
371 | advancePosBy(1);
|
372 | advanceUntil('?>');
|
373 | continue;
|
374 | }
|
375 | if (peekChCode === 33 ) {
|
376 | advancePosBy(1);
|
377 | if (advanceIfStartsWith('--')) {
|
378 | advanceUntil('-->');
|
379 | continue;
|
380 | }
|
381 | advanceUntil('>');
|
382 | continue;
|
383 | }
|
384 | if (peekChCode === 47 ) {
|
385 | advancePosBy(1);
|
386 | skipWhitespace();
|
387 | if (advanceIfStartsWith('plist')) {
|
388 | advanceUntil('>');
|
389 | continue;
|
390 | }
|
391 | if (advanceIfStartsWith('dict')) {
|
392 | advanceUntil('>');
|
393 | leaveDict();
|
394 | continue;
|
395 | }
|
396 | if (advanceIfStartsWith('array')) {
|
397 | advanceUntil('>');
|
398 | leaveArray();
|
399 | continue;
|
400 | }
|
401 | fail('unexpected closed tag');
|
402 | }
|
403 | var tag = parseOpenTag();
|
404 | switch (tag.name) {
|
405 | case 'dict':
|
406 | enterDict();
|
407 | if (tag.isClosed) {
|
408 | leaveDict();
|
409 | }
|
410 | continue;
|
411 | case 'array':
|
412 | enterArray();
|
413 | if (tag.isClosed) {
|
414 | leaveArray();
|
415 | }
|
416 | continue;
|
417 | case 'key':
|
418 | acceptKey(parseTagValue(tag));
|
419 | continue;
|
420 | case 'string':
|
421 | acceptString(parseTagValue(tag));
|
422 | continue;
|
423 | case 'real':
|
424 | acceptReal(parseFloat(parseTagValue(tag)));
|
425 | continue;
|
426 | case 'integer':
|
427 | acceptInteger(parseInt(parseTagValue(tag), 10));
|
428 | continue;
|
429 | case 'date':
|
430 | acceptDate(new Date(parseTagValue(tag)));
|
431 | continue;
|
432 | case 'data':
|
433 | acceptData(parseTagValue(tag));
|
434 | continue;
|
435 | case 'true':
|
436 | parseTagValue(tag);
|
437 | acceptBool(true);
|
438 | continue;
|
439 | case 'false':
|
440 | parseTagValue(tag);
|
441 | acceptBool(false);
|
442 | continue;
|
443 | }
|
444 | if (/^plist/.test(tag.name)) {
|
445 | continue;
|
446 | }
|
447 | fail('unexpected opened tag ' + tag.name);
|
448 | }
|
449 | return cur;
|
450 | }
|
451 |
|
\ | No newline at end of file |