@lango-project/lango-js 0.1.2 → 0.1.4
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.
- package/NOTICE +5 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/cli.mjs +8 -2
- package/src/compiler.mjs +198 -58
- package/src/runtime.mjs +92 -1
- package/src/template/package.json +4 -0
- package/src/vendor/xmldom/conventions.js +429 -0
- package/src/vendor/xmldom/dom-parser.js +591 -0
- package/src/vendor/xmldom/dom.js +3759 -0
- package/src/vendor/xmldom/entities.js +2171 -0
- package/src/vendor/xmldom/errors.js +205 -0
- package/src/vendor/xmldom/grammar.js +559 -0
- package/src/vendor/xmldom/index.js +41 -0
- package/src/vendor/xmldom/sax.js +983 -0
- package/src/vendor/xmldom.LICENSE.md +8 -0
|
@@ -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;
|