@jerp/xml-stream-ts 0.1.0
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/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/names.d.ts +39 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +141 -0
- package/dist/names.js.map +1 -0
- package/dist/parser.d.ts +29 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +811 -0
- package/dist/parser.js.map +1 -0
- package/dist/tagParser.d.ts +62 -0
- package/dist/tagParser.d.ts.map +1 -0
- package/dist/tagParser.js +89 -0
- package/dist/tagParser.js.map +1 -0
- package/dist/textDecoder.d.ts +3 -0
- package/dist/textDecoder.d.ts.map +1 -0
- package/dist/textDecoder.js +104 -0
- package/dist/textDecoder.js.map +1 -0
- package/dist/valueResolver.d.ts +67 -0
- package/dist/valueResolver.d.ts.map +1 -0
- package/dist/valueResolver.js +422 -0
- package/dist/valueResolver.js.map +1 -0
- package/package.json +44 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { AttributeNameMatcher, NamespaceMap, NO_XML_PREFIX, PREDEFINED_XML_NAMESPACE } from "./names.js";
|
|
2
|
+
import { TagResolver } from "./tagParser.js";
|
|
3
|
+
// class TagAttributeBuilder {
|
|
4
|
+
// private attributeNames: string[] = [];
|
|
5
|
+
// private names: FQName[] = [];
|
|
6
|
+
// private values: any[] = [];
|
|
7
|
+
// push(fqName: FQName, value: Uint8Array<ArrayBuffer>) {
|
|
8
|
+
// this.attributeNames.push(fqName.name)
|
|
9
|
+
// this.names.push(fqName)
|
|
10
|
+
// this.values.push(value)
|
|
11
|
+
// }
|
|
12
|
+
// getProxy(): TagAttributes {
|
|
13
|
+
// const { attributeNames, names, values } = this
|
|
14
|
+
// return new Proxy({} as TagAttributes, {
|
|
15
|
+
// get(_, prop) {
|
|
16
|
+
// if (typeof prop === 'string') {
|
|
17
|
+
// const index = attributeNames.indexOf(prop)
|
|
18
|
+
// if (index !== -1) {
|
|
19
|
+
// return values[index]
|
|
20
|
+
// }
|
|
21
|
+
// } else if (prop === Symbol.iterator) {
|
|
22
|
+
// return function* () {
|
|
23
|
+
// for (let i = 0; i < attributeNames.length; i++) {
|
|
24
|
+
// const fqName = names[i]
|
|
25
|
+
// yield { name: fqName.name, value: values[i], namespace: fqName.namespace }
|
|
26
|
+
// }
|
|
27
|
+
// }
|
|
28
|
+
// }
|
|
29
|
+
// return undefined
|
|
30
|
+
// }
|
|
31
|
+
// })
|
|
32
|
+
// }
|
|
33
|
+
// }
|
|
34
|
+
class XMLTransformStream {
|
|
35
|
+
readable;
|
|
36
|
+
writable;
|
|
37
|
+
constructor(readable, writable) {
|
|
38
|
+
this.readable = readable;
|
|
39
|
+
this.writable = writable;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const NOOP_CONTROLLER = {
|
|
43
|
+
enqueue() { },
|
|
44
|
+
close() { },
|
|
45
|
+
error() { },
|
|
46
|
+
desiredSize: 0,
|
|
47
|
+
};
|
|
48
|
+
//const EMPTY_FROZEN_OBJECT = Object.freeze({})
|
|
49
|
+
const EMPTY_SEQUENCE = new Uint8Array(0);
|
|
50
|
+
const NO_ATTRIBUTES = Object.freeze({});
|
|
51
|
+
export function createParser(rootResolver, context = {}) {
|
|
52
|
+
const textContentBuffer = createTextBuffer(0x3c /*<*/);
|
|
53
|
+
const dqValueBuffer = createTextBuffer(0x22 /* " */);
|
|
54
|
+
const sqValueBuffer = createTextBuffer(0x27 /* ' */);
|
|
55
|
+
let controller = NOOP_CONTROLLER;
|
|
56
|
+
let resolver = new TagResolver({}, rootResolver.nsMap, '/');
|
|
57
|
+
resolver.addChildResolver(rootResolver);
|
|
58
|
+
const resolverStack = [resolver];
|
|
59
|
+
let pendingCaptureFn = null;
|
|
60
|
+
let seenLength = 0;
|
|
61
|
+
let begin = 0;
|
|
62
|
+
let end = 0;
|
|
63
|
+
let o = 0;
|
|
64
|
+
let tailBuffer = new TailBuffer();
|
|
65
|
+
let pendingException = null;
|
|
66
|
+
let bytes = new Uint8Array(0);
|
|
67
|
+
const readable = new ReadableStream({
|
|
68
|
+
start(readableController) {
|
|
69
|
+
controller = readableController;
|
|
70
|
+
},
|
|
71
|
+
cancel() {
|
|
72
|
+
controller = NOOP_CONTROLLER;
|
|
73
|
+
},
|
|
74
|
+
pull(_controller) {
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const writable = new WritableStream({
|
|
78
|
+
write(chunk) {
|
|
79
|
+
try {
|
|
80
|
+
const { front, tail } = tailBuffer.addChunk(chunk);
|
|
81
|
+
if (front) {
|
|
82
|
+
bytes = front;
|
|
83
|
+
parse();
|
|
84
|
+
pendingException = null;
|
|
85
|
+
}
|
|
86
|
+
if (tail) {
|
|
87
|
+
bytes = tail;
|
|
88
|
+
parse();
|
|
89
|
+
pendingException = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err instanceof XmlParserException) {
|
|
94
|
+
// this is potentially resumable at next chunk, so save the tail for next round of parsing
|
|
95
|
+
if (err instanceof XmlParserStartTagException) {
|
|
96
|
+
dqValueBuffer.reset();
|
|
97
|
+
sqValueBuffer.reset();
|
|
98
|
+
}
|
|
99
|
+
tailBuffer.saveTail(bytes, begin, end, 0);
|
|
100
|
+
pendingException = err;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
close() {
|
|
108
|
+
if (pendingException) {
|
|
109
|
+
controller.error(new XmlParserError(pendingException, bytes, seenLength));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (pendingCaptureFn || resolver !== resolverStack[0] || resolverStack.length > 1) {
|
|
113
|
+
controller.error(new Error('Unexpected end of XML stream'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
controller.close();
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return new XMLTransformStream(readable, writable);
|
|
120
|
+
function parse() {
|
|
121
|
+
begin = o = 0;
|
|
122
|
+
end = bytes.length;
|
|
123
|
+
while (o < end) {
|
|
124
|
+
if (pendingCaptureFn) {
|
|
125
|
+
pendingCaptureFn();
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (bytes[o] !== 0x3c /*<*/) {
|
|
129
|
+
captureTextContent();
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (o + 1 === end)
|
|
133
|
+
throw new XmlParserStartTagException(o, 'tag opening');
|
|
134
|
+
let b = bytes[++o];
|
|
135
|
+
switch (b) {
|
|
136
|
+
case 0x2f /*/*/:
|
|
137
|
+
o++;
|
|
138
|
+
captureEndTagClosing();
|
|
139
|
+
break;
|
|
140
|
+
case 0x21 /*!*/:
|
|
141
|
+
o++;
|
|
142
|
+
tokenizeExclamTag();
|
|
143
|
+
break;
|
|
144
|
+
case 0x3f /*?*/:
|
|
145
|
+
o++;
|
|
146
|
+
skippingQuestionTag();
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
captureStartTagOpening();
|
|
150
|
+
}
|
|
151
|
+
begin = o;
|
|
152
|
+
}
|
|
153
|
+
if (!pendingCaptureFn)
|
|
154
|
+
tailBuffer.reset();
|
|
155
|
+
seenLength += bytes.length;
|
|
156
|
+
}
|
|
157
|
+
// CaptureFn functions
|
|
158
|
+
function captureStartTagOpening() {
|
|
159
|
+
while (o < end && bytes[o] <= 0x20 /* */)
|
|
160
|
+
o++; // skip spaces before tag name
|
|
161
|
+
let tagName = { prefixBegin: o, nameBegin: o, nameLength: 0 };
|
|
162
|
+
let b = bytes[o];
|
|
163
|
+
while (o < end && b > 0x20 /* */ && b !== 0x3a /*:*/ && b !== 0x3e /*>*/ && b !== 0x2f /*/*/)
|
|
164
|
+
b = bytes[++o]; // expecting prefix or local name
|
|
165
|
+
if (o >= end)
|
|
166
|
+
throw new XmlParserStartTagException(o, 'tag name');
|
|
167
|
+
if (b === 0x3a /*:*/) {
|
|
168
|
+
tagName.prefixLength = o++ - tagName.prefixBegin;
|
|
169
|
+
tagName.nameBegin = o;
|
|
170
|
+
b = bytes[o];
|
|
171
|
+
while (o < end && b > 0x20 /* */ && b !== 0x3e /*>*/ && b !== 0x2f /*/*/)
|
|
172
|
+
b = bytes[++o]; // expecting local name
|
|
173
|
+
if (o >= end)
|
|
174
|
+
throw new XmlParserStartTagException(o, 'tag name');
|
|
175
|
+
tagName.nameLength = o - tagName.nameBegin;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
tagName.nameLength = o - tagName.nameBegin;
|
|
179
|
+
}
|
|
180
|
+
if (o >= end)
|
|
181
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
182
|
+
let hasContent = true;
|
|
183
|
+
const attOffsets = [];
|
|
184
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
185
|
+
o++; // skip spaces before attribute name
|
|
186
|
+
while (o < end) {
|
|
187
|
+
let b = bytes[o];
|
|
188
|
+
if (bytes[o] === 0x2f /* / */) {
|
|
189
|
+
b = bytes[++o];
|
|
190
|
+
hasContent = undefined;
|
|
191
|
+
}
|
|
192
|
+
if (o === end && b !== 0x3e /* > */) {
|
|
193
|
+
throw new XmlParserStartTagException(o, 'tag closing');
|
|
194
|
+
}
|
|
195
|
+
if (b === 0x3e /* > */) {
|
|
196
|
+
o++;
|
|
197
|
+
pendingCaptureFn = null;
|
|
198
|
+
if (hasContent !== undefined) {
|
|
199
|
+
const [b1, b2] = [bytes[o], bytes[o + 1]];
|
|
200
|
+
if (b1 === 0x3c /*<*/ && b2 === 0x2f /*/*/) {
|
|
201
|
+
scanEndTagClosing();
|
|
202
|
+
hasContent = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return resolveOnStart(tagName, attOffsets, hasContent);
|
|
206
|
+
}
|
|
207
|
+
if (b === 0x78 /* x */ && bytes[o + 1] === 0x6d /* m */ && bytes[o + 2] === 0x6c /* l */ && bytes[o + 3] === 0x6e /* n */ && bytes[o + 4] === 0x73 /* s */) {
|
|
208
|
+
const isDefaultNS = bytes[o + 5] !== 0x3a; /*:*/
|
|
209
|
+
const nsNameOffset = o += isDefaultNS ? 5 : 6;
|
|
210
|
+
if (!isDefaultNS) {
|
|
211
|
+
b = bytes[o];
|
|
212
|
+
while (o < end && b > 0x20 /* */ && b !== 0x3d /*=*/ && b !== 0x3e /*>*/ && b !== 0x2f /*/*/)
|
|
213
|
+
b = bytes[++o]; // name of the namespace declaration
|
|
214
|
+
}
|
|
215
|
+
if (o >= end)
|
|
216
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
217
|
+
const nsNameEnd = o;
|
|
218
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
219
|
+
o++; // skip spaces before =
|
|
220
|
+
if (bytes[o] === 0x3d /* = */) {
|
|
221
|
+
o++;
|
|
222
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
223
|
+
o++; // skip spaces after =
|
|
224
|
+
const q = bytes[o];
|
|
225
|
+
if (o >= end)
|
|
226
|
+
throw new XmlParserStartTagException(o, 'namespace uri');
|
|
227
|
+
if (q !== 0x22 /* " */ && q !== 0x27 /* ' */)
|
|
228
|
+
throwFatalError(new XmlParserStartTagException(o, 'expecting namespace uri'));
|
|
229
|
+
const valueBuffer = q === 0x27 /* ' */ ? sqValueBuffer : dqValueBuffer;
|
|
230
|
+
const uri = valueBuffer.append(bytes, ++o);
|
|
231
|
+
if (!uri.bytes)
|
|
232
|
+
throw new XmlParserStartTagException(o, 'closing quote for namespace uri');
|
|
233
|
+
const prefix = isDefaultNS ? null : bytes.subarray(nsNameOffset, nsNameEnd);
|
|
234
|
+
resolver.nsMap = resolver.nsMap.registerNS(prefix, uri.bytes);
|
|
235
|
+
o = uri.o + 1;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const attOffset = { nameBegin: o, nameLength: 0, value: EMPTY_SEQUENCE };
|
|
240
|
+
while (o < end && b > 0x20 /* */ && b !== 0x3a /*:*/ && b !== 0x3d /*=*/ && b !== 0x3e /*>*/ && b !== 0x2f /*/*/)
|
|
241
|
+
b = bytes[++o]; // expecting prefix or local name
|
|
242
|
+
if (o >= end)
|
|
243
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
244
|
+
if (bytes[o] === 0x3a /*:*/) {
|
|
245
|
+
attOffset.prefixBegin = attOffset.nameBegin;
|
|
246
|
+
attOffset.prefixLength = o++ - attOffset.prefixBegin;
|
|
247
|
+
attOffset.nameBegin = o;
|
|
248
|
+
b = bytes[o];
|
|
249
|
+
while (o < end && b > 0x20 /* */ && b !== 0x3d /*=*/ && b !== 0x3e /*>*/ && b !== 0x2f /*/*/)
|
|
250
|
+
b = bytes[++o]; // expecting local name
|
|
251
|
+
if (o >= end)
|
|
252
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
253
|
+
attOffset.nameLength = o - attOffset.nameBegin;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
attOffset.nameLength = o - attOffset.nameBegin;
|
|
257
|
+
}
|
|
258
|
+
if (!attOffset.nameLength)
|
|
259
|
+
throwFatalError(new XmlParserStartTagException(o, 'expecting attribute name'));
|
|
260
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
261
|
+
o++; // skip spaces before =
|
|
262
|
+
if (o >= end)
|
|
263
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
264
|
+
if (bytes[o] === 0x3d /*=*/) {
|
|
265
|
+
o++;
|
|
266
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
267
|
+
o++; // skip spaces after =
|
|
268
|
+
if (o >= end)
|
|
269
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
270
|
+
let q = bytes[o];
|
|
271
|
+
if (o >= end)
|
|
272
|
+
throw new XmlParserStartTagException(o, 'attribute value');
|
|
273
|
+
if (q !== 0x22 /* " */ && q !== 0x27 /* ' */)
|
|
274
|
+
throwFatalError(new XmlParserStartTagException(o, 'expecting attribute value'));
|
|
275
|
+
const valueBuffer = q === 0x27 /* ' */ ? sqValueBuffer : dqValueBuffer;
|
|
276
|
+
const attValue = valueBuffer.append(bytes, ++o);
|
|
277
|
+
if (!attValue.bytes)
|
|
278
|
+
throw new XmlParserStartTagException(o, 'closing quote for attribute value');
|
|
279
|
+
attOffset.value = attValue.bytes;
|
|
280
|
+
if (attOffset.nameLength > 0)
|
|
281
|
+
attOffsets.push(attOffset);
|
|
282
|
+
o = attValue.o + 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
while (o < end && (bytes[o] <= 0x20 /* */))
|
|
286
|
+
o++; // skip spaces after attribute
|
|
287
|
+
}
|
|
288
|
+
throw new XmlParserStartTagException(o, 'tag body');
|
|
289
|
+
}
|
|
290
|
+
function throwFatalError(exception) {
|
|
291
|
+
throw new XmlParserError(exception, bytes, seenLength);
|
|
292
|
+
}
|
|
293
|
+
function resolveOnStart(tagNameOffset, attOffsets, hasContent) {
|
|
294
|
+
const nsMap = resolver.nsMap;
|
|
295
|
+
const namespace = tagNameOffset.prefixLength !== undefined ? nsMap.resolveNamespace(bytes, tagNameOffset.prefixBegin, tagNameOffset.prefixLength) : nsMap.resolveNamespace(NO_XML_PREFIX, 0, 0);
|
|
296
|
+
const resolvedStartTag = resolver && resolver.childResolver?.find(bytes, tagNameOffset.nameBegin, tagNameOffset.nameLength, namespace);
|
|
297
|
+
if (!resolvedStartTag) {
|
|
298
|
+
if (hasContent) {
|
|
299
|
+
skipContent(1);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
begin = o;
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const { fqName, resolver: childResolver } = resolvedStartTag;
|
|
307
|
+
const attributes = attOffsets?.length && childResolver.attributeResolvers ? resolveAttributes(nsMap, childResolver.attributeResolvers, attOffsets) : NO_ATTRIBUTES;
|
|
308
|
+
const skipNode = childResolver.onStart?.call(context, attributes, fqName, hasContent);
|
|
309
|
+
if (skipNode) {
|
|
310
|
+
skipContent(1);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!hasContent) {
|
|
314
|
+
resolveOnEnd(childResolver);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
resolverStack.push(resolver);
|
|
318
|
+
childResolver.nsMap = resolver.nsMap;
|
|
319
|
+
resolver = childResolver;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function resolveAttributes(nsMap, attributeNames, attOffsets) {
|
|
323
|
+
const attributes = {};
|
|
324
|
+
for (let j = 0; j < attOffsets.length; j++) {
|
|
325
|
+
const attOffset = attOffsets[j];
|
|
326
|
+
const namespace = attOffset.prefixLength !== undefined ? nsMap.resolveNamespace(bytes, attOffset.prefixBegin, attOffset.prefixLength) : PREDEFINED_XML_NAMESPACE;
|
|
327
|
+
const fqName = attributeNames.find(bytes, attOffset.nameBegin, attOffset.nameLength, namespace);
|
|
328
|
+
if (!fqName)
|
|
329
|
+
continue;
|
|
330
|
+
attributes[fqName.name] = attOffset.value;
|
|
331
|
+
}
|
|
332
|
+
return attributes;
|
|
333
|
+
}
|
|
334
|
+
function scanEndTagClosing() {
|
|
335
|
+
while (o < end && bytes[o] <= 0x20 /* */)
|
|
336
|
+
o++; // skip spaces before tag name
|
|
337
|
+
// should check tag name of closing tag match opening tag?
|
|
338
|
+
while (o < end && bytes[o] !== 0x3e /*>*/)
|
|
339
|
+
o++; // skip all before >
|
|
340
|
+
if (bytes[o] !== 0x3e /*>*/) {
|
|
341
|
+
if (o === end)
|
|
342
|
+
return tailBuffer.saveTail(bytes, begin, end, 0x3e /*>*/);
|
|
343
|
+
throwFatalError(new XmlParserEndTagException(o, 'tag closing'));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function captureEndTagClosing() {
|
|
347
|
+
scanEndTagClosing();
|
|
348
|
+
resolveOnEnd(resolver);
|
|
349
|
+
resolver = resolverStack.pop();
|
|
350
|
+
begin = ++o;
|
|
351
|
+
pendingCaptureFn = null;
|
|
352
|
+
}
|
|
353
|
+
function resolveOnEnd(resolver) {
|
|
354
|
+
const onEnd = resolver?.onEnd;
|
|
355
|
+
if (onEnd) {
|
|
356
|
+
const chunk = onEnd.call(context);
|
|
357
|
+
if (chunk !== undefined) {
|
|
358
|
+
controller.enqueue(chunk);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function tokenizeExclamTag() {
|
|
363
|
+
if (o === end)
|
|
364
|
+
throw new XmlParserStartTagException(o, 'markup declaration');
|
|
365
|
+
let b = bytes[o];
|
|
366
|
+
if (b === 0x2d /*-*/) {
|
|
367
|
+
o++;
|
|
368
|
+
pendingCaptureFn = tokenizeCommentOpening;
|
|
369
|
+
}
|
|
370
|
+
else if (b === 0x5b /*[*/) {
|
|
371
|
+
o++;
|
|
372
|
+
pendingCaptureFn = tokenizeCDATAOpening;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
pendingCaptureFn = skippingDeclaration;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function tokenizeCommentOpening() {
|
|
379
|
+
if (o === end)
|
|
380
|
+
throw new XmlParserCommentException(o, '<!--');
|
|
381
|
+
if (bytes[o] !== 0x2d /*-*/) {
|
|
382
|
+
throwFatalError(new XmlParserCommentException(o, '<!--'));
|
|
383
|
+
}
|
|
384
|
+
begin = ++o;
|
|
385
|
+
pendingCaptureFn = tokenizeComment;
|
|
386
|
+
}
|
|
387
|
+
function tokenizeComment() {
|
|
388
|
+
let b = bytes[o];
|
|
389
|
+
while (o < end && b !== 0x2d /*-*/)
|
|
390
|
+
b = bytes[++o];
|
|
391
|
+
if (o === end)
|
|
392
|
+
throw new XmlParserCommentException(o, '-->');
|
|
393
|
+
if (b !== 0x2d /*-*/ || bytes[o + 1] !== 0x2d /*-*/ || bytes[o + 2] !== 0x3e /*>*/)
|
|
394
|
+
throw new XmlParserCommentException(o, '-->');
|
|
395
|
+
if (resolver && resolver.onComment) {
|
|
396
|
+
resolver.onComment.call(context, bytes.subarray(begin, o));
|
|
397
|
+
}
|
|
398
|
+
o += 3;
|
|
399
|
+
pendingCaptureFn = null;
|
|
400
|
+
}
|
|
401
|
+
function tokenizeCDATAOpening() {
|
|
402
|
+
if (o + 5 >= end)
|
|
403
|
+
return;
|
|
404
|
+
// [0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]) /*CDATA[*/
|
|
405
|
+
if (bytes[o] !== 0x43 /*C*/ || bytes[o + 1] !== 0x44 /*D*/ || bytes[o + 2] !== 0x41 /*A*/ || bytes[o + 3] !== 0x54 /*T*/ || bytes[o + 4] !== 0x41 /*A*/ || bytes[o + 5] !== 0x5b /*[*/) {
|
|
406
|
+
throwFatalError(new XmlParserCDataException(o, '<![CDATA['));
|
|
407
|
+
}
|
|
408
|
+
o += 6;
|
|
409
|
+
begin = o;
|
|
410
|
+
pendingCaptureFn = captureCDATA;
|
|
411
|
+
}
|
|
412
|
+
function captureCDATA() {
|
|
413
|
+
let b = bytes[o];
|
|
414
|
+
while (o < end && (b !== 0x5d /*]*/))
|
|
415
|
+
b = bytes[++o];
|
|
416
|
+
if (o + 2 >= end)
|
|
417
|
+
throw new XmlParserCDataException(o, ']]>');
|
|
418
|
+
if (b !== 0x5d /*]*/ || bytes[o + 1] !== 0x5d /*]*/ || bytes[o + 2] !== 0x3e /*>*/)
|
|
419
|
+
return tailBuffer.saveTail(bytes, begin, end, 0x5d /*]*/);
|
|
420
|
+
pushTextContent(bytes.subarray(begin, o));
|
|
421
|
+
o += 3;
|
|
422
|
+
begin = o;
|
|
423
|
+
pendingCaptureFn = null;
|
|
424
|
+
}
|
|
425
|
+
function captureTextContent() {
|
|
426
|
+
if (resolver.onTextContent) {
|
|
427
|
+
const textContent = textContentBuffer.append(bytes, o);
|
|
428
|
+
o = textContent.o;
|
|
429
|
+
if (textContent.bytes) {
|
|
430
|
+
if (textContent.bytes.length > 0) {
|
|
431
|
+
pushTextContent(textContent.bytes);
|
|
432
|
+
}
|
|
433
|
+
pendingCaptureFn = null;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
pendingCaptureFn = captureTextContentInto;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
pendingCaptureFn = skipTextContent;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function skipContent(openedTag = 1, _pendingSkipFn) {
|
|
444
|
+
const startAt = begin;
|
|
445
|
+
while (o < end && openedTag > 0) {
|
|
446
|
+
let b = bytes[o];
|
|
447
|
+
while (o < end && b !== 0x3c /*<*/)
|
|
448
|
+
b = bytes[++o];
|
|
449
|
+
if (o + 1 === end)
|
|
450
|
+
break;
|
|
451
|
+
b = bytes[++o];
|
|
452
|
+
switch (b) {
|
|
453
|
+
case 0x2f /*/*/:
|
|
454
|
+
b = bytes[++o];
|
|
455
|
+
while (b && b !== 0x3e /*>*/)
|
|
456
|
+
b = bytes[++o]; // skip all before >
|
|
457
|
+
if (bytes[o] === 0x3e /*>*/) {
|
|
458
|
+
begin = ++o;
|
|
459
|
+
--openedTag;
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
462
|
+
case 0x21 /*!*/:
|
|
463
|
+
b = bytes[++o];
|
|
464
|
+
if (b === 0x2d /*-*/) {
|
|
465
|
+
b = bytes[++o];
|
|
466
|
+
while (b && (b !== 0x2d /*-*/ || bytes[o + 1] !== 0x3e /*>*/))
|
|
467
|
+
b = bytes[++o];
|
|
468
|
+
if (bytes[o + 1] === 0x3e /*>*/) {
|
|
469
|
+
begin = o += 2;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (b === 0x5b /*[*/) {
|
|
473
|
+
b = bytes[++o];
|
|
474
|
+
while (b && (b !== 0x5d /*]*/ || bytes[o + 1] !== 0x5d /*]*/ || bytes[o + 2] !== 0x3e /*>*/))
|
|
475
|
+
b = bytes[++o];
|
|
476
|
+
if (bytes[o + 2] === 0x3e /*>*/) {
|
|
477
|
+
begin = o += 3;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
let openQuote;
|
|
482
|
+
b = bytes[++o];
|
|
483
|
+
while (o < end) {
|
|
484
|
+
if (openQuote && b === openQuote) {
|
|
485
|
+
openQuote = undefined;
|
|
486
|
+
}
|
|
487
|
+
else if (b === 0x22 /* " */ || b === 0x27 /* ' */) {
|
|
488
|
+
openQuote = b;
|
|
489
|
+
}
|
|
490
|
+
else if (!openQuote && b === 0x3e /*>*/) {
|
|
491
|
+
begin = ++o;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
b = bytes[++o];
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
case 0x3f /*?*/:
|
|
499
|
+
b = bytes[++o];
|
|
500
|
+
while (b && (b !== 0x3f /*?*/ || bytes[o + 1] !== 0x3e /*>*/))
|
|
501
|
+
b = bytes[++o];
|
|
502
|
+
if (bytes[o + 1] === 0x3e /*>*/) {
|
|
503
|
+
begin = o += 2;
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
default:
|
|
507
|
+
openedTag = skipTagOpening(openedTag);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (begin === startAt) {
|
|
511
|
+
tailBuffer.saveTail(bytes, begin, end);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
tailBuffer.reset();
|
|
515
|
+
}
|
|
516
|
+
if (openedTag === 0) {
|
|
517
|
+
pendingCaptureFn = null;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
pendingCaptureFn = skipContent.bind(null, openedTag);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function skipTagOpening(openedTag, openQuote) {
|
|
524
|
+
while (o < end) {
|
|
525
|
+
const b = bytes[o];
|
|
526
|
+
if (openQuote) {
|
|
527
|
+
if (b === openQuote)
|
|
528
|
+
openQuote = undefined;
|
|
529
|
+
o++;
|
|
530
|
+
}
|
|
531
|
+
else if (b === 0x22 /* " */ || b === 0x27 /* ' */) {
|
|
532
|
+
openQuote = b;
|
|
533
|
+
o++;
|
|
534
|
+
}
|
|
535
|
+
else if (b === 0x3e /* > */) {
|
|
536
|
+
let previous = o - 1;
|
|
537
|
+
while (previous > begin && bytes[previous] <= 0x20 /* */)
|
|
538
|
+
previous--;
|
|
539
|
+
if (bytes[previous] !== 0x2f /*/*/) {
|
|
540
|
+
openedTag++;
|
|
541
|
+
}
|
|
542
|
+
begin = o += 1;
|
|
543
|
+
return openedTag;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
o++;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return openedTag;
|
|
550
|
+
}
|
|
551
|
+
function skipTextContent() {
|
|
552
|
+
let b = bytes[begin];
|
|
553
|
+
o = begin;
|
|
554
|
+
while (o < end && b !== 0x3c /*<*/)
|
|
555
|
+
b = bytes[++o];
|
|
556
|
+
if (b === 0x3c) {
|
|
557
|
+
pendingCaptureFn = null;
|
|
558
|
+
}
|
|
559
|
+
else if (o >= end) {
|
|
560
|
+
pendingCaptureFn = null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function captureTextContentInto() {
|
|
564
|
+
let b = bytes[begin];
|
|
565
|
+
o = begin;
|
|
566
|
+
while (b && b !== 0x3c /*<*/)
|
|
567
|
+
b = bytes[++o];
|
|
568
|
+
const textContent = textContentBuffer.append(bytes, begin);
|
|
569
|
+
o = textContent.o;
|
|
570
|
+
if (textContent.bytes) {
|
|
571
|
+
pushTextContent(textContent.bytes);
|
|
572
|
+
pendingCaptureFn = null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function pushTextContent(textContent) {
|
|
576
|
+
if (resolver.onTextContent) {
|
|
577
|
+
resolver.onTextContent.call(context, textContent);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function skippingQuestionTag() {
|
|
581
|
+
let b = bytes[o];
|
|
582
|
+
while (o < end && (b !== 0x3f /*?*/ || bytes[o + 1] !== 0x3e /*>*/))
|
|
583
|
+
b = bytes[++o];
|
|
584
|
+
if (o + 1 >= end) {
|
|
585
|
+
pendingCaptureFn = skippingQuestionTag;
|
|
586
|
+
return pendingCaptureFn;
|
|
587
|
+
}
|
|
588
|
+
begin = o += 2;
|
|
589
|
+
pendingCaptureFn = null;
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
function skippingDeclaration(openQuote, openBrackets = 0) {
|
|
593
|
+
let b = bytes[o];
|
|
594
|
+
while (o < end) {
|
|
595
|
+
if (openQuote && b === openQuote) {
|
|
596
|
+
openQuote = undefined;
|
|
597
|
+
}
|
|
598
|
+
else if (b === 0x22 /* " */ || b === 0x27 /* ' */) {
|
|
599
|
+
openQuote = b;
|
|
600
|
+
}
|
|
601
|
+
else if (b === 0x5b /* [ */) {
|
|
602
|
+
openBrackets++;
|
|
603
|
+
}
|
|
604
|
+
else if (b === 0x5d /* ] */) {
|
|
605
|
+
if (openBrackets > 0) {
|
|
606
|
+
openBrackets--;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else if (b === 0x3e /* > */ && openBrackets === 0) {
|
|
610
|
+
// skip it all
|
|
611
|
+
o += 1;
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
b = bytes[++o];
|
|
615
|
+
}
|
|
616
|
+
return skippingDeclaration.bind(null, openQuote, openBrackets);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const DEFAULT_TAIL_BUFFER_CAPACITY = 1024; // 1 KB
|
|
620
|
+
export class TailBuffer {
|
|
621
|
+
bytes;
|
|
622
|
+
end;
|
|
623
|
+
tailByte;
|
|
624
|
+
extendCapacityBy;
|
|
625
|
+
constructor(extendCapacityBy) {
|
|
626
|
+
this.extendCapacityBy = extendCapacityBy ?? DEFAULT_TAIL_BUFFER_CAPACITY;
|
|
627
|
+
this.bytes = new Uint8Array(0);
|
|
628
|
+
this.end = 0;
|
|
629
|
+
this.tailByte = 0;
|
|
630
|
+
}
|
|
631
|
+
addChunk(bytes) {
|
|
632
|
+
if (this.end === 0) {
|
|
633
|
+
return { front: null, tail: bytes };
|
|
634
|
+
}
|
|
635
|
+
if (!this.tailByte) {
|
|
636
|
+
this.appendBytes(bytes, bytes.length);
|
|
637
|
+
const tail = this.bytes.slice(0, this.end);
|
|
638
|
+
this.reset();
|
|
639
|
+
return { front: null, tail };
|
|
640
|
+
}
|
|
641
|
+
let i = 0;
|
|
642
|
+
let b = bytes[i];
|
|
643
|
+
const tailByte = this.tailByte;
|
|
644
|
+
const end = bytes.length;
|
|
645
|
+
while (i < end && b !== tailByte)
|
|
646
|
+
b = bytes[++i];
|
|
647
|
+
if (b === tailByte) {
|
|
648
|
+
this.appendBytes(bytes, i + 1);
|
|
649
|
+
const front = this.bytes.subarray(0, this.end);
|
|
650
|
+
this.reset();
|
|
651
|
+
return { front, tail: bytes.subarray(i + 1, bytes.length) };
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
this.appendBytes(bytes, i);
|
|
655
|
+
return { front: null, tail: null };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
saveTail(source, begin, end, tailByte = this.tailByte) {
|
|
659
|
+
const length = end - begin;
|
|
660
|
+
if (length > this.bytes.length) {
|
|
661
|
+
while (this.extendCapacityBy < length)
|
|
662
|
+
this.extendCapacityBy *= 2;
|
|
663
|
+
this.bytes = new Uint8Array(this.extendCapacityBy);
|
|
664
|
+
}
|
|
665
|
+
this.bytes.set(source.subarray(begin, end), 0);
|
|
666
|
+
this.end = end - begin;
|
|
667
|
+
this.tailByte = tailByte;
|
|
668
|
+
}
|
|
669
|
+
reset() {
|
|
670
|
+
this.tailByte = 0;
|
|
671
|
+
this.end = 0;
|
|
672
|
+
}
|
|
673
|
+
appendBytes(bytes, length) {
|
|
674
|
+
if (length === 0)
|
|
675
|
+
return this;
|
|
676
|
+
const minCapacity = this.end + length;
|
|
677
|
+
if (minCapacity > this.bytes.length) {
|
|
678
|
+
while (this.extendCapacityBy < minCapacity)
|
|
679
|
+
this.extendCapacityBy *= 2;
|
|
680
|
+
const next = new Uint8Array(this.extendCapacityBy);
|
|
681
|
+
next.set(this.bytes.subarray(0, this.end));
|
|
682
|
+
this.bytes = next;
|
|
683
|
+
}
|
|
684
|
+
this.bytes.set(bytes.subarray(0, length), this.end);
|
|
685
|
+
this.end += length;
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
class XmlParserError extends Error {
|
|
690
|
+
constructor(exception, bytes, seenLength) {
|
|
691
|
+
const preContext = bytes.subarray(Math.max(0, exception.position - 10), exception.position);
|
|
692
|
+
const postContext = bytes.subarray(exception.position, Math.min(bytes.length, exception.position + 10));
|
|
693
|
+
const contextStr = new TextDecoder().decode(preContext) + 'ꞈ' + new TextDecoder().decode(postContext);
|
|
694
|
+
const locationName = exception.location ? ` in ${exception.location}` : '';
|
|
695
|
+
const message = `Error in ${locationName} ${exception.message ?? ''} (at position ${exception.position + seenLength}): ${contextStr}`;
|
|
696
|
+
super(message);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
class XmlParserException extends Error {
|
|
700
|
+
position;
|
|
701
|
+
location;
|
|
702
|
+
tailByte = 0;
|
|
703
|
+
constructor(position, message, location) {
|
|
704
|
+
super(message);
|
|
705
|
+
this.position = position;
|
|
706
|
+
this.location = location;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
class XmlParserStartTagException extends XmlParserException {
|
|
710
|
+
tailByte = 0x3e; /*>*/
|
|
711
|
+
constructor(position, expecting) {
|
|
712
|
+
super(position, `Expecting ${expecting}`, 'start');
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
class XmlParserEndTagException extends XmlParserException {
|
|
716
|
+
tailByte = 0x3e; /*>*/
|
|
717
|
+
constructor(position, expecting) {
|
|
718
|
+
super(position, `Expecting ${expecting}`, 'end');
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
class XmlParserCommentException extends XmlParserException {
|
|
722
|
+
tailByte = 0x2d; /*-*/
|
|
723
|
+
constructor(position, expecting) {
|
|
724
|
+
super(position, `Expecting ${expecting}`, 'comment');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
class XmlParserCDataException extends XmlParserException {
|
|
728
|
+
tailByte = 0x5d; /*]*/
|
|
729
|
+
constructor(position, expecting) {
|
|
730
|
+
super(position, `Expecting ${expecting}`, 'cdata');
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function createTextBuffer(upTo /* ' */, expandCapacityBy = 1024) {
|
|
734
|
+
let at = 0;
|
|
735
|
+
let flushedAt = 0;
|
|
736
|
+
let byteBuffer = new Uint8Array(expandCapacityBy);
|
|
737
|
+
let pendingCR = false;
|
|
738
|
+
const utf8Buffer = {
|
|
739
|
+
append(bytesToAppend, begin) {
|
|
740
|
+
let o = begin;
|
|
741
|
+
if (pendingCR) {
|
|
742
|
+
if (bytesToAppend[o] === 0x0a /*\n*/)
|
|
743
|
+
o++;
|
|
744
|
+
checkCapacity(1);
|
|
745
|
+
byteBuffer[at++] = 0x0a; /*\n*/
|
|
746
|
+
begin = o;
|
|
747
|
+
pendingCR = false;
|
|
748
|
+
}
|
|
749
|
+
while (o < bytesToAppend.length) {
|
|
750
|
+
const b = bytesToAppend[o];
|
|
751
|
+
if (b === upTo) {
|
|
752
|
+
if (at > flushedAt) {
|
|
753
|
+
appendBytes(bytesToAppend, begin, o);
|
|
754
|
+
const textContent = byteBuffer.slice(flushedAt, at);
|
|
755
|
+
flushedAt = at;
|
|
756
|
+
return { bytes: textContent, o };
|
|
757
|
+
}
|
|
758
|
+
return { bytes: bytesToAppend.subarray(begin, o), o };
|
|
759
|
+
}
|
|
760
|
+
if (b === 0x0d /*\r*/) {
|
|
761
|
+
appendBytes(bytesToAppend, begin, o);
|
|
762
|
+
if (o + 1 >= bytesToAppend.length) {
|
|
763
|
+
pendingCR = true;
|
|
764
|
+
return { o: o + 1 };
|
|
765
|
+
}
|
|
766
|
+
if (bytesToAppend[o + 1] === 0x0a /*\n*/) {
|
|
767
|
+
o += 2;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
o++;
|
|
771
|
+
}
|
|
772
|
+
checkCapacity(1);
|
|
773
|
+
byteBuffer[at++] = 0x0a; /*\n*/
|
|
774
|
+
begin = o;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
o++;
|
|
778
|
+
}
|
|
779
|
+
appendBytes(bytesToAppend, begin, o);
|
|
780
|
+
return { o };
|
|
781
|
+
},
|
|
782
|
+
reset() {
|
|
783
|
+
at = 0;
|
|
784
|
+
flushedAt = 0;
|
|
785
|
+
pendingCR = false;
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
return utf8Buffer;
|
|
789
|
+
function appendBytes(bytesToAppend, begin, end) {
|
|
790
|
+
const addLength = end - begin;
|
|
791
|
+
checkCapacity(addLength);
|
|
792
|
+
byteBuffer.set(bytesToAppend.subarray(begin, end), at);
|
|
793
|
+
at += addLength;
|
|
794
|
+
}
|
|
795
|
+
function checkCapacity(addLength) {
|
|
796
|
+
const requiredCapacity = at + addLength;
|
|
797
|
+
if (requiredCapacity > byteBuffer.length && flushedAt > 0) {
|
|
798
|
+
byteBuffer.copyWithin(0, flushedAt, at);
|
|
799
|
+
at -= flushedAt;
|
|
800
|
+
flushedAt = 0;
|
|
801
|
+
}
|
|
802
|
+
if (requiredCapacity <= byteBuffer.length)
|
|
803
|
+
return;
|
|
804
|
+
while (byteBuffer.length + expandCapacityBy < requiredCapacity)
|
|
805
|
+
expandCapacityBy *= 2;
|
|
806
|
+
const next = new Uint8Array(byteBuffer.length + expandCapacityBy);
|
|
807
|
+
next.set(byteBuffer, 0);
|
|
808
|
+
byteBuffer = next;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
//# sourceMappingURL=parser.js.map
|