@lango-project/lango-js 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,983 @@
1
+ 'use strict';
2
+
3
+ var conventions = require('./conventions');
4
+ var g = require('./grammar');
5
+ var errors = require('./errors');
6
+
7
+ var isHTMLEscapableRawTextElement = conventions.isHTMLEscapableRawTextElement;
8
+ var isHTMLMimeType = conventions.isHTMLMimeType;
9
+ var isHTMLRawTextElement = conventions.isHTMLRawTextElement;
10
+ var hasOwn = conventions.hasOwn;
11
+ var NAMESPACE = conventions.NAMESPACE;
12
+ var ParseError = errors.ParseError;
13
+ var DOMException = errors.DOMException;
14
+
15
+ //var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
16
+
17
+ //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
18
+ //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
19
+ var S_TAG = 0; //tag name offerring
20
+ var S_ATTR = 1; //attr name offerring
21
+ var S_ATTR_SPACE = 2; //attr name end and space offer
22
+ var S_EQ = 3; //=space?
23
+ var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
24
+ var S_ATTR_END = 5; //attr value end and no space(quot end)
25
+ var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
26
+ var S_TAG_CLOSE = 7; //closed el<el />
27
+
28
+ function XMLReader() {}
29
+
30
+ XMLReader.prototype = {
31
+ parse: function (source, defaultNSMap, entityMap) {
32
+ var domBuilder = this.domBuilder;
33
+ domBuilder.startDocument();
34
+ _copy(defaultNSMap, (defaultNSMap = Object.create(null)));
35
+ parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
36
+ domBuilder.endDocument();
37
+ },
38
+ };
39
+
40
+ /**
41
+ * Detecting everything that might be a reference,
42
+ * including those without ending `;`, since those are allowed in HTML.
43
+ * The entityReplacer takes care of verifying and transforming each occurrence,
44
+ * and reports to the errorHandler on those that are not OK,
45
+ * depending on the context.
46
+ */
47
+ var ENTITY_REG = /&#?\w+;?/g;
48
+
49
+ function parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
50
+ var isHTML = isHTMLMimeType(domBuilder.mimeType);
51
+ if (source.indexOf(g.UNICODE_REPLACEMENT_CHARACTER) >= 0) {
52
+ errorHandler.warning('Unicode replacement character detected, source encoding issues?');
53
+ }
54
+
55
+ function fixedFromCharCode(code) {
56
+ // String.prototype.fromCharCode does not supports
57
+ // > 2 bytes unicode chars directly
58
+ if (code > 0xffff) {
59
+ code -= 0x10000;
60
+ var surrogate1 = 0xd800 + (code >> 10),
61
+ surrogate2 = 0xdc00 + (code & 0x3ff);
62
+
63
+ return String.fromCharCode(surrogate1, surrogate2);
64
+ } else {
65
+ return String.fromCharCode(code);
66
+ }
67
+ }
68
+
69
+ function entityReplacer(a) {
70
+ var complete = a[a.length - 1] === ';' ? a : a + ';';
71
+ if (!isHTML && complete !== a) {
72
+ errorHandler.error('EntityRef: expecting ;');
73
+ return a;
74
+ }
75
+ var match = g.Reference.exec(complete);
76
+ if (!match || match[0].length !== complete.length) {
77
+ errorHandler.error('entity not matching Reference production: ' + a);
78
+ return a;
79
+ }
80
+ var k = complete.slice(1, -1);
81
+ if (hasOwn(entityMap, k)) {
82
+ return entityMap[k];
83
+ } else if (k.charAt(0) === '#') {
84
+ return fixedFromCharCode(parseInt(k.substring(1).replace('x', '0x')));
85
+ } else {
86
+ errorHandler.error('entity not found:' + a);
87
+ return a;
88
+ }
89
+ }
90
+
91
+ function appendText(end) {
92
+ //has some bugs
93
+ if (end > start) {
94
+ var xt = source.substring(start, end).replace(ENTITY_REG, entityReplacer);
95
+ locator && position(start);
96
+ domBuilder.characters(xt, 0, end - start);
97
+ start = end;
98
+ }
99
+ }
100
+
101
+ var lineStart = 0;
102
+ var lineEnd = 0;
103
+ var linePattern = /\r\n?|\n|$/g;
104
+ var locator = domBuilder.locator;
105
+
106
+ function position(p, m) {
107
+ while (p >= lineEnd && (m = linePattern.exec(source))) {
108
+ lineStart = lineEnd;
109
+ lineEnd = m.index + m[0].length;
110
+ locator.lineNumber++;
111
+ }
112
+ locator.columnNumber = p - lineStart + 1;
113
+ }
114
+
115
+ var parseStack = [{ currentNSMap: defaultNSMapCopy }];
116
+ var unclosedTags = [];
117
+ var start = 0;
118
+ while (true) {
119
+ try {
120
+ var tagStart = source.indexOf('<', start);
121
+ if (tagStart < 0) {
122
+ if (!isHTML && unclosedTags.length > 0) {
123
+ return errorHandler.fatalError('unclosed xml tag(s): ' + unclosedTags.join(', '));
124
+ }
125
+ if (!source.substring(start).match(/^\s*$/)) {
126
+ var doc = domBuilder.doc;
127
+ var text = doc.createTextNode(source.substring(start));
128
+ if (doc.documentElement) {
129
+ // `return errorHandler.error` is not a common pattern,
130
+ // it is usually only used with `.fatalError`s.
131
+ // In this case it is intentional, because it allows to stop parsing
132
+ // and returning doc after reporting the extra content that will not be part of the document.
133
+ return errorHandler.error('Extra content at the end of the document');
134
+ }
135
+ doc.appendChild(text);
136
+ domBuilder.currentElement = text;
137
+ }
138
+ return;
139
+ }
140
+ if (tagStart > start) {
141
+ var fromSource = source.substring(start, tagStart);
142
+ if (!isHTML && unclosedTags.length === 0) {
143
+ fromSource = fromSource.replace(new RegExp(g.S_OPT.source, 'g'), '');
144
+ fromSource && errorHandler.error("Unexpected content outside root element: '" + fromSource + "'");
145
+ }
146
+ appendText(tagStart);
147
+ }
148
+ switch (source.charAt(tagStart + 1)) {
149
+ case '/':
150
+ var end = source.indexOf('>', tagStart + 2);
151
+ var tagNameRaw = source.substring(tagStart + 2, end > 0 ? end : undefined);
152
+ if (!tagNameRaw) {
153
+ return errorHandler.fatalError('end tag name missing');
154
+ }
155
+ var endTagNameStrict = g.reg('^', g.QName_group, g.S_OPT, '$');
156
+ var tagNameMatch = end > 0 && endTagNameStrict.exec(tagNameRaw);
157
+ if (!tagNameMatch) {
158
+ var leadingTagNameMatch = end > 0 && g.reg('^', g.QName_group).exec(tagNameRaw);
159
+ if (isHTML && leadingTagNameMatch) {
160
+ errorHandler.warning('end tag name contains invalid trailing characters: "' + tagNameRaw + '"');
161
+ tagNameMatch = leadingTagNameMatch;
162
+ } else if (
163
+ // Backward compatibility, remove this whole `else if` arm in the next breaking release
164
+ // (XML then falls through to the `fatalError` below, for a clean mode split: XML fatal,
165
+ // HTML warning). A valid end-tag name followed by a line break and trailing content was
166
+ // silently accepted while `reg` still used the `m` flag; re-adding `m` here matches exactly
167
+ // those inputs, kept recoverable and reported.
168
+ leadingTagNameMatch &&
169
+ new RegExp(endTagNameStrict.source, endTagNameStrict.flags + 'm').test(tagNameRaw)
170
+ ) {
171
+ errorHandler.error('end tag name is followed by a line break and trailing content: "' + tagNameRaw + '"');
172
+ tagNameMatch = leadingTagNameMatch;
173
+ } else {
174
+ return errorHandler.fatalError('end tag name contains invalid characters: "' + tagNameRaw + '"');
175
+ }
176
+ }
177
+ if (!domBuilder.currentElement && !domBuilder.doc.documentElement) {
178
+ // not enough information to provide a helpful error message,
179
+ // but parsing will throw since there is no root element
180
+ return;
181
+ }
182
+ var currentTagName =
183
+ unclosedTags[unclosedTags.length - 1] ||
184
+ domBuilder.currentElement.tagName ||
185
+ domBuilder.doc.documentElement.tagName ||
186
+ '';
187
+ if (currentTagName !== tagNameMatch[1]) {
188
+ var tagNameLower = tagNameMatch[1].toLowerCase();
189
+ if (!isHTML || currentTagName.toLowerCase() !== tagNameLower) {
190
+ return errorHandler.fatalError('Opening and ending tag mismatch: "' + currentTagName + '" != "' + tagNameRaw + '"');
191
+ }
192
+ }
193
+ var config = parseStack.pop();
194
+ unclosedTags.pop();
195
+ var localNSMap = config.localNSMap;
196
+ domBuilder.endElement(config.uri, config.localName, currentTagName);
197
+ if (localNSMap) {
198
+ for (var prefix in localNSMap) {
199
+ if (hasOwn(localNSMap, prefix)) {
200
+ domBuilder.endPrefixMapping(prefix);
201
+ }
202
+ }
203
+ }
204
+
205
+ end++;
206
+ break;
207
+ // end element
208
+ case '?': // <?...?>
209
+ locator && position(tagStart);
210
+ end = parseProcessingInstruction(source, tagStart, domBuilder, errorHandler);
211
+ break;
212
+ case '!': // <!doctype,<![CDATA,<!--
213
+ locator && position(tagStart);
214
+ end = parseDoctypeCommentOrCData(source, tagStart, domBuilder, errorHandler, isHTML);
215
+ break;
216
+ default:
217
+ locator && position(tagStart);
218
+ var el = new ElementAttributes();
219
+ var currentNSMap = parseStack[parseStack.length - 1].currentNSMap;
220
+ //elStartEnd
221
+ var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler, isHTML);
222
+ var len = el.length;
223
+
224
+ if (!el.closed) {
225
+ if (isHTML && conventions.isHTMLVoidElement(el.tagName)) {
226
+ el.closed = true;
227
+ } else {
228
+ unclosedTags.push(el.tagName);
229
+ }
230
+ }
231
+ if (locator && len) {
232
+ var locator2 = copyLocator(locator, {});
233
+ //try{//attribute position fixed
234
+ for (var i = 0; i < len; i++) {
235
+ var a = el[i];
236
+ position(a.offset);
237
+ a.locator = copyLocator(locator, {});
238
+ }
239
+ domBuilder.locator = locator2;
240
+ if (appendElement(el, domBuilder, currentNSMap)) {
241
+ parseStack.push(el);
242
+ }
243
+ domBuilder.locator = locator;
244
+ } else {
245
+ if (appendElement(el, domBuilder, currentNSMap)) {
246
+ parseStack.push(el);
247
+ }
248
+ }
249
+
250
+ if (isHTML && !el.closed) {
251
+ end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
252
+ } else {
253
+ end++;
254
+ }
255
+ }
256
+ } catch (e) {
257
+ if (e instanceof ParseError) {
258
+ throw e;
259
+ } else if (e instanceof DOMException) {
260
+ // A DOMException raised while building the DOM is not-well-formed input,
261
+ // so it is wrapped into a ParseError and reported before being thrown.
262
+ return errorHandler.fatalError('Error constructing the DOM: ' + e.name + ': ' + e.message, e);
263
+ }
264
+ errorHandler.error('element parse error: ' + e);
265
+ end = -1;
266
+ }
267
+ if (end > start) {
268
+ start = end;
269
+ } else {
270
+ //Possible sax fallback here, risk of positional error
271
+ appendText(Math.max(tagStart, start) + 1);
272
+ }
273
+ }
274
+ }
275
+
276
+ function copyLocator(f, t) {
277
+ t.lineNumber = f.lineNumber;
278
+ t.columnNumber = f.columnNumber;
279
+ return t;
280
+ }
281
+
282
+ /**
283
+ * @returns
284
+ * end of the elementStartPart(end of elementEndPart for selfClosed el)
285
+ * @see {@link #appendElement}
286
+ */
287
+ function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler, isHTML) {
288
+ /**
289
+ * @param {string} qname
290
+ * @param {string} value
291
+ * @param {number} startIndex
292
+ */
293
+ function addAttribute(qname, value, startIndex) {
294
+ if (hasOwn(el.attributeNames, qname)) {
295
+ return errorHandler.fatalError('Attribute ' + qname + ' redefined');
296
+ }
297
+ if (!isHTML && value.indexOf('<') >= 0) {
298
+ return errorHandler.fatalError("Unescaped '<' not allowed in attributes values");
299
+ }
300
+ el.addValue(
301
+ qname,
302
+ // @see https://www.w3.org/TR/xml/#AVNormalize
303
+ // since the xmldom sax parser does not "interpret" DTD the following is not implemented:
304
+ // - recursive replacement of (DTD) entity references
305
+ // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
306
+ value.replace(/[\t\n\r]/g, ' ').replace(ENTITY_REG, entityReplacer),
307
+ startIndex
308
+ );
309
+ }
310
+
311
+ var attrName;
312
+ var value;
313
+ var p = ++start;
314
+ var s = S_TAG; //status
315
+ while (true) {
316
+ var c = source.charAt(p);
317
+ if (s === S_TAG && c === '<') {
318
+ // A `<` can never occur inside a tag name. Without this guard the scan runs
319
+ // on to the next `>` (or EOF) before `setTagName` rejects the whole slice, so
320
+ // a document with many `<` inside a malformed tag makes each one-character
321
+ // recovery step re-scan to the distant `>` — O(n^2). Stopping at the `<` keeps
322
+ // each recovery step bounded. The candidate scanned so far is reported raw,
323
+ // consistent with the sibling invalid-tag-name throw below.
324
+ throw new Error('unexpected < in tag name: ' + source.slice(start, p));
325
+ }
326
+ switch (c) {
327
+ case '=':
328
+ if (s === S_ATTR) {
329
+ //attrName
330
+ attrName = source.slice(start, p);
331
+ s = S_EQ;
332
+ } else if (s === S_ATTR_SPACE) {
333
+ s = S_EQ;
334
+ } else {
335
+ //fatalError: equal must after attrName or space after attrName
336
+ throw new Error('attribute equal must after attrName');
337
+ }
338
+ break;
339
+ case "'":
340
+ case '"':
341
+ if (
342
+ s === S_EQ ||
343
+ s === S_ATTR //|| s == S_ATTR_SPACE
344
+ ) {
345
+ //equal
346
+ if (s === S_ATTR) {
347
+ errorHandler.warning('attribute value must after "="');
348
+ attrName = source.slice(start, p);
349
+ }
350
+ start = p + 1;
351
+ p = source.indexOf(c, start);
352
+ if (p > 0) {
353
+ value = source.slice(start, p);
354
+ addAttribute(attrName, value, start - 1);
355
+ s = S_ATTR_END;
356
+ } else {
357
+ //fatalError: no end quot match
358
+ throw new Error("attribute value no end '" + c + "' match");
359
+ }
360
+ } else if (s == S_ATTR_NOQUOT_VALUE) {
361
+ value = source.slice(start, p);
362
+ addAttribute(attrName, value, start);
363
+ errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
364
+ start = p + 1;
365
+ s = S_ATTR_END;
366
+ } else {
367
+ //fatalError: no equal before
368
+ throw new Error('attribute value must after "="');
369
+ }
370
+ break;
371
+ case '/':
372
+ switch (s) {
373
+ case S_TAG:
374
+ el.setTagName(source.slice(start, p));
375
+ case S_ATTR_END:
376
+ case S_TAG_SPACE:
377
+ case S_TAG_CLOSE:
378
+ s = S_TAG_CLOSE;
379
+ el.closed = true;
380
+ case S_ATTR_NOQUOT_VALUE:
381
+ case S_ATTR:
382
+ break;
383
+ case S_ATTR_SPACE:
384
+ el.closed = true;
385
+ break;
386
+ //case S_EQ:
387
+ default:
388
+ throw new Error("attribute invalid close char('/')");
389
+ }
390
+ break;
391
+ case '': //end document
392
+ errorHandler.error('unexpected end of input');
393
+ if (s == S_TAG) {
394
+ el.setTagName(source.slice(start, p));
395
+ }
396
+ return p;
397
+ case '>':
398
+ switch (s) {
399
+ case S_TAG:
400
+ el.setTagName(source.slice(start, p));
401
+ case S_ATTR_END:
402
+ case S_TAG_SPACE:
403
+ case S_TAG_CLOSE:
404
+ break; //normal
405
+ case S_ATTR_NOQUOT_VALUE: //Compatible state
406
+ case S_ATTR:
407
+ value = source.slice(start, p);
408
+ if (value.slice(-1) === '/') {
409
+ el.closed = true;
410
+ value = value.slice(0, -1);
411
+ }
412
+ case S_ATTR_SPACE:
413
+ if (s === S_ATTR_SPACE) {
414
+ value = attrName;
415
+ }
416
+ if (s == S_ATTR_NOQUOT_VALUE) {
417
+ errorHandler.warning('attribute "' + value + '" missed quot(")!');
418
+ addAttribute(attrName, value, start);
419
+ } else {
420
+ if (!isHTML) {
421
+ errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
422
+ }
423
+ addAttribute(value, value, start);
424
+ }
425
+ break;
426
+ case S_EQ:
427
+ if (!isHTML) {
428
+ return errorHandler.fatalError('AttValue: \' or " expected');
429
+ }
430
+ }
431
+ return p;
432
+ /*xml space '\x20' | #x9 | #xD | #xA; */
433
+ case '\u0080':
434
+ c = ' ';
435
+ default:
436
+ if (c <= ' ') {
437
+ //space
438
+ switch (s) {
439
+ case S_TAG:
440
+ el.setTagName(source.slice(start, p)); //tagName
441
+ s = S_TAG_SPACE;
442
+ break;
443
+ case S_ATTR:
444
+ attrName = source.slice(start, p);
445
+ s = S_ATTR_SPACE;
446
+ break;
447
+ case S_ATTR_NOQUOT_VALUE:
448
+ var value = source.slice(start, p);
449
+ errorHandler.warning('attribute "' + value + '" missed quot(")!!');
450
+ addAttribute(attrName, value, start);
451
+ case S_ATTR_END:
452
+ s = S_TAG_SPACE;
453
+ break;
454
+ //case S_TAG_SPACE:
455
+ //case S_EQ:
456
+ //case S_ATTR_SPACE:
457
+ // void();break;
458
+ //case S_TAG_CLOSE:
459
+ //ignore warning
460
+ }
461
+ } else {
462
+ //not space
463
+ //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
464
+ //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
465
+ switch (s) {
466
+ //case S_TAG:void();break;
467
+ //case S_ATTR:void();break;
468
+ //case S_ATTR_NOQUOT_VALUE:void();break;
469
+ case S_ATTR_SPACE:
470
+ if (!isHTML) {
471
+ errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
472
+ }
473
+ addAttribute(attrName, attrName, start);
474
+ start = p;
475
+ s = S_ATTR;
476
+ break;
477
+ case S_ATTR_END:
478
+ errorHandler.warning('attribute space is required"' + attrName + '"!!');
479
+ case S_TAG_SPACE:
480
+ s = S_ATTR;
481
+ start = p;
482
+ break;
483
+ case S_EQ:
484
+ s = S_ATTR_NOQUOT_VALUE;
485
+ start = p;
486
+ break;
487
+ case S_TAG_CLOSE:
488
+ throw new Error("elements closed character '/' and '>' must be connected to");
489
+ }
490
+ }
491
+ } //end outer switch
492
+ p++;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * @returns
498
+ * `true` if a new namespace has been defined.
499
+ */
500
+ function appendElement(el, domBuilder, currentNSMap) {
501
+ var tagName = el.tagName;
502
+ var localNSMap = null;
503
+ var i = el.length;
504
+ while (i--) {
505
+ var a = el[i];
506
+ var qName = a.qName;
507
+ var value = a.value;
508
+ var nsp = qName.indexOf(':');
509
+ if (nsp > 0) {
510
+ var prefix = (a.prefix = qName.slice(0, nsp));
511
+ var localName = qName.slice(nsp + 1);
512
+ var nsPrefix = prefix === 'xmlns' && localName;
513
+ } else {
514
+ localName = qName;
515
+ prefix = null;
516
+ nsPrefix = qName === 'xmlns' && '';
517
+ }
518
+ //can not set prefix,because prefix !== ''
519
+ a.localName = localName;
520
+ //prefix == null for no ns prefix attribute
521
+ if (nsPrefix !== false) {
522
+ //hack!!
523
+ if (localNSMap == null) {
524
+ localNSMap = Object.create(null);
525
+ // Derive the child scope's namespace map by prototype-chain inheritance
526
+ // instead of a flat copy: lookups inherit ancestor prefixes transparently,
527
+ // so a document nesting N scopes retains O(N) map entries rather than
528
+ // sum(1..N) = O(N^2). `localNSMap` stays a flat own-only record of the
529
+ // prefixes declared at THIS element, so own-property enumeration
530
+ // (endPrefixMapping below) still reports only local declarations.
531
+ currentNSMap = Object.create(currentNSMap);
532
+ }
533
+ currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
534
+ a.uri = NAMESPACE.XMLNS;
535
+ domBuilder.startPrefixMapping(nsPrefix, value);
536
+ }
537
+ }
538
+ var i = el.length;
539
+ while (i--) {
540
+ a = el[i];
541
+ if (a.prefix) {
542
+ //no prefix attribute has no namespace
543
+ if (a.prefix === 'xml') {
544
+ a.uri = NAMESPACE.XML;
545
+ }
546
+ if (a.prefix !== 'xmlns') {
547
+ a.uri = currentNSMap[a.prefix];
548
+ }
549
+ }
550
+ }
551
+ var nsp = tagName.indexOf(':');
552
+ if (nsp > 0) {
553
+ prefix = el.prefix = tagName.slice(0, nsp);
554
+ localName = el.localName = tagName.slice(nsp + 1);
555
+ } else {
556
+ prefix = null; //important!!
557
+ localName = el.localName = tagName;
558
+ }
559
+ //no prefix element has default namespace
560
+ var ns = (el.uri = currentNSMap[prefix || '']);
561
+ domBuilder.startElement(ns, localName, tagName, el);
562
+ //endPrefixMapping and startPrefixMapping have not any help for dom builder
563
+ //localNSMap = null
564
+ if (el.closed) {
565
+ domBuilder.endElement(ns, localName, tagName);
566
+ if (localNSMap) {
567
+ for (prefix in localNSMap) {
568
+ if (hasOwn(localNSMap, prefix)) {
569
+ domBuilder.endPrefixMapping(prefix);
570
+ }
571
+ }
572
+ }
573
+ } else {
574
+ el.currentNSMap = currentNSMap;
575
+ el.localNSMap = localNSMap;
576
+ //parseStack.push(el);
577
+ return true;
578
+ }
579
+ }
580
+
581
+ function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
582
+ // https://html.spec.whatwg.org/#raw-text-elements
583
+ // https://html.spec.whatwg.org/#escapable-raw-text-elements
584
+ // https://html.spec.whatwg.org/#cdata-rcdata-restrictions:raw-text-elements
585
+ // TODO: https://html.spec.whatwg.org/#cdata-rcdata-restrictions
586
+ var isEscapableRaw = isHTMLEscapableRawTextElement(tagName);
587
+ if (isEscapableRaw || isHTMLRawTextElement(tagName)) {
588
+ // The closing tag of a raw-text element matches case-insensitively
589
+ // (WHATWG HTML §13.2.5.14 RAWTEXT end tag name state). A case-sensitive
590
+ // search that missed the closing tag would return -1 and the `substring`
591
+ // below would treat -1 as a backward slice from position 0, re-emitting all
592
+ // prior source and amplifying output quadratically across repeated elements.
593
+ // The regex is anchored to `elStartEnd` via `lastIndex` so it scans forward
594
+ // only (O(distance), not the whole source).
595
+ var closeTag = new RegExp('</' + tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '>', 'ig');
596
+ closeTag.lastIndex = elStartEnd;
597
+ var match = closeTag.exec(source);
598
+ var elEndStart = match ? match.index : -1;
599
+ if (elEndStart < 0) {
600
+ // No closing tag: never slice with a -1 end index. Leave the element to
601
+ // the parse loop's normal recovery instead of back-capturing the source.
602
+ return elStartEnd + 1;
603
+ }
604
+ var text = source.substring(elStartEnd + 1, elEndStart);
605
+
606
+ if (isEscapableRaw) {
607
+ text = text.replace(ENTITY_REG, entityReplacer);
608
+ }
609
+ domBuilder.characters(text, 0, text.length);
610
+ return elEndStart;
611
+ }
612
+ return elStartEnd + 1;
613
+ }
614
+
615
+ function _copy(source, target) {
616
+ for (var n in source) {
617
+ if (hasOwn(source, n)) {
618
+ target[n] = source[n];
619
+ }
620
+ }
621
+ }
622
+
623
+ /**
624
+ * @typedef ParseUtils
625
+ * @property {function(relativeIndex: number?): string | undefined} char
626
+ * Provides look ahead access to a singe character relative to the current index.
627
+ * @property {function(): number} getIndex
628
+ * Provides read-only access to the current index.
629
+ * @property {function(reg: RegExp): string | null} getMatch
630
+ * Applies the provided regular expression enforcing that it starts at the current index and
631
+ * returns the complete matching string,
632
+ * and moves the current index by the length of the matching string.
633
+ * @property {function(): string} getSource
634
+ * Provides read-only access to the complete source.
635
+ * @property {function(places: number?): void} skip
636
+ * moves the current index by places (defaults to 1)
637
+ * @property {function(): number} skipBlanks
638
+ * Moves the current index by the amount of white space that directly follows the current index
639
+ * and returns the amount of whitespace chars skipped (0..n),
640
+ * or -1 if the end of the source was reached.
641
+ * @property {function(): string} substringFromIndex
642
+ * creates a substring from the current index to the end of `source`
643
+ * @property {function(compareWith: string): boolean} substringStartsWith
644
+ * Checks if `source` contains `compareWith`, starting from the current index.
645
+ * @property {function(compareWith: string): boolean} substringStartsWithCaseInsensitive
646
+ * Checks if `source` contains `compareWith`, starting from the current index,
647
+ * comparing the upper case of both sides.
648
+ * @see {@link parseUtils}
649
+ */
650
+
651
+ /**
652
+ * A temporary scope for parsing and look ahead operations in `source`,
653
+ * starting from index `start`.
654
+ *
655
+ * Some operations move the current index by a number of positions,
656
+ * after which `getIndex` returns the new index.
657
+ *
658
+ * @param {string} source
659
+ * @param {number} start
660
+ * @returns {ParseUtils}
661
+ */
662
+ function parseUtils(source, start) {
663
+ var index = start;
664
+
665
+ function char(n) {
666
+ n = n || 0;
667
+ return source.charAt(index + n);
668
+ }
669
+
670
+ function skip(n) {
671
+ n = n || 1;
672
+ index += n;
673
+ }
674
+
675
+ function skipBlanks() {
676
+ var blanks = 0;
677
+ while (index < source.length) {
678
+ var c = char();
679
+ if (c !== ' ' && c !== '\n' && c !== '\t' && c !== '\r') {
680
+ return blanks;
681
+ }
682
+ blanks++;
683
+ skip();
684
+ }
685
+ return -1;
686
+ }
687
+ function substringFromIndex() {
688
+ return source.substring(index);
689
+ }
690
+ function substringStartsWith(text) {
691
+ return source.substring(index, index + text.length) === text;
692
+ }
693
+ function substringStartsWithCaseInsensitive(text) {
694
+ return source.substring(index, index + text.length).toUpperCase() === text.toUpperCase();
695
+ }
696
+
697
+ function getMatch(args) {
698
+ var expr = g.reg('^', args);
699
+ var match = expr.exec(substringFromIndex());
700
+ if (match) {
701
+ skip(match[0].length);
702
+ return match[0];
703
+ }
704
+ return null;
705
+ }
706
+ return {
707
+ char: char,
708
+ getIndex: function () {
709
+ return index;
710
+ },
711
+ getMatch: getMatch,
712
+ getSource: function () {
713
+ return source;
714
+ },
715
+ skip: skip,
716
+ skipBlanks: skipBlanks,
717
+ substringFromIndex: substringFromIndex,
718
+ substringStartsWith: substringStartsWith,
719
+ substringStartsWithCaseInsensitive: substringStartsWithCaseInsensitive,
720
+ };
721
+ }
722
+
723
+ /**
724
+ * @param {ParseUtils} p
725
+ * @param {DOMHandler} errorHandler
726
+ * @returns {string}
727
+ */
728
+ function parseDoctypeInternalSubset(p, errorHandler) {
729
+ /**
730
+ * @param {ParseUtils} p
731
+ * @param {DOMHandler} errorHandler
732
+ * @returns {string}
733
+ */
734
+ function parsePI(p, errorHandler) {
735
+ var match = g.PI.exec(p.substringFromIndex());
736
+ if (!match) {
737
+ return errorHandler.fatalError('processing instruction is not well-formed at position ' + p.getIndex());
738
+ }
739
+ if (match[1].toLowerCase() === 'xml') {
740
+ return errorHandler.fatalError(
741
+ 'xml declaration is only allowed at the start of the document, but found at position ' + p.getIndex()
742
+ );
743
+ }
744
+ p.skip(match[0].length);
745
+ return match[0];
746
+ }
747
+ // Parse internal subset
748
+ var source = p.getSource();
749
+ if (p.char() === '[') {
750
+ p.skip(1);
751
+ var intSubsetStart = p.getIndex();
752
+ while (p.getIndex() < source.length) {
753
+ p.skipBlanks();
754
+ if (p.char() === ']') {
755
+ var internalSubset = source.substring(intSubsetStart, p.getIndex());
756
+ p.skip(1);
757
+ return internalSubset;
758
+ }
759
+ var current = null;
760
+ // Only in external subset
761
+ // if (char() === '<' && char(1) === '!' && char(2) === '[') {
762
+ // parseConditionalSections(p, errorHandler);
763
+ // } else
764
+ if (p.char() === '<' && p.char(1) === '!') {
765
+ switch (p.char(2)) {
766
+ case 'E': // ELEMENT | ENTITY
767
+ if (p.char(3) === 'L') {
768
+ current = p.getMatch(g.elementdecl);
769
+ } else if (p.char(3) === 'N') {
770
+ current = p.getMatch(g.EntityDecl);
771
+ }
772
+ break;
773
+ case 'A': // ATTRIBUTE
774
+ current = p.getMatch(g.AttlistDecl);
775
+ break;
776
+ case 'N': // NOTATION
777
+ current = p.getMatch(g.NotationDecl);
778
+ break;
779
+ case '-': // COMMENT
780
+ current = p.getMatch(g.Comment);
781
+ break;
782
+ }
783
+ } else if (p.char() === '<' && p.char(1) === '?') {
784
+ current = parsePI(p, errorHandler);
785
+ } else if (p.char() === '%') {
786
+ current = p.getMatch(g.PEReference);
787
+ } else {
788
+ return errorHandler.fatalError('Error detected in Markup declaration');
789
+ }
790
+ if (!current) {
791
+ return errorHandler.fatalError('Error in internal subset at position ' + p.getIndex());
792
+ }
793
+ }
794
+ return errorHandler.fatalError('doctype internal subset is not well-formed, missing ]');
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Called when the parser encounters an element starting with '<!'.
800
+ *
801
+ * @param {string} source
802
+ * The xml.
803
+ * @param {number} start
804
+ * the start index of the '<!'
805
+ * @param {DOMHandler} domBuilder
806
+ * @param {DOMHandler} errorHandler
807
+ * @param {boolean} isHTML
808
+ * @returns {number | never}
809
+ * The end index of the element.
810
+ * @throws {ParseError}
811
+ * In case the element is not well-formed.
812
+ */
813
+ function parseDoctypeCommentOrCData(source, start, domBuilder, errorHandler, isHTML) {
814
+ var p = parseUtils(source, start);
815
+
816
+ switch (isHTML ? p.char(2).toUpperCase() : p.char(2)) {
817
+ case '-':
818
+ // should be a comment
819
+ var comment = p.getMatch(g.Comment);
820
+ if (comment) {
821
+ domBuilder.comment(comment, g.COMMENT_START.length, comment.length - g.COMMENT_START.length - g.COMMENT_END.length);
822
+ return p.getIndex();
823
+ } else {
824
+ return errorHandler.fatalError('comment is not well-formed at position ' + p.getIndex());
825
+ }
826
+ case '[':
827
+ // should be CDATA
828
+ var cdata = p.getMatch(g.CDSect);
829
+ if (cdata) {
830
+ if (!isHTML && !domBuilder.currentElement) {
831
+ return errorHandler.fatalError('CDATA outside of element');
832
+ }
833
+ domBuilder.startCDATA();
834
+ domBuilder.characters(cdata, g.CDATA_START.length, cdata.length - g.CDATA_START.length - g.CDATA_END.length);
835
+ domBuilder.endCDATA();
836
+ return p.getIndex();
837
+ } else {
838
+ return errorHandler.fatalError('Invalid CDATA starting at position ' + start);
839
+ }
840
+ case 'D': {
841
+ // should be DOCTYPE
842
+ if (domBuilder.doc && domBuilder.doc.documentElement) {
843
+ return errorHandler.fatalError('Doctype not allowed inside or after documentElement at position ' + p.getIndex());
844
+ }
845
+ if (isHTML ? !p.substringStartsWithCaseInsensitive(g.DOCTYPE_DECL_START) : !p.substringStartsWith(g.DOCTYPE_DECL_START)) {
846
+ return errorHandler.fatalError('Expected ' + g.DOCTYPE_DECL_START + ' at position ' + p.getIndex());
847
+ }
848
+ p.skip(g.DOCTYPE_DECL_START.length);
849
+ if (p.skipBlanks() < 1) {
850
+ return errorHandler.fatalError('Expected whitespace after ' + g.DOCTYPE_DECL_START + ' at position ' + p.getIndex());
851
+ }
852
+
853
+ var doctype = {
854
+ name: undefined,
855
+ publicId: undefined,
856
+ systemId: undefined,
857
+ internalSubset: undefined,
858
+ };
859
+ // Parse the DOCTYPE name
860
+ doctype.name = p.getMatch(g.Name);
861
+ if (!doctype.name)
862
+ return errorHandler.fatalError('doctype name missing or contains unexpected characters at position ' + p.getIndex());
863
+
864
+ if (isHTML && doctype.name.toLowerCase() !== 'html') {
865
+ errorHandler.warning('Unexpected DOCTYPE in HTML document at position ' + p.getIndex());
866
+ }
867
+ p.skipBlanks();
868
+
869
+ // Check for ExternalID
870
+ if (p.substringStartsWith(g.PUBLIC) || p.substringStartsWith(g.SYSTEM)) {
871
+ var match = g.ExternalID_match.exec(p.substringFromIndex());
872
+ if (!match) {
873
+ return errorHandler.fatalError('doctype external id is not well-formed at position ' + p.getIndex());
874
+ }
875
+ if (match.groups.SystemLiteralOnly !== undefined) {
876
+ doctype.systemId = match.groups.SystemLiteralOnly;
877
+ } else {
878
+ doctype.systemId = match.groups.SystemLiteral;
879
+ doctype.publicId = match.groups.PubidLiteral;
880
+ }
881
+ p.skip(match[0].length);
882
+ } else if (isHTML && p.substringStartsWithCaseInsensitive(g.SYSTEM)) {
883
+ // https://html.spec.whatwg.org/multipage/syntax.html#doctype-legacy-string
884
+ p.skip(g.SYSTEM.length);
885
+ if (p.skipBlanks() < 1) {
886
+ return errorHandler.fatalError('Expected whitespace after ' + g.SYSTEM + ' at position ' + p.getIndex());
887
+ }
888
+ doctype.systemId = p.getMatch(g.ABOUT_LEGACY_COMPAT_SystemLiteral);
889
+ if (!doctype.systemId) {
890
+ return errorHandler.fatalError(
891
+ 'Expected ' + g.ABOUT_LEGACY_COMPAT + ' in single or double quotes after ' + g.SYSTEM + ' at position ' + p.getIndex()
892
+ );
893
+ }
894
+ }
895
+ if (isHTML && doctype.systemId && !g.ABOUT_LEGACY_COMPAT_SystemLiteral.test(doctype.systemId)) {
896
+ errorHandler.warning('Unexpected doctype.systemId in HTML document at position ' + p.getIndex());
897
+ }
898
+ if (!isHTML) {
899
+ p.skipBlanks();
900
+ doctype.internalSubset = parseDoctypeInternalSubset(p, errorHandler);
901
+ }
902
+ p.skipBlanks();
903
+ if (p.char() !== '>') {
904
+ return errorHandler.fatalError('doctype not terminated with > at position ' + p.getIndex());
905
+ }
906
+ p.skip(1);
907
+ domBuilder.startDTD(doctype.name, doctype.publicId, doctype.systemId, doctype.internalSubset);
908
+ domBuilder.endDTD();
909
+ return p.getIndex();
910
+ }
911
+ default:
912
+ return errorHandler.fatalError('Not well-formed XML starting with "<!" at position ' + start);
913
+ }
914
+ }
915
+
916
+ function parseProcessingInstruction(source, start, domBuilder, errorHandler) {
917
+ var match = source.substring(start).match(g.PI);
918
+ if (!match) {
919
+ return errorHandler.fatalError('Invalid processing instruction starting at position ' + start);
920
+ }
921
+ if (match[1].toLowerCase() === 'xml') {
922
+ if (start > 0) {
923
+ return errorHandler.fatalError(
924
+ 'processing instruction at position ' + start + ' is an xml declaration which is only at the start of the document'
925
+ );
926
+ }
927
+ if (!g.XMLDecl.test(source.substring(start))) {
928
+ return errorHandler.fatalError('xml declaration is not well-formed');
929
+ }
930
+ }
931
+ domBuilder.processingInstruction(match[1], match[2]);
932
+ return start + match[0].length;
933
+ }
934
+
935
+ function ElementAttributes() {
936
+ this.attributeNames = Object.create(null);
937
+ }
938
+
939
+ ElementAttributes.prototype = {
940
+ setTagName: function (tagName) {
941
+ if (!g.QName_exact.test(tagName)) {
942
+ throw new Error('invalid tagName:' + tagName);
943
+ }
944
+ this.tagName = tagName;
945
+ },
946
+ addValue: function (qName, value, offset) {
947
+ if (!g.QName_exact.test(qName)) {
948
+ throw new Error('invalid attribute:' + qName);
949
+ }
950
+ this.attributeNames[qName] = this.length;
951
+ this[this.length++] = { qName: qName, value: value, offset: offset };
952
+ },
953
+ length: 0,
954
+ getLocalName: function (i) {
955
+ return this[i].localName;
956
+ },
957
+ getLocator: function (i) {
958
+ return this[i].locator;
959
+ },
960
+ getQName: function (i) {
961
+ return this[i].qName;
962
+ },
963
+ getURI: function (i) {
964
+ return this[i].uri;
965
+ },
966
+ getValue: function (i) {
967
+ return this[i].value;
968
+ },
969
+ // ,getIndex:function(uri, localName)){
970
+ // if(localName){
971
+ //
972
+ // }else{
973
+ // var qName = uri
974
+ // }
975
+ // },
976
+ // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
977
+ // getType:function(uri,localName){}
978
+ // getType:function(i){},
979
+ };
980
+
981
+ exports.XMLReader = XMLReader;
982
+ exports.parseUtils = parseUtils;
983
+ exports.parseDoctypeCommentOrCData = parseDoctypeCommentOrCData;