xml-sax-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 +162 -0
- package/dist/index.cjs +724 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +119 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +718 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
// src/assert.ts
|
|
2
|
+
var DEV = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
3
|
+
function assert(condition, message) {
|
|
4
|
+
if (!DEV) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (!condition) {
|
|
8
|
+
throw new Error(`Invariant failed: ${message}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/errors.ts
|
|
13
|
+
var XmlSaxError = class extends Error {
|
|
14
|
+
constructor(message, offset, line, column) {
|
|
15
|
+
super(`${message} at ${line}:${column}`);
|
|
16
|
+
this.name = "XmlSaxError";
|
|
17
|
+
this.offset = offset;
|
|
18
|
+
this.line = line;
|
|
19
|
+
this.column = column;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/entities.ts
|
|
24
|
+
var NAMED_ENTITIES = {
|
|
25
|
+
lt: "<",
|
|
26
|
+
gt: ">",
|
|
27
|
+
amp: "&",
|
|
28
|
+
quot: '"',
|
|
29
|
+
apos: "'"
|
|
30
|
+
};
|
|
31
|
+
function decodeEntities(input, onError) {
|
|
32
|
+
let output = "";
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < input.length) {
|
|
35
|
+
const ch = input[i];
|
|
36
|
+
if (ch !== "&") {
|
|
37
|
+
output += ch;
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const semi = input.indexOf(";", i + 1);
|
|
42
|
+
if (semi === -1) {
|
|
43
|
+
const err = new XmlSaxError("Unterminated entity", i, 0, 0);
|
|
44
|
+
onError?.(err);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
const entity = input.slice(i + 1, semi);
|
|
48
|
+
let decoded;
|
|
49
|
+
if (entity.startsWith("#x") || entity.startsWith("#X")) {
|
|
50
|
+
const codePoint = Number.parseInt(entity.slice(2), 16);
|
|
51
|
+
decoded = decodeCodePoint(codePoint);
|
|
52
|
+
} else if (entity.startsWith("#")) {
|
|
53
|
+
const codePoint = Number.parseInt(entity.slice(1), 10);
|
|
54
|
+
decoded = decodeCodePoint(codePoint);
|
|
55
|
+
} else {
|
|
56
|
+
decoded = NAMED_ENTITIES[entity];
|
|
57
|
+
}
|
|
58
|
+
if (decoded === void 0) {
|
|
59
|
+
const err = new XmlSaxError(`Unknown entity: &${entity};`, i, 0, 0);
|
|
60
|
+
onError?.(err);
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
output += decoded;
|
|
64
|
+
i = semi + 1;
|
|
65
|
+
}
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
function decodeCodePoint(codePoint) {
|
|
69
|
+
if (!Number.isFinite(codePoint)) {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
if (codePoint < 0 || codePoint > 1114111) {
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
if (codePoint >= 55296 && codePoint <= 57343) {
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
return String.fromCodePoint(codePoint);
|
|
79
|
+
}
|
|
80
|
+
function splitTextForEntities(text) {
|
|
81
|
+
const lastAmp = text.lastIndexOf("&");
|
|
82
|
+
if (lastAmp === -1) {
|
|
83
|
+
return { emit: text, carry: "" };
|
|
84
|
+
}
|
|
85
|
+
const nextSemi = text.indexOf(";", lastAmp + 1);
|
|
86
|
+
if (nextSemi === -1) {
|
|
87
|
+
return {
|
|
88
|
+
emit: text.slice(0, lastAmp),
|
|
89
|
+
carry: text.slice(lastAmp)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return { emit: text, carry: "" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/parser.ts
|
|
96
|
+
var DEFAULT_OPTIONS = {
|
|
97
|
+
xmlns: true,
|
|
98
|
+
includeNamespaceAttributes: false,
|
|
99
|
+
allowDoctype: true
|
|
100
|
+
};
|
|
101
|
+
var XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
|
|
102
|
+
var XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
|
|
103
|
+
var XmlSaxParser = class {
|
|
104
|
+
constructor(options = {}) {
|
|
105
|
+
this.buffer = "";
|
|
106
|
+
this.offset = 0;
|
|
107
|
+
this.line = 1;
|
|
108
|
+
this.column = 1;
|
|
109
|
+
this.elementStack = [];
|
|
110
|
+
this.nsStack = [
|
|
111
|
+
Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
112
|
+
xml: XML_NAMESPACE_URI,
|
|
113
|
+
xmlns: XMLNS_NAMESPACE_URI
|
|
114
|
+
})
|
|
115
|
+
];
|
|
116
|
+
this.closed = false;
|
|
117
|
+
this.pendingCR = false;
|
|
118
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
119
|
+
}
|
|
120
|
+
feed(chunk) {
|
|
121
|
+
if (this.closed) {
|
|
122
|
+
this._error("Parser is closed");
|
|
123
|
+
}
|
|
124
|
+
if (!chunk) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.buffer += chunk;
|
|
128
|
+
this._parseBuffer(false);
|
|
129
|
+
}
|
|
130
|
+
close() {
|
|
131
|
+
if (this.closed) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this._parseBuffer(true);
|
|
135
|
+
this._flushPendingCR();
|
|
136
|
+
if (this.buffer.length > 0) {
|
|
137
|
+
this._error("Unexpected end of input");
|
|
138
|
+
}
|
|
139
|
+
if (this.elementStack.length > 0) {
|
|
140
|
+
this._error("Unclosed tag(s) remaining");
|
|
141
|
+
}
|
|
142
|
+
this.closed = true;
|
|
143
|
+
}
|
|
144
|
+
_parseBuffer(final) {
|
|
145
|
+
let i = 0;
|
|
146
|
+
while (i < this.buffer.length) {
|
|
147
|
+
const lt = this.buffer.indexOf("<", i);
|
|
148
|
+
if (lt === -1) {
|
|
149
|
+
const tail = this.buffer.slice(i);
|
|
150
|
+
const split = splitTextForEntities(tail);
|
|
151
|
+
if (split.emit.length > 0) {
|
|
152
|
+
this._emitText(split.emit, true);
|
|
153
|
+
this._advance(split.emit);
|
|
154
|
+
}
|
|
155
|
+
this.buffer = split.carry;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (lt > i) {
|
|
159
|
+
const text = this.buffer.slice(i, lt);
|
|
160
|
+
if (text.length > 0) {
|
|
161
|
+
this._emitText(text, false);
|
|
162
|
+
this._advance(text);
|
|
163
|
+
}
|
|
164
|
+
i = lt;
|
|
165
|
+
}
|
|
166
|
+
const consumed = this._parseMarkupFrom(lt, final);
|
|
167
|
+
if (consumed === null) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const markup = this.buffer.slice(lt, lt + consumed);
|
|
171
|
+
this._advance(markup);
|
|
172
|
+
i = lt + consumed;
|
|
173
|
+
}
|
|
174
|
+
this.buffer = this.buffer.slice(i);
|
|
175
|
+
if (final && this.buffer.length > 0) {
|
|
176
|
+
this._error("Unexpected end of input");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
_parseMarkupFrom(start, final) {
|
|
180
|
+
assert(this.buffer[start] === "<", "Markup must start with '<'");
|
|
181
|
+
this._flushPendingCR();
|
|
182
|
+
if (this.buffer.startsWith("<!--", start)) {
|
|
183
|
+
const end = this.buffer.indexOf("-->", start + 4);
|
|
184
|
+
if (end === -1) {
|
|
185
|
+
if (final) {
|
|
186
|
+
this._error("Unterminated comment");
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const comment = this.buffer.slice(start + 4, end);
|
|
191
|
+
this.options.onComment?.(comment);
|
|
192
|
+
return end + 3 - start;
|
|
193
|
+
}
|
|
194
|
+
if (this.buffer.startsWith("<![CDATA[", start)) {
|
|
195
|
+
const end = this.buffer.indexOf("]]>", start + 9);
|
|
196
|
+
if (end === -1) {
|
|
197
|
+
if (final) {
|
|
198
|
+
this._error("Unterminated CDATA section");
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const cdata = this.buffer.slice(start + 9, end);
|
|
203
|
+
const normalized = this._normalizeText(cdata, false);
|
|
204
|
+
if (normalized.length > 0) {
|
|
205
|
+
this.options.onCdata?.(normalized);
|
|
206
|
+
}
|
|
207
|
+
return end + 3 - start;
|
|
208
|
+
}
|
|
209
|
+
if (this.buffer.startsWith("<?", start)) {
|
|
210
|
+
const end = this.buffer.indexOf("?>", start + 2);
|
|
211
|
+
if (end === -1) {
|
|
212
|
+
if (final) {
|
|
213
|
+
this._error("Unterminated processing instruction");
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const body = this.buffer.slice(start + 2, end).trim();
|
|
218
|
+
const split = body.search(/\s/);
|
|
219
|
+
const target = split === -1 ? body : body.slice(0, split);
|
|
220
|
+
const data = split === -1 ? "" : body.slice(split).trim();
|
|
221
|
+
const pi = { target, body: data };
|
|
222
|
+
this.options.onProcessingInstruction?.(pi);
|
|
223
|
+
return end + 2 - start;
|
|
224
|
+
}
|
|
225
|
+
if (this.buffer.startsWith("<!DOCTYPE", start)) {
|
|
226
|
+
const end = this._findDoctypeEnd(start + 9);
|
|
227
|
+
if (end === -1) {
|
|
228
|
+
if (final) {
|
|
229
|
+
this._error("Unterminated doctype declaration");
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (!this.options.allowDoctype) {
|
|
234
|
+
this._error("Doctype is not allowed");
|
|
235
|
+
}
|
|
236
|
+
const raw = this.buffer.slice(start + 9, end).trim();
|
|
237
|
+
const doctype = { raw };
|
|
238
|
+
this.options.onDoctype?.(doctype);
|
|
239
|
+
return end + 1 - start;
|
|
240
|
+
}
|
|
241
|
+
if (this.buffer.startsWith("</", start)) {
|
|
242
|
+
const end = this.buffer.indexOf(">", start + 2);
|
|
243
|
+
if (end === -1) {
|
|
244
|
+
if (final) {
|
|
245
|
+
this._error("Unterminated closing tag");
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const raw = this.buffer.slice(start + 2, end).trim();
|
|
250
|
+
const parsed = this._parseName(raw, 0, raw.length);
|
|
251
|
+
if (raw.slice(parsed.end).trim().length > 0) {
|
|
252
|
+
this._error("Invalid closing tag");
|
|
253
|
+
}
|
|
254
|
+
this._handleCloseTag(parsed.name);
|
|
255
|
+
return end + 1 - start;
|
|
256
|
+
}
|
|
257
|
+
const tagEnd = this._findTagEnd(start + 1);
|
|
258
|
+
if (tagEnd === -1) {
|
|
259
|
+
if (final) {
|
|
260
|
+
this._error("Unterminated start tag");
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const content = this.buffer.slice(start + 1, tagEnd);
|
|
265
|
+
this._handleStartTag(content);
|
|
266
|
+
return tagEnd + 1 - start;
|
|
267
|
+
}
|
|
268
|
+
_handleStartTag(content) {
|
|
269
|
+
const trimmed = content.trim();
|
|
270
|
+
const selfClosing = trimmed.endsWith("/");
|
|
271
|
+
const body = selfClosing ? trimmed.slice(0, -1).trim() : trimmed;
|
|
272
|
+
const parsed = this._parseTagBody(body);
|
|
273
|
+
let ns = this._currentNs();
|
|
274
|
+
if (this.options.xmlns) {
|
|
275
|
+
ns = Object.create(ns);
|
|
276
|
+
for (const attr of parsed.attributes) {
|
|
277
|
+
if (attr.name === "xmlns") {
|
|
278
|
+
ns[""] = attr.value;
|
|
279
|
+
} else if (attr.name.startsWith("xmlns:")) {
|
|
280
|
+
ns[attr.name.slice(6)] = attr.value;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const resolvedName = this._resolveName(parsed.name, ns);
|
|
285
|
+
const attributes = {};
|
|
286
|
+
for (const attr of parsed.attributes) {
|
|
287
|
+
if (this.options.xmlns && !this.options.includeNamespaceAttributes) {
|
|
288
|
+
if (attr.name === "xmlns" || attr.name.startsWith("xmlns:")) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const resolvedAttr = this._resolveAttributeName(attr.name, ns);
|
|
293
|
+
attributes[resolvedAttr.name] = {
|
|
294
|
+
name: resolvedAttr.name,
|
|
295
|
+
value: attr.value,
|
|
296
|
+
prefix: resolvedAttr.prefix,
|
|
297
|
+
local: resolvedAttr.local,
|
|
298
|
+
uri: resolvedAttr.uri
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const tag = {
|
|
302
|
+
name: resolvedName.name,
|
|
303
|
+
prefix: resolvedName.prefix,
|
|
304
|
+
local: resolvedName.local,
|
|
305
|
+
uri: resolvedName.uri,
|
|
306
|
+
attributes,
|
|
307
|
+
isSelfClosing: selfClosing
|
|
308
|
+
};
|
|
309
|
+
this.options.onOpenTag?.(tag);
|
|
310
|
+
if (selfClosing) {
|
|
311
|
+
const closeTag = {
|
|
312
|
+
name: resolvedName.name,
|
|
313
|
+
prefix: resolvedName.prefix,
|
|
314
|
+
local: resolvedName.local,
|
|
315
|
+
uri: resolvedName.uri
|
|
316
|
+
};
|
|
317
|
+
this.options.onCloseTag?.(closeTag);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.elementStack.push({ rawName: parsed.name, resolved: resolvedName, ns });
|
|
321
|
+
this.nsStack.push(ns);
|
|
322
|
+
}
|
|
323
|
+
_handleCloseTag(rawName) {
|
|
324
|
+
const entry = this.elementStack.pop();
|
|
325
|
+
const ns = this.nsStack.pop();
|
|
326
|
+
if (!entry || !ns) {
|
|
327
|
+
this._error("Closing tag without matching start tag");
|
|
328
|
+
}
|
|
329
|
+
if (entry.rawName !== rawName) {
|
|
330
|
+
this._error(`Mismatched closing tag: expected </${entry.rawName}>`);
|
|
331
|
+
}
|
|
332
|
+
const closeTag = {
|
|
333
|
+
name: entry.resolved.name,
|
|
334
|
+
prefix: entry.resolved.prefix,
|
|
335
|
+
local: entry.resolved.local,
|
|
336
|
+
uri: entry.resolved.uri
|
|
337
|
+
};
|
|
338
|
+
this.options.onCloseTag?.(closeTag);
|
|
339
|
+
}
|
|
340
|
+
_parseTagBody(body) {
|
|
341
|
+
let i = 0;
|
|
342
|
+
const length = body.length;
|
|
343
|
+
i = this._skipWhitespace(body, i, length);
|
|
344
|
+
const parsedName = this._parseName(body, i, length);
|
|
345
|
+
i = parsedName.end;
|
|
346
|
+
const attributes = [];
|
|
347
|
+
while (i < length) {
|
|
348
|
+
i = this._skipWhitespace(body, i, length);
|
|
349
|
+
if (i >= length) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
const attrName = this._parseName(body, i, length);
|
|
353
|
+
i = attrName.end;
|
|
354
|
+
i = this._skipWhitespace(body, i, length);
|
|
355
|
+
if (body[i] !== "=") {
|
|
356
|
+
this._error("Attribute without '='");
|
|
357
|
+
}
|
|
358
|
+
i += 1;
|
|
359
|
+
i = this._skipWhitespace(body, i, length);
|
|
360
|
+
const quote = body[i];
|
|
361
|
+
if (quote !== '"' && quote !== "'") {
|
|
362
|
+
this._error("Attribute value must be quoted");
|
|
363
|
+
}
|
|
364
|
+
i += 1;
|
|
365
|
+
const valueEnd = body.indexOf(quote, i);
|
|
366
|
+
if (valueEnd === -1) {
|
|
367
|
+
this._error("Unterminated attribute value");
|
|
368
|
+
}
|
|
369
|
+
const rawValue = body.slice(i, valueEnd);
|
|
370
|
+
const normalized = rawValue.replace(/\r\n?/g, "\n");
|
|
371
|
+
const value = decodeEntities(normalized, this.options.onError);
|
|
372
|
+
attributes.push({ name: attrName.name, value });
|
|
373
|
+
i = valueEnd + 1;
|
|
374
|
+
}
|
|
375
|
+
return { name: parsedName.name, attributes };
|
|
376
|
+
}
|
|
377
|
+
_emitText(text, allowPendingCR) {
|
|
378
|
+
const normalized = this._normalizeText(text, allowPendingCR);
|
|
379
|
+
if (normalized.length === 0) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const decoded = decodeEntities(normalized, this.options.onError);
|
|
383
|
+
if (decoded.length > 0) {
|
|
384
|
+
this.options.onText?.(decoded);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
_resolveName(rawName, ns) {
|
|
388
|
+
if (!this.options.xmlns) {
|
|
389
|
+
const split2 = rawName.indexOf(":");
|
|
390
|
+
if (split2 === -1) {
|
|
391
|
+
return { name: rawName, prefix: "", local: rawName, uri: "" };
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
name: rawName,
|
|
395
|
+
prefix: rawName.slice(0, split2),
|
|
396
|
+
local: rawName.slice(split2 + 1),
|
|
397
|
+
uri: ""
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const split = rawName.indexOf(":");
|
|
401
|
+
if (split === -1) {
|
|
402
|
+
return {
|
|
403
|
+
name: rawName,
|
|
404
|
+
prefix: "",
|
|
405
|
+
local: rawName,
|
|
406
|
+
uri: ns[""] ?? ""
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const prefix = rawName.slice(0, split);
|
|
410
|
+
const local = rawName.slice(split + 1);
|
|
411
|
+
const uri = ns[prefix];
|
|
412
|
+
if (uri === void 0) {
|
|
413
|
+
this._error(`Undeclared namespace prefix: ${prefix}`);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
name: rawName,
|
|
417
|
+
prefix,
|
|
418
|
+
local,
|
|
419
|
+
uri
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
_resolveAttributeName(rawName, ns) {
|
|
423
|
+
if (!this.options.xmlns) {
|
|
424
|
+
return this._resolveName(rawName, ns);
|
|
425
|
+
}
|
|
426
|
+
if (rawName === "xmlns") {
|
|
427
|
+
return {
|
|
428
|
+
name: rawName,
|
|
429
|
+
prefix: "",
|
|
430
|
+
local: rawName,
|
|
431
|
+
uri: ns.xmlns ?? XMLNS_NAMESPACE_URI
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const split = rawName.indexOf(":");
|
|
435
|
+
if (split === -1) {
|
|
436
|
+
return {
|
|
437
|
+
name: rawName,
|
|
438
|
+
prefix: "",
|
|
439
|
+
local: rawName,
|
|
440
|
+
uri: ""
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const prefix = rawName.slice(0, split);
|
|
444
|
+
const local = rawName.slice(split + 1);
|
|
445
|
+
const uri = ns[prefix];
|
|
446
|
+
if (uri === void 0) {
|
|
447
|
+
this._error(`Undeclared namespace prefix: ${prefix}`);
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
name: rawName,
|
|
451
|
+
prefix,
|
|
452
|
+
local,
|
|
453
|
+
uri
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
_findTagEnd(start) {
|
|
457
|
+
let quote = null;
|
|
458
|
+
for (let i = start; i < this.buffer.length; i += 1) {
|
|
459
|
+
const ch = this.buffer[i];
|
|
460
|
+
if (quote) {
|
|
461
|
+
if (ch === quote) {
|
|
462
|
+
quote = null;
|
|
463
|
+
}
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (ch === '"' || ch === "'") {
|
|
467
|
+
quote = ch;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (ch === ">") {
|
|
471
|
+
return i;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return -1;
|
|
475
|
+
}
|
|
476
|
+
_findDoctypeEnd(start) {
|
|
477
|
+
let quote = null;
|
|
478
|
+
let bracketDepth = 0;
|
|
479
|
+
for (let i = start; i < this.buffer.length; i += 1) {
|
|
480
|
+
const ch = this.buffer[i];
|
|
481
|
+
if (quote) {
|
|
482
|
+
if (ch === quote) {
|
|
483
|
+
quote = null;
|
|
484
|
+
}
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (ch === '"' || ch === "'") {
|
|
488
|
+
quote = ch;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (ch === "[") {
|
|
492
|
+
bracketDepth += 1;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (ch === "]") {
|
|
496
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (ch === ">" && bracketDepth === 0) {
|
|
500
|
+
return i;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return -1;
|
|
504
|
+
}
|
|
505
|
+
_parseName(input, start, end) {
|
|
506
|
+
if (start >= end) {
|
|
507
|
+
this._error("Expected name");
|
|
508
|
+
}
|
|
509
|
+
const first = input[start];
|
|
510
|
+
if (first === void 0) {
|
|
511
|
+
this._error("Expected name");
|
|
512
|
+
}
|
|
513
|
+
if (!this._isNameStart(first)) {
|
|
514
|
+
this._error(`Invalid name start: '${first}'`);
|
|
515
|
+
}
|
|
516
|
+
let i = start + 1;
|
|
517
|
+
while (i < end) {
|
|
518
|
+
const ch = input[i];
|
|
519
|
+
if (ch === void 0 || !this._isNameChar(ch)) {
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
i += 1;
|
|
523
|
+
}
|
|
524
|
+
return { name: input.slice(start, i), end: i };
|
|
525
|
+
}
|
|
526
|
+
_isNameStart(ch) {
|
|
527
|
+
return /[A-Za-z_]/.test(ch);
|
|
528
|
+
}
|
|
529
|
+
_isNameChar(ch) {
|
|
530
|
+
return /[A-Za-z0-9_:\-.]/.test(ch);
|
|
531
|
+
}
|
|
532
|
+
_skipWhitespace(input, start, end) {
|
|
533
|
+
let i = start;
|
|
534
|
+
while (i < end) {
|
|
535
|
+
const ch = input[i];
|
|
536
|
+
if (ch === void 0 || !/\s/.test(ch)) {
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
i += 1;
|
|
540
|
+
}
|
|
541
|
+
return i;
|
|
542
|
+
}
|
|
543
|
+
_currentNs() {
|
|
544
|
+
return this.nsStack[this.nsStack.length - 1] ?? /* @__PURE__ */ Object.create(null);
|
|
545
|
+
}
|
|
546
|
+
_advance(text) {
|
|
547
|
+
this.offset += text.length;
|
|
548
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
549
|
+
if (lastNewline === -1) {
|
|
550
|
+
this.column += text.length;
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const newlineCount = text.split("\n").length - 1;
|
|
554
|
+
this.line += newlineCount;
|
|
555
|
+
this.column = text.length - lastNewline;
|
|
556
|
+
}
|
|
557
|
+
_normalizeText(text, allowPendingCR) {
|
|
558
|
+
if (!text) {
|
|
559
|
+
return "";
|
|
560
|
+
}
|
|
561
|
+
let value = text;
|
|
562
|
+
let prefix = "";
|
|
563
|
+
if (this.pendingCR) {
|
|
564
|
+
prefix = "\n";
|
|
565
|
+
if (value.startsWith("\n")) {
|
|
566
|
+
value = value.slice(1);
|
|
567
|
+
}
|
|
568
|
+
this.pendingCR = false;
|
|
569
|
+
}
|
|
570
|
+
if (allowPendingCR && value.endsWith("\r")) {
|
|
571
|
+
this.pendingCR = true;
|
|
572
|
+
value = value.slice(0, -1);
|
|
573
|
+
}
|
|
574
|
+
const normalized = value.replace(/\r\n?/g, "\n");
|
|
575
|
+
return `${prefix}${normalized}`;
|
|
576
|
+
}
|
|
577
|
+
_flushPendingCR() {
|
|
578
|
+
if (!this.pendingCR) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this.pendingCR = false;
|
|
582
|
+
this.options.onText?.("\n");
|
|
583
|
+
}
|
|
584
|
+
_error(message) {
|
|
585
|
+
const error = new XmlSaxError(message, this.offset, this.line, this.column);
|
|
586
|
+
this.options.onError?.(error);
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/tree.ts
|
|
592
|
+
var TreeBuilder = class {
|
|
593
|
+
constructor() {
|
|
594
|
+
this.stack = [];
|
|
595
|
+
this.root = null;
|
|
596
|
+
this.onOpenTag = (tag) => {
|
|
597
|
+
const node = {
|
|
598
|
+
name: tag.name,
|
|
599
|
+
attributes: Object.fromEntries(
|
|
600
|
+
Object.entries(tag.attributes).map(([key, attr]) => [key, attr.value])
|
|
601
|
+
),
|
|
602
|
+
children: []
|
|
603
|
+
};
|
|
604
|
+
const parent = this.stack[this.stack.length - 1];
|
|
605
|
+
if (parent) {
|
|
606
|
+
parent.children?.push(node);
|
|
607
|
+
} else {
|
|
608
|
+
this.root = node;
|
|
609
|
+
}
|
|
610
|
+
this.stack.push(node);
|
|
611
|
+
};
|
|
612
|
+
this.onText = (text) => {
|
|
613
|
+
if (!this.stack.length) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const node = this.stack[this.stack.length - 1];
|
|
617
|
+
if (!node) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const children = node.children ?? [];
|
|
621
|
+
const last = children[children.length - 1];
|
|
622
|
+
if (typeof last === "string") {
|
|
623
|
+
children[children.length - 1] = last + text;
|
|
624
|
+
} else {
|
|
625
|
+
children.push(text);
|
|
626
|
+
}
|
|
627
|
+
node.children = children;
|
|
628
|
+
};
|
|
629
|
+
this.onCdata = (text) => {
|
|
630
|
+
this.onText(text);
|
|
631
|
+
};
|
|
632
|
+
this.onCloseTag = () => {
|
|
633
|
+
this.stack.pop();
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
getRoot() {
|
|
637
|
+
if (!this.root) {
|
|
638
|
+
throw new Error("No root element found");
|
|
639
|
+
}
|
|
640
|
+
return this.root;
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
function parseXmlString(xml, options = {}) {
|
|
644
|
+
const builder = new TreeBuilder();
|
|
645
|
+
const parser = new XmlSaxParser({
|
|
646
|
+
...options,
|
|
647
|
+
onOpenTag: builder.onOpenTag,
|
|
648
|
+
onText: builder.onText,
|
|
649
|
+
onCdata: builder.onCdata,
|
|
650
|
+
onCloseTag: builder.onCloseTag
|
|
651
|
+
});
|
|
652
|
+
parser.feed(xml);
|
|
653
|
+
parser.close();
|
|
654
|
+
return builder.getRoot();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/serializer.ts
|
|
658
|
+
var DEFAULT_OPTIONS2 = {
|
|
659
|
+
pretty: false,
|
|
660
|
+
indent: " ",
|
|
661
|
+
newline: "\n",
|
|
662
|
+
xmlDeclaration: false
|
|
663
|
+
};
|
|
664
|
+
function serializeXml(node, options = {}) {
|
|
665
|
+
const settings = { ...DEFAULT_OPTIONS2, ...options };
|
|
666
|
+
const body = serializeNode(node, settings, 0);
|
|
667
|
+
if (!settings.xmlDeclaration) {
|
|
668
|
+
return body;
|
|
669
|
+
}
|
|
670
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${settings.newline}${body}`;
|
|
671
|
+
}
|
|
672
|
+
function serializeNode(node, options, depth) {
|
|
673
|
+
const attrs = serializeAttributes(node.attributes);
|
|
674
|
+
const tagOpen = `<${node.name}${attrs}>`;
|
|
675
|
+
const tagClose = `</${node.name}>`;
|
|
676
|
+
const children = node.children ?? [];
|
|
677
|
+
if (!children.length) {
|
|
678
|
+
if (!options.pretty) {
|
|
679
|
+
return `<${node.name}${attrs}/>`;
|
|
680
|
+
}
|
|
681
|
+
const indent2 = options.indent.repeat(depth);
|
|
682
|
+
return `${indent2}<${node.name}${attrs}/>`;
|
|
683
|
+
}
|
|
684
|
+
if (!options.pretty) {
|
|
685
|
+
const inner2 = children.map((child) => serializeChild(child, options, depth + 1)).join("");
|
|
686
|
+
return `${tagOpen}${inner2}${tagClose}`;
|
|
687
|
+
}
|
|
688
|
+
const indent = options.indent.repeat(depth);
|
|
689
|
+
const innerIndent = options.indent.repeat(depth + 1);
|
|
690
|
+
const prettyOpen = `${indent}${tagOpen}`;
|
|
691
|
+
const prettyClose = `${indent}${tagClose}`;
|
|
692
|
+
const inner = children.map(
|
|
693
|
+
(child) => typeof child === "string" ? `${innerIndent}${escapeText(child)}` : serializeNode(child, options, depth + 1)
|
|
694
|
+
).join(options.newline);
|
|
695
|
+
return `${prettyOpen}${options.newline}${inner}${options.newline}${prettyClose}`;
|
|
696
|
+
}
|
|
697
|
+
function serializeChild(child, options, depth) {
|
|
698
|
+
if (typeof child === "string") {
|
|
699
|
+
return escapeText(child);
|
|
700
|
+
}
|
|
701
|
+
return serializeNode(child, options, depth);
|
|
702
|
+
}
|
|
703
|
+
function serializeAttributes(attrs) {
|
|
704
|
+
if (!attrs) {
|
|
705
|
+
return "";
|
|
706
|
+
}
|
|
707
|
+
return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
|
|
708
|
+
}
|
|
709
|
+
function escapeText(value) {
|
|
710
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
711
|
+
}
|
|
712
|
+
function escapeAttribute(value) {
|
|
713
|
+
return escapeText(value).replace(/"/g, """);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export { TreeBuilder, XmlSaxError, XmlSaxParser, parseXmlString, serializeXml };
|
|
717
|
+
//# sourceMappingURL=index.js.map
|
|
718
|
+
//# sourceMappingURL=index.js.map
|