1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | "use strict";
|
23 |
|
24 | Object.defineProperty(exports, "__esModule", {
|
25 | value: true
|
26 | });
|
27 | exports.SimpleXMLParser = void 0;
|
28 | const XMLParserErrorCode = {
|
29 | NoError: 0,
|
30 | EndOfDocument: -1,
|
31 | UnterminatedCdat: -2,
|
32 | UnterminatedXmlDeclaration: -3,
|
33 | UnterminatedDoctypeDeclaration: -4,
|
34 | UnterminatedComment: -5,
|
35 | MalformedElement: -6,
|
36 | OutOfMemory: -7,
|
37 | UnterminatedAttributeValue: -8,
|
38 | UnterminatedElement: -9,
|
39 | ElementNeverBegun: -10
|
40 | };
|
41 |
|
42 | function isWhitespace(s, index) {
|
43 | const ch = s[index];
|
44 | return ch === " " || ch === "\n" || ch === "\r" || ch === "\t";
|
45 | }
|
46 |
|
47 | function isWhitespaceString(s) {
|
48 | for (let i = 0, ii = s.length; i < ii; i++) {
|
49 | if (!isWhitespace(s, i)) {
|
50 | return false;
|
51 | }
|
52 | }
|
53 |
|
54 | return true;
|
55 | }
|
56 |
|
57 | class XMLParserBase {
|
58 | _resolveEntities(s) {
|
59 | return s.replace(/&([^;]+);/g, (all, entity) => {
|
60 | if (entity.substring(0, 2) === "#x") {
|
61 | return String.fromCharCode(parseInt(entity.substring(2), 16));
|
62 | } else if (entity.substring(0, 1) === "#") {
|
63 | return String.fromCharCode(parseInt(entity.substring(1), 10));
|
64 | }
|
65 |
|
66 | switch (entity) {
|
67 | case "lt":
|
68 | return "<";
|
69 |
|
70 | case "gt":
|
71 | return ">";
|
72 |
|
73 | case "amp":
|
74 | return "&";
|
75 |
|
76 | case "quot":
|
77 | return '"';
|
78 | }
|
79 |
|
80 | return this.onResolveEntity(entity);
|
81 | });
|
82 | }
|
83 |
|
84 | _parseContent(s, start) {
|
85 | const attributes = [];
|
86 | let pos = start;
|
87 |
|
88 | function skipWs() {
|
89 | while (pos < s.length && isWhitespace(s, pos)) {
|
90 | ++pos;
|
91 | }
|
92 | }
|
93 |
|
94 | while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "/") {
|
95 | ++pos;
|
96 | }
|
97 |
|
98 | const name = s.substring(start, pos);
|
99 | skipWs();
|
100 |
|
101 | while (pos < s.length && s[pos] !== ">" && s[pos] !== "/" && s[pos] !== "?") {
|
102 | skipWs();
|
103 | let attrName = "",
|
104 | attrValue = "";
|
105 |
|
106 | while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== "=") {
|
107 | attrName += s[pos];
|
108 | ++pos;
|
109 | }
|
110 |
|
111 | skipWs();
|
112 |
|
113 | if (s[pos] !== "=") {
|
114 | return null;
|
115 | }
|
116 |
|
117 | ++pos;
|
118 | skipWs();
|
119 | const attrEndChar = s[pos];
|
120 |
|
121 | if (attrEndChar !== '"' && attrEndChar !== "'") {
|
122 | return null;
|
123 | }
|
124 |
|
125 | const attrEndIndex = s.indexOf(attrEndChar, ++pos);
|
126 |
|
127 | if (attrEndIndex < 0) {
|
128 | return null;
|
129 | }
|
130 |
|
131 | attrValue = s.substring(pos, attrEndIndex);
|
132 | attributes.push({
|
133 | name: attrName,
|
134 | value: this._resolveEntities(attrValue)
|
135 | });
|
136 | pos = attrEndIndex + 1;
|
137 | skipWs();
|
138 | }
|
139 |
|
140 | return {
|
141 | name,
|
142 | attributes,
|
143 | parsed: pos - start
|
144 | };
|
145 | }
|
146 |
|
147 | _parseProcessingInstruction(s, start) {
|
148 | let pos = start;
|
149 |
|
150 | function skipWs() {
|
151 | while (pos < s.length && isWhitespace(s, pos)) {
|
152 | ++pos;
|
153 | }
|
154 | }
|
155 |
|
156 | while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "/") {
|
157 | ++pos;
|
158 | }
|
159 |
|
160 | const name = s.substring(start, pos);
|
161 | skipWs();
|
162 | const attrStart = pos;
|
163 |
|
164 | while (pos < s.length && (s[pos] !== "?" || s[pos + 1] !== ">")) {
|
165 | ++pos;
|
166 | }
|
167 |
|
168 | const value = s.substring(attrStart, pos);
|
169 | return {
|
170 | name,
|
171 | value,
|
172 | parsed: pos - start
|
173 | };
|
174 | }
|
175 |
|
176 | parseXml(s) {
|
177 | let i = 0;
|
178 |
|
179 | while (i < s.length) {
|
180 | const ch = s[i];
|
181 | let j = i;
|
182 |
|
183 | if (ch === "<") {
|
184 | ++j;
|
185 | const ch2 = s[j];
|
186 | let q;
|
187 |
|
188 | switch (ch2) {
|
189 | case "/":
|
190 | ++j;
|
191 | q = s.indexOf(">", j);
|
192 |
|
193 | if (q < 0) {
|
194 | this.onError(XMLParserErrorCode.UnterminatedElement);
|
195 | return;
|
196 | }
|
197 |
|
198 | this.onEndElement(s.substring(j, q));
|
199 | j = q + 1;
|
200 | break;
|
201 |
|
202 | case "?":
|
203 | ++j;
|
204 |
|
205 | const pi = this._parseProcessingInstruction(s, j);
|
206 |
|
207 | if (s.substring(j + pi.parsed, j + pi.parsed + 2) !== "?>") {
|
208 | this.onError(XMLParserErrorCode.UnterminatedXmlDeclaration);
|
209 | return;
|
210 | }
|
211 |
|
212 | this.onPi(pi.name, pi.value);
|
213 | j += pi.parsed + 2;
|
214 | break;
|
215 |
|
216 | case "!":
|
217 | if (s.substring(j + 1, j + 3) === "--") {
|
218 | q = s.indexOf("-->", j + 3);
|
219 |
|
220 | if (q < 0) {
|
221 | this.onError(XMLParserErrorCode.UnterminatedComment);
|
222 | return;
|
223 | }
|
224 |
|
225 | this.onComment(s.substring(j + 3, q));
|
226 | j = q + 3;
|
227 | } else if (s.substring(j + 1, j + 8) === "[CDATA[") {
|
228 | q = s.indexOf("]]>", j + 8);
|
229 |
|
230 | if (q < 0) {
|
231 | this.onError(XMLParserErrorCode.UnterminatedCdat);
|
232 | return;
|
233 | }
|
234 |
|
235 | this.onCdata(s.substring(j + 8, q));
|
236 | j = q + 3;
|
237 | } else if (s.substring(j + 1, j + 8) === "DOCTYPE") {
|
238 | const q2 = s.indexOf("[", j + 8);
|
239 | let complexDoctype = false;
|
240 | q = s.indexOf(">", j + 8);
|
241 |
|
242 | if (q < 0) {
|
243 | this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
|
244 | return;
|
245 | }
|
246 |
|
247 | if (q2 > 0 && q > q2) {
|
248 | q = s.indexOf("]>", j + 8);
|
249 |
|
250 | if (q < 0) {
|
251 | this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
|
252 | return;
|
253 | }
|
254 |
|
255 | complexDoctype = true;
|
256 | }
|
257 |
|
258 | const doctypeContent = s.substring(j + 8, q + (complexDoctype ? 1 : 0));
|
259 | this.onDoctype(doctypeContent);
|
260 | j = q + (complexDoctype ? 2 : 1);
|
261 | } else {
|
262 | this.onError(XMLParserErrorCode.MalformedElement);
|
263 | return;
|
264 | }
|
265 |
|
266 | break;
|
267 |
|
268 | default:
|
269 | const content = this._parseContent(s, j);
|
270 |
|
271 | if (content === null) {
|
272 | this.onError(XMLParserErrorCode.MalformedElement);
|
273 | return;
|
274 | }
|
275 |
|
276 | let isClosed = false;
|
277 |
|
278 | if (s.substring(j + content.parsed, j + content.parsed + 2) === "/>") {
|
279 | isClosed = true;
|
280 | } else if (s.substring(j + content.parsed, j + content.parsed + 1) !== ">") {
|
281 | this.onError(XMLParserErrorCode.UnterminatedElement);
|
282 | return;
|
283 | }
|
284 |
|
285 | this.onBeginElement(content.name, content.attributes, isClosed);
|
286 | j += content.parsed + (isClosed ? 2 : 1);
|
287 | break;
|
288 | }
|
289 | } else {
|
290 | while (j < s.length && s[j] !== "<") {
|
291 | j++;
|
292 | }
|
293 |
|
294 | const text = s.substring(i, j);
|
295 | this.onText(this._resolveEntities(text));
|
296 | }
|
297 |
|
298 | i = j;
|
299 | }
|
300 | }
|
301 |
|
302 | onResolveEntity(name) {
|
303 | return `&${name};`;
|
304 | }
|
305 |
|
306 | onPi(name, value) {}
|
307 |
|
308 | onComment(text) {}
|
309 |
|
310 | onCdata(text) {}
|
311 |
|
312 | onDoctype(doctypeContent) {}
|
313 |
|
314 | onText(text) {}
|
315 |
|
316 | onBeginElement(name, attributes, isEmpty) {}
|
317 |
|
318 | onEndElement(name) {}
|
319 |
|
320 | onError(code) {}
|
321 |
|
322 | }
|
323 |
|
324 | class SimpleDOMNode {
|
325 | constructor(nodeName, nodeValue) {
|
326 | this.nodeName = nodeName;
|
327 | this.nodeValue = nodeValue;
|
328 | Object.defineProperty(this, "parentNode", {
|
329 | value: null,
|
330 | writable: true
|
331 | });
|
332 | }
|
333 |
|
334 | get firstChild() {
|
335 | return this.childNodes && this.childNodes[0];
|
336 | }
|
337 |
|
338 | get nextSibling() {
|
339 | const childNodes = this.parentNode.childNodes;
|
340 |
|
341 | if (!childNodes) {
|
342 | return undefined;
|
343 | }
|
344 |
|
345 | const index = childNodes.indexOf(this);
|
346 |
|
347 | if (index === -1) {
|
348 | return undefined;
|
349 | }
|
350 |
|
351 | return childNodes[index + 1];
|
352 | }
|
353 |
|
354 | get textContent() {
|
355 | if (!this.childNodes) {
|
356 | return this.nodeValue || "";
|
357 | }
|
358 |
|
359 | return this.childNodes.map(function (child) {
|
360 | return child.textContent;
|
361 | }).join("");
|
362 | }
|
363 |
|
364 | hasChildNodes() {
|
365 | return this.childNodes && this.childNodes.length > 0;
|
366 | }
|
367 |
|
368 | }
|
369 |
|
370 | class SimpleXMLParser extends XMLParserBase {
|
371 | constructor() {
|
372 | super();
|
373 | this._currentFragment = null;
|
374 | this._stack = null;
|
375 | this._errorCode = XMLParserErrorCode.NoError;
|
376 | }
|
377 |
|
378 | parseFromString(data) {
|
379 | this._currentFragment = [];
|
380 | this._stack = [];
|
381 | this._errorCode = XMLParserErrorCode.NoError;
|
382 | this.parseXml(data);
|
383 |
|
384 | if (this._errorCode !== XMLParserErrorCode.NoError) {
|
385 | return undefined;
|
386 | }
|
387 |
|
388 | const [documentElement] = this._currentFragment;
|
389 |
|
390 | if (!documentElement) {
|
391 | return undefined;
|
392 | }
|
393 |
|
394 | return {
|
395 | documentElement
|
396 | };
|
397 | }
|
398 |
|
399 | onResolveEntity(name) {
|
400 | switch (name) {
|
401 | case "apos":
|
402 | return "'";
|
403 | }
|
404 |
|
405 | return super.onResolveEntity(name);
|
406 | }
|
407 |
|
408 | onText(text) {
|
409 | if (isWhitespaceString(text)) {
|
410 | return;
|
411 | }
|
412 |
|
413 | const node = new SimpleDOMNode("#text", text);
|
414 |
|
415 | this._currentFragment.push(node);
|
416 | }
|
417 |
|
418 | onCdata(text) {
|
419 | const node = new SimpleDOMNode("#text", text);
|
420 |
|
421 | this._currentFragment.push(node);
|
422 | }
|
423 |
|
424 | onBeginElement(name, attributes, isEmpty) {
|
425 | const node = new SimpleDOMNode(name);
|
426 | node.childNodes = [];
|
427 |
|
428 | this._currentFragment.push(node);
|
429 |
|
430 | if (isEmpty) {
|
431 | return;
|
432 | }
|
433 |
|
434 | this._stack.push(this._currentFragment);
|
435 |
|
436 | this._currentFragment = node.childNodes;
|
437 | }
|
438 |
|
439 | onEndElement(name) {
|
440 | this._currentFragment = this._stack.pop() || [];
|
441 | const lastElement = this._currentFragment[this._currentFragment.length - 1];
|
442 |
|
443 | if (!lastElement) {
|
444 | return;
|
445 | }
|
446 |
|
447 | for (let i = 0, ii = lastElement.childNodes.length; i < ii; i++) {
|
448 | lastElement.childNodes[i].parentNode = lastElement;
|
449 | }
|
450 | }
|
451 |
|
452 | onError(code) {
|
453 | this._errorCode = code;
|
454 | }
|
455 |
|
456 | }
|
457 |
|
458 | exports.SimpleXMLParser = SimpleXMLParser; |
\ | No newline at end of file |