UNPKG

10.3 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * Javascript code in this page
4 *
5 * Copyright 2020 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * Javascript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.SimpleXMLParser = void 0;
28const 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
42function isWhitespace(s, index) {
43 const ch = s[index];
44 return ch === " " || ch === "\n" || ch === "\r" || ch === "\t";
45}
46
47function 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
57class 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
324class 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
370class 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
458exports.SimpleXMLParser = SimpleXMLParser;
\No newline at end of file