@cj-tech-master/excelts 7.2.0 → 7.3.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/dist/browser/modules/xml/dom.d.ts +28 -2
- package/dist/browser/modules/xml/dom.js +67 -1
- package/dist/browser/modules/xml/index.d.ts +3 -2
- package/dist/browser/modules/xml/index.js +2 -1
- package/dist/browser/modules/xml/to-object-shared.d.ts +29 -0
- package/dist/browser/modules/xml/to-object-shared.js +80 -0
- package/dist/browser/modules/xml/to-object.d.ts +35 -0
- package/dist/browser/modules/xml/to-object.js +121 -0
- package/dist/browser/modules/xml/types.d.ts +53 -0
- package/dist/cjs/modules/xml/dom.js +67 -0
- package/dist/cjs/modules/xml/index.js +4 -1
- package/dist/cjs/modules/xml/to-object-shared.js +85 -0
- package/dist/cjs/modules/xml/to-object.js +123 -0
- package/dist/esm/modules/xml/dom.js +67 -1
- package/dist/esm/modules/xml/index.js +2 -1
- package/dist/esm/modules/xml/to-object-shared.js +80 -0
- package/dist/esm/modules/xml/to-object.js +121 -0
- package/dist/iife/excelts.iife.js +1 -1
- package/dist/iife/excelts.iife.min.js +1 -1
- package/dist/types/modules/xml/dom.d.ts +28 -2
- package/dist/types/modules/xml/index.d.ts +3 -2
- package/dist/types/modules/xml/to-object-shared.d.ts +29 -0
- package/dist/types/modules/xml/to-object.d.ts +35 -0
- package/dist/types/modules/xml/types.d.ts +53 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* For large documents, prefer SAX-based streaming.
|
|
9
9
|
* This is intended for small-to-medium XML where tree access is convenient.
|
|
10
10
|
*/
|
|
11
|
-
import type { XmlDocument, XmlElement, XmlNode, XmlParseOptions } from "./types.js";
|
|
11
|
+
import type { ToPlainObjectOptions, XmlDocument, XmlElement, XmlNode, XmlParseOptions } from "./types.js";
|
|
12
12
|
/**
|
|
13
13
|
* Parse an XML string into a DOM tree.
|
|
14
14
|
*
|
|
@@ -49,4 +49,30 @@ declare function attr(element: XmlElement, name: string): string | undefined;
|
|
|
49
49
|
* Walk all descendant elements depth-first, calling visitor for each.
|
|
50
50
|
*/
|
|
51
51
|
declare function walk(element: XmlElement, visitor: (el: XmlElement) => void): void;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Convert an {@link XmlElement} DOM tree to a plain JavaScript object.
|
|
54
|
+
*
|
|
55
|
+
* Produces output similar to fast-xml-parser: element names become object keys,
|
|
56
|
+
* attributes are prefixed (default `@_`), text-only elements collapse to their
|
|
57
|
+
* string value, and repeated sibling names merge into arrays.
|
|
58
|
+
*
|
|
59
|
+
* @param element - The element to convert.
|
|
60
|
+
* @param options - Conversion options.
|
|
61
|
+
* @returns A plain JavaScript object representing the element.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const doc = parseXml('<root attr="1"><child>text</child></root>');
|
|
66
|
+
* const obj = toPlainObject(doc.root);
|
|
67
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const doc = parseXml('<list><item>a</item><item>b</item></list>');
|
|
73
|
+
* const obj = toPlainObject(doc.root);
|
|
74
|
+
* // { list: { item: ["a", "b"] } }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function toPlainObject(element: XmlElement, options?: ToPlainObjectOptions): Record<string, unknown>;
|
|
78
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject };
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { SaxParser } from "./sax.js";
|
|
12
12
|
import { XmlParseError } from "./errors.js";
|
|
13
|
+
import { resolveOptions, resolveValue, addChildValue } from "./to-object-shared.js";
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// Security
|
|
15
16
|
// =============================================================================
|
|
@@ -249,4 +250,69 @@ function walk(element, visitor) {
|
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// DOM → Plain Object Conversion
|
|
255
|
+
// =============================================================================
|
|
256
|
+
/**
|
|
257
|
+
* Convert an {@link XmlElement} DOM tree to a plain JavaScript object.
|
|
258
|
+
*
|
|
259
|
+
* Produces output similar to fast-xml-parser: element names become object keys,
|
|
260
|
+
* attributes are prefixed (default `@_`), text-only elements collapse to their
|
|
261
|
+
* string value, and repeated sibling names merge into arrays.
|
|
262
|
+
*
|
|
263
|
+
* @param element - The element to convert.
|
|
264
|
+
* @param options - Conversion options.
|
|
265
|
+
* @returns A plain JavaScript object representing the element.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const doc = parseXml('<root attr="1"><child>text</child></root>');
|
|
270
|
+
* const obj = toPlainObject(doc.root);
|
|
271
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const doc = parseXml('<list><item>a</item><item>b</item></list>');
|
|
277
|
+
* const obj = toPlainObject(doc.root);
|
|
278
|
+
* // { list: { item: ["a", "b"] } }
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
function toPlainObject(element, options) {
|
|
282
|
+
const opts = resolveOptions(options);
|
|
283
|
+
function convertElement(el) {
|
|
284
|
+
const obj = Object.create(null);
|
|
285
|
+
// Add attributes — el.attributes is created via Object.create(null)
|
|
286
|
+
// by safeAttributes(), so no prototype keys to guard against.
|
|
287
|
+
let hasAttributes = false;
|
|
288
|
+
for (const key in el.attributes) {
|
|
289
|
+
obj[opts.attrPrefix + key] = el.attributes[key];
|
|
290
|
+
hasAttributes = true;
|
|
291
|
+
}
|
|
292
|
+
// Collect text and child elements in a single pass.
|
|
293
|
+
let text = "";
|
|
294
|
+
let hasChildren = false;
|
|
295
|
+
for (const child of el.children) {
|
|
296
|
+
switch (child.type) {
|
|
297
|
+
case "text":
|
|
298
|
+
text += child.value;
|
|
299
|
+
break;
|
|
300
|
+
case "cdata":
|
|
301
|
+
if (opts.preserveCData) {
|
|
302
|
+
text += child.value;
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
case "element": {
|
|
306
|
+
const value = convertElement(child);
|
|
307
|
+
addChildValue(obj, child.name, value, opts.alwaysArray);
|
|
308
|
+
hasChildren = true;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
// comments and PIs are ignored in plain-object conversion
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return resolveValue(obj, text, hasAttributes, hasChildren, opts);
|
|
315
|
+
}
|
|
316
|
+
return { [element.name]: convertElement(element) };
|
|
317
|
+
}
|
|
318
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject };
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* - Dual-mode: streaming (SAX parser + stream writer) and buffered (DOM parser + writer)
|
|
11
11
|
* - Shared XmlSink interface lets rendering code target both modes transparently
|
|
12
12
|
*/
|
|
13
|
-
export type { XmlAttributes, XmlNodeType, XmlElement, XmlText, XmlCData, XmlComment, XmlProcessingInstruction, XmlNode, XmlDocument, XmlSink, SaxTag, SaxEvent, SaxEventAny, SaxHandlers, SaxOptions, WritableTarget, XmlParseOptions } from "./types.js";
|
|
13
|
+
export type { XmlAttributes, XmlNodeType, XmlElement, XmlText, XmlCData, XmlComment, XmlProcessingInstruction, XmlNode, XmlDocument, XmlSink, SaxTag, SaxEvent, SaxEventAny, SaxHandlers, SaxOptions, WritableTarget, XmlParseOptions, ToPlainObjectOptions, ParseXmlToObjectOptions } from "./types.js";
|
|
14
14
|
export { xmlEncode, xmlDecode, xmlEncodeAttr, validateXmlName, encodeCData, validateCommentText } from "./encode.js";
|
|
15
15
|
export { XmlWriter, StdDocAttributes } from "./writer.js";
|
|
16
16
|
export { XmlStreamWriter } from "./stream-writer.js";
|
|
17
17
|
export { SaxParser, parseSax, saxStream } from "./sax.js";
|
|
18
|
-
export { parseXml, findChild, findChildren, textContent, attr, walk } from "./dom.js";
|
|
18
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject } from "./dom.js";
|
|
19
|
+
export { parseXmlToObject } from "./to-object.js";
|
|
19
20
|
export { query, queryAll } from "./query.js";
|
|
20
21
|
export { XmlError, XmlParseError, XmlWriteError, isXmlError, isXmlParseError } from "./errors.js";
|
|
@@ -23,7 +23,8 @@ export { XmlStreamWriter } from "./stream-writer.js";
|
|
|
23
23
|
// Parsers
|
|
24
24
|
// =============================================================================
|
|
25
25
|
export { SaxParser, parseSax, saxStream } from "./sax.js";
|
|
26
|
-
export { parseXml, findChild, findChildren, textContent, attr, walk } from "./dom.js";
|
|
26
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject } from "./dom.js";
|
|
27
|
+
export { parseXmlToObject } from "./to-object.js";
|
|
27
28
|
export { query, queryAll } from "./query.js";
|
|
28
29
|
// =============================================================================
|
|
29
30
|
// Errors
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object — Shared Conversion Logic
|
|
3
|
+
*
|
|
4
|
+
* Internal module used by both `toPlainObject` (DOM path) and
|
|
5
|
+
* `parseXmlToObject` (SAX-direct path). Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import type { ToPlainObjectOptions } from "./types.js";
|
|
8
|
+
/** Options with all defaults resolved — no more `??` checks at hot-path call sites. */
|
|
9
|
+
export interface ResolvedOptions {
|
|
10
|
+
readonly attrPrefix: string;
|
|
11
|
+
readonly textKey: string;
|
|
12
|
+
readonly alwaysArray: boolean;
|
|
13
|
+
readonly preserveCData: boolean;
|
|
14
|
+
readonly ignoreWS: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveOptions(options?: ToPlainObjectOptions): ResolvedOptions;
|
|
17
|
+
/**
|
|
18
|
+
* Determine the final plain-object value for an element given its parts.
|
|
19
|
+
*
|
|
20
|
+
* Returns either:
|
|
21
|
+
* - a string (text-only / empty element)
|
|
22
|
+
* - the `obj` record (element with attributes and/or children)
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveValue(obj: Record<string, unknown>, text: string, hasAttributes: boolean, hasChildren: boolean, opts: ResolvedOptions): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Add a resolved child value into a parent object, merging repeated names
|
|
27
|
+
* into arrays.
|
|
28
|
+
*/
|
|
29
|
+
export declare function addChildValue(parent: Record<string, unknown>, name: string, value: unknown, alwaysArray: boolean): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object — Shared Conversion Logic
|
|
3
|
+
*
|
|
4
|
+
* Internal module used by both `toPlainObject` (DOM path) and
|
|
5
|
+
* `parseXmlToObject` (SAX-direct path). Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveOptions(options) {
|
|
8
|
+
return {
|
|
9
|
+
attrPrefix: options?.attributePrefix ?? "@_",
|
|
10
|
+
textKey: options?.textKey ?? "#text",
|
|
11
|
+
alwaysArray: options?.alwaysArray ?? false,
|
|
12
|
+
preserveCData: options?.preserveCData ?? true,
|
|
13
|
+
ignoreWS: options?.ignoreWhitespaceText ?? true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Helpers
|
|
18
|
+
// =============================================================================
|
|
19
|
+
/** Check if a string contains only whitespace without allocating a trimmed copy. */
|
|
20
|
+
function isWhitespaceOnly(s) {
|
|
21
|
+
for (let i = 0; i < s.length; i++) {
|
|
22
|
+
const ch = s.charCodeAt(i);
|
|
23
|
+
// space, tab, newline, carriage return
|
|
24
|
+
if (ch !== 0x20 && ch !== 0x09 && ch !== 0x0a && ch !== 0x0d) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Value Resolution
|
|
32
|
+
// =============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Determine the final plain-object value for an element given its parts.
|
|
35
|
+
*
|
|
36
|
+
* Returns either:
|
|
37
|
+
* - a string (text-only / empty element)
|
|
38
|
+
* - the `obj` record (element with attributes and/or children)
|
|
39
|
+
*/
|
|
40
|
+
export function resolveValue(obj, text, hasAttributes, hasChildren, opts) {
|
|
41
|
+
// Discard whitespace-only text when element has children (formatting indentation).
|
|
42
|
+
if (opts.ignoreWS && hasChildren && text.length > 0 && isWhitespaceOnly(text)) {
|
|
43
|
+
text = "";
|
|
44
|
+
}
|
|
45
|
+
const hasText = text.length > 0;
|
|
46
|
+
// Text-only element with no attributes → collapse to string value
|
|
47
|
+
if (hasText && !hasChildren && !hasAttributes) {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
// Add text content
|
|
51
|
+
if (hasText) {
|
|
52
|
+
obj[opts.textKey] = text;
|
|
53
|
+
}
|
|
54
|
+
// Empty element with no attributes → empty string (like fast-xml-parser)
|
|
55
|
+
if (!hasAttributes && !hasChildren && !hasText) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
return obj;
|
|
59
|
+
}
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Child Merging
|
|
62
|
+
// =============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Add a resolved child value into a parent object, merging repeated names
|
|
65
|
+
* into arrays.
|
|
66
|
+
*/
|
|
67
|
+
export function addChildValue(parent, name, value, alwaysArray) {
|
|
68
|
+
const existing = parent[name];
|
|
69
|
+
if (existing !== undefined) {
|
|
70
|
+
if (Array.isArray(existing)) {
|
|
71
|
+
existing.push(value);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
parent[name] = [existing, value];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
parent[name] = alwaysArray ? [value] : value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object (SAX-direct)
|
|
3
|
+
*
|
|
4
|
+
* Parses an XML string directly into a plain JavaScript object, bypassing the
|
|
5
|
+
* DOM tree entirely. This is the fastest path for "XML string → plain object →
|
|
6
|
+
* JSON.stringify" workflows.
|
|
7
|
+
*
|
|
8
|
+
* For converting an already-parsed {@link XmlElement} tree, use
|
|
9
|
+
* {@link toPlainObject} from `./dom` instead.
|
|
10
|
+
*/
|
|
11
|
+
import type { ParseXmlToObjectOptions } from "./types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Parse an XML string directly into a plain JavaScript object.
|
|
14
|
+
*
|
|
15
|
+
* Unlike `parseXml` + `toPlainObject`, this function builds the plain object
|
|
16
|
+
* in a single SAX pass with **zero intermediate DOM nodes**. Use this when
|
|
17
|
+
* performance matters and you only need the plain-object output.
|
|
18
|
+
*
|
|
19
|
+
* The output format matches {@link toPlainObject}: attributes are prefixed
|
|
20
|
+
* (default `@_`), text-only elements collapse to strings, and repeated
|
|
21
|
+
* sibling names merge into arrays.
|
|
22
|
+
*
|
|
23
|
+
* @param xml - Complete XML string.
|
|
24
|
+
* @param options - Conversion and parser options.
|
|
25
|
+
* @returns A plain JavaScript object representing the root element.
|
|
26
|
+
* @throws {XmlParseError} If the XML is malformed.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const obj = parseXmlToObject('<root attr="1"><child>text</child></root>');
|
|
31
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function parseXmlToObject(xml: string, options?: ParseXmlToObjectOptions): Record<string, unknown>;
|
|
35
|
+
export { parseXmlToObject };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object (SAX-direct)
|
|
3
|
+
*
|
|
4
|
+
* Parses an XML string directly into a plain JavaScript object, bypassing the
|
|
5
|
+
* DOM tree entirely. This is the fastest path for "XML string → plain object →
|
|
6
|
+
* JSON.stringify" workflows.
|
|
7
|
+
*
|
|
8
|
+
* For converting an already-parsed {@link XmlElement} tree, use
|
|
9
|
+
* {@link toPlainObject} from `./dom` instead.
|
|
10
|
+
*/
|
|
11
|
+
import { SaxParser } from "./sax.js";
|
|
12
|
+
import { XmlParseError } from "./errors.js";
|
|
13
|
+
import { resolveOptions, resolveValue, addChildValue } from "./to-object-shared.js";
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// parseXmlToObject
|
|
16
|
+
// =============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Parse an XML string directly into a plain JavaScript object.
|
|
19
|
+
*
|
|
20
|
+
* Unlike `parseXml` + `toPlainObject`, this function builds the plain object
|
|
21
|
+
* in a single SAX pass with **zero intermediate DOM nodes**. Use this when
|
|
22
|
+
* performance matters and you only need the plain-object output.
|
|
23
|
+
*
|
|
24
|
+
* The output format matches {@link toPlainObject}: attributes are prefixed
|
|
25
|
+
* (default `@_`), text-only elements collapse to strings, and repeated
|
|
26
|
+
* sibling names merge into arrays.
|
|
27
|
+
*
|
|
28
|
+
* @param xml - Complete XML string.
|
|
29
|
+
* @param options - Conversion and parser options.
|
|
30
|
+
* @returns A plain JavaScript object representing the root element.
|
|
31
|
+
* @throws {XmlParseError} If the XML is malformed.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const obj = parseXmlToObject('<root attr="1"><child>text</child></root>');
|
|
36
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function parseXmlToObject(xml, options) {
|
|
40
|
+
const opts = resolveOptions(options);
|
|
41
|
+
const parser = new SaxParser({
|
|
42
|
+
position: false,
|
|
43
|
+
fragment: options?.fragment ?? false,
|
|
44
|
+
maxDepth: options?.maxDepth,
|
|
45
|
+
maxEntityExpansions: options?.maxEntityExpansions
|
|
46
|
+
});
|
|
47
|
+
// Stack: bottom is a synthetic root frame that collects the document root.
|
|
48
|
+
const syntheticObj = Object.create(null);
|
|
49
|
+
const stack = [
|
|
50
|
+
{ obj: syntheticObj, text: "", hasChildren: false, hasAttributes: false, name: "" }
|
|
51
|
+
];
|
|
52
|
+
let error;
|
|
53
|
+
parser.on("error", err => {
|
|
54
|
+
if (!error) {
|
|
55
|
+
error = err instanceof XmlParseError ? err : new XmlParseError(err.message);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
parser.on("opentag", tag => {
|
|
59
|
+
const frame = {
|
|
60
|
+
obj: Object.create(null),
|
|
61
|
+
text: "",
|
|
62
|
+
hasChildren: false,
|
|
63
|
+
hasAttributes: false,
|
|
64
|
+
name: tag.name
|
|
65
|
+
};
|
|
66
|
+
// Write attributes directly into frame.obj
|
|
67
|
+
for (const key in tag.attributes) {
|
|
68
|
+
frame.obj[opts.attrPrefix + key] = tag.attributes[key];
|
|
69
|
+
frame.hasAttributes = true;
|
|
70
|
+
}
|
|
71
|
+
// Mark parent as having children
|
|
72
|
+
stack[stack.length - 1].hasChildren = true;
|
|
73
|
+
if (tag.isSelfClosing) {
|
|
74
|
+
// Resolve immediately — this element has no children or text.
|
|
75
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
stack.push(frame);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
parser.on("text", text => {
|
|
82
|
+
if (text.length > 0) {
|
|
83
|
+
stack[stack.length - 1].text += text;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
parser.on("cdata", text => {
|
|
87
|
+
if (opts.preserveCData && text.length > 0) {
|
|
88
|
+
stack[stack.length - 1].text += text;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
parser.on("closetag", tag => {
|
|
92
|
+
if (tag.isSelfClosing) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (stack.length <= 1) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const frame = stack.pop();
|
|
99
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
100
|
+
});
|
|
101
|
+
// Ignore comments and PIs — not needed for plain-object output
|
|
102
|
+
parser.write(xml);
|
|
103
|
+
parser.close();
|
|
104
|
+
if (error) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
// The synthetic root's obj should contain exactly one key (the document root).
|
|
108
|
+
return syntheticObj;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve a completed frame and add it to the parent.
|
|
112
|
+
* The synthetic root (stack depth 1) never gets `alwaysArray` applied to it.
|
|
113
|
+
*/
|
|
114
|
+
function finishFrame(frame, parent, opts) {
|
|
115
|
+
const value = resolveValue(frame.obj, frame.text, frame.hasAttributes, frame.hasChildren, opts);
|
|
116
|
+
// The document root element (child of synthetic root) should never be
|
|
117
|
+
// wrapped in an array by alwaysArray — it's always a direct value.
|
|
118
|
+
const isDocRoot = parent.name === "";
|
|
119
|
+
addChildValue(parent.obj, frame.name, value, isDocRoot ? false : opts.alwaysArray);
|
|
120
|
+
}
|
|
121
|
+
export { parseXmlToObject };
|
|
@@ -198,6 +198,59 @@ export interface SaxOptions {
|
|
|
198
198
|
export interface WritableTarget {
|
|
199
199
|
write(chunk: string | Uint8Array): void;
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Options for `toPlainObject()`.
|
|
203
|
+
*
|
|
204
|
+
* Controls how an {@link XmlElement} DOM tree is converted to a plain
|
|
205
|
+
* JavaScript object (similar to fast-xml-parser output format).
|
|
206
|
+
*/
|
|
207
|
+
export interface ToPlainObjectOptions {
|
|
208
|
+
/**
|
|
209
|
+
* Prefix for attribute keys.
|
|
210
|
+
* Set to `""` to use bare attribute names.
|
|
211
|
+
* @default "@_"
|
|
212
|
+
*/
|
|
213
|
+
attributePrefix?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Key used for text content when an element has both text and child elements
|
|
216
|
+
* (mixed content).
|
|
217
|
+
* @default "#text"
|
|
218
|
+
*/
|
|
219
|
+
textKey?: string;
|
|
220
|
+
/**
|
|
221
|
+
* When true, always wrap child elements in arrays even if there is only one.
|
|
222
|
+
* When false (default), single children are stored as a plain value.
|
|
223
|
+
* @default false
|
|
224
|
+
*/
|
|
225
|
+
alwaysArray?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* When true, include CDATA node values (already merged to text by default
|
|
228
|
+
* in `parseXml`, only relevant with `cdataAsNodes`).
|
|
229
|
+
* @default true
|
|
230
|
+
*/
|
|
231
|
+
preserveCData?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* When true, discard whitespace-only text in elements that also have child
|
|
234
|
+
* elements (typical of pretty-printed XML indentation). Leaf elements that
|
|
235
|
+
* contain only whitespace text are **not** affected — their text is preserved.
|
|
236
|
+
* @default true
|
|
237
|
+
*/
|
|
238
|
+
ignoreWhitespaceText?: boolean;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Options for `parseXmlToObject()`.
|
|
242
|
+
*
|
|
243
|
+
* Extends {@link ToPlainObjectOptions} with SAX parser settings, since
|
|
244
|
+
* `parseXmlToObject` handles both parsing and conversion in a single pass.
|
|
245
|
+
*/
|
|
246
|
+
export interface ParseXmlToObjectOptions extends ToPlainObjectOptions {
|
|
247
|
+
/** Parse as fragment (no root element required). Default: false */
|
|
248
|
+
fragment?: boolean;
|
|
249
|
+
/** Maximum element nesting depth. Default: 256. */
|
|
250
|
+
maxDepth?: number;
|
|
251
|
+
/** Maximum total entity expansions. Default: 10000. */
|
|
252
|
+
maxEntityExpansions?: number;
|
|
253
|
+
}
|
|
201
254
|
/** Options for `parseXml()`. */
|
|
202
255
|
export interface XmlParseOptions {
|
|
203
256
|
/** Include comments in the tree. Default: false */
|
|
@@ -16,8 +16,10 @@ exports.findChildren = findChildren;
|
|
|
16
16
|
exports.textContent = textContent;
|
|
17
17
|
exports.attr = attr;
|
|
18
18
|
exports.walk = walk;
|
|
19
|
+
exports.toPlainObject = toPlainObject;
|
|
19
20
|
const sax_1 = require("./sax.js");
|
|
20
21
|
const errors_1 = require("./errors.js");
|
|
22
|
+
const to_object_shared_1 = require("./to-object-shared.js");
|
|
21
23
|
// =============================================================================
|
|
22
24
|
// Security
|
|
23
25
|
// =============================================================================
|
|
@@ -257,3 +259,68 @@ function walk(element, visitor) {
|
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
}
|
|
262
|
+
// =============================================================================
|
|
263
|
+
// DOM → Plain Object Conversion
|
|
264
|
+
// =============================================================================
|
|
265
|
+
/**
|
|
266
|
+
* Convert an {@link XmlElement} DOM tree to a plain JavaScript object.
|
|
267
|
+
*
|
|
268
|
+
* Produces output similar to fast-xml-parser: element names become object keys,
|
|
269
|
+
* attributes are prefixed (default `@_`), text-only elements collapse to their
|
|
270
|
+
* string value, and repeated sibling names merge into arrays.
|
|
271
|
+
*
|
|
272
|
+
* @param element - The element to convert.
|
|
273
|
+
* @param options - Conversion options.
|
|
274
|
+
* @returns A plain JavaScript object representing the element.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const doc = parseXml('<root attr="1"><child>text</child></root>');
|
|
279
|
+
* const obj = toPlainObject(doc.root);
|
|
280
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* const doc = parseXml('<list><item>a</item><item>b</item></list>');
|
|
286
|
+
* const obj = toPlainObject(doc.root);
|
|
287
|
+
* // { list: { item: ["a", "b"] } }
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
function toPlainObject(element, options) {
|
|
291
|
+
const opts = (0, to_object_shared_1.resolveOptions)(options);
|
|
292
|
+
function convertElement(el) {
|
|
293
|
+
const obj = Object.create(null);
|
|
294
|
+
// Add attributes — el.attributes is created via Object.create(null)
|
|
295
|
+
// by safeAttributes(), so no prototype keys to guard against.
|
|
296
|
+
let hasAttributes = false;
|
|
297
|
+
for (const key in el.attributes) {
|
|
298
|
+
obj[opts.attrPrefix + key] = el.attributes[key];
|
|
299
|
+
hasAttributes = true;
|
|
300
|
+
}
|
|
301
|
+
// Collect text and child elements in a single pass.
|
|
302
|
+
let text = "";
|
|
303
|
+
let hasChildren = false;
|
|
304
|
+
for (const child of el.children) {
|
|
305
|
+
switch (child.type) {
|
|
306
|
+
case "text":
|
|
307
|
+
text += child.value;
|
|
308
|
+
break;
|
|
309
|
+
case "cdata":
|
|
310
|
+
if (opts.preserveCData) {
|
|
311
|
+
text += child.value;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case "element": {
|
|
315
|
+
const value = convertElement(child);
|
|
316
|
+
(0, to_object_shared_1.addChildValue)(obj, child.name, value, opts.alwaysArray);
|
|
317
|
+
hasChildren = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
// comments and PIs are ignored in plain-object conversion
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return (0, to_object_shared_1.resolveValue)(obj, text, hasAttributes, hasChildren, opts);
|
|
324
|
+
}
|
|
325
|
+
return { [element.name]: convertElement(element) };
|
|
326
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Shared XmlSink interface lets rendering code target both modes transparently
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.isXmlParseError = exports.isXmlError = exports.XmlWriteError = exports.XmlParseError = exports.XmlError = exports.queryAll = exports.query = exports.walk = exports.attr = exports.textContent = exports.findChildren = exports.findChild = exports.parseXml = exports.saxStream = exports.parseSax = exports.SaxParser = exports.XmlStreamWriter = exports.StdDocAttributes = exports.XmlWriter = exports.validateCommentText = exports.encodeCData = exports.validateXmlName = exports.xmlEncodeAttr = exports.xmlDecode = exports.xmlEncode = void 0;
|
|
15
|
+
exports.isXmlParseError = exports.isXmlError = exports.XmlWriteError = exports.XmlParseError = exports.XmlError = exports.queryAll = exports.query = exports.parseXmlToObject = exports.toPlainObject = exports.walk = exports.attr = exports.textContent = exports.findChildren = exports.findChild = exports.parseXml = exports.saxStream = exports.parseSax = exports.SaxParser = exports.XmlStreamWriter = exports.StdDocAttributes = exports.XmlWriter = exports.validateCommentText = exports.encodeCData = exports.validateXmlName = exports.xmlEncodeAttr = exports.xmlDecode = exports.xmlEncode = void 0;
|
|
16
16
|
// =============================================================================
|
|
17
17
|
// Encoding / Decoding
|
|
18
18
|
// =============================================================================
|
|
@@ -45,6 +45,9 @@ Object.defineProperty(exports, "findChildren", { enumerable: true, get: function
|
|
|
45
45
|
Object.defineProperty(exports, "textContent", { enumerable: true, get: function () { return dom_1.textContent; } });
|
|
46
46
|
Object.defineProperty(exports, "attr", { enumerable: true, get: function () { return dom_1.attr; } });
|
|
47
47
|
Object.defineProperty(exports, "walk", { enumerable: true, get: function () { return dom_1.walk; } });
|
|
48
|
+
Object.defineProperty(exports, "toPlainObject", { enumerable: true, get: function () { return dom_1.toPlainObject; } });
|
|
49
|
+
var to_object_1 = require("./to-object");
|
|
50
|
+
Object.defineProperty(exports, "parseXmlToObject", { enumerable: true, get: function () { return to_object_1.parseXmlToObject; } });
|
|
48
51
|
var query_1 = require("./query");
|
|
49
52
|
Object.defineProperty(exports, "query", { enumerable: true, get: function () { return query_1.query; } });
|
|
50
53
|
Object.defineProperty(exports, "queryAll", { enumerable: true, get: function () { return query_1.queryAll; } });
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XML → Plain Object — Shared Conversion Logic
|
|
4
|
+
*
|
|
5
|
+
* Internal module used by both `toPlainObject` (DOM path) and
|
|
6
|
+
* `parseXmlToObject` (SAX-direct path). Not part of the public API.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.resolveOptions = resolveOptions;
|
|
10
|
+
exports.resolveValue = resolveValue;
|
|
11
|
+
exports.addChildValue = addChildValue;
|
|
12
|
+
function resolveOptions(options) {
|
|
13
|
+
return {
|
|
14
|
+
attrPrefix: options?.attributePrefix ?? "@_",
|
|
15
|
+
textKey: options?.textKey ?? "#text",
|
|
16
|
+
alwaysArray: options?.alwaysArray ?? false,
|
|
17
|
+
preserveCData: options?.preserveCData ?? true,
|
|
18
|
+
ignoreWS: options?.ignoreWhitespaceText ?? true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Helpers
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/** Check if a string contains only whitespace without allocating a trimmed copy. */
|
|
25
|
+
function isWhitespaceOnly(s) {
|
|
26
|
+
for (let i = 0; i < s.length; i++) {
|
|
27
|
+
const ch = s.charCodeAt(i);
|
|
28
|
+
// space, tab, newline, carriage return
|
|
29
|
+
if (ch !== 0x20 && ch !== 0x09 && ch !== 0x0a && ch !== 0x0d) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Value Resolution
|
|
37
|
+
// =============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Determine the final plain-object value for an element given its parts.
|
|
40
|
+
*
|
|
41
|
+
* Returns either:
|
|
42
|
+
* - a string (text-only / empty element)
|
|
43
|
+
* - the `obj` record (element with attributes and/or children)
|
|
44
|
+
*/
|
|
45
|
+
function resolveValue(obj, text, hasAttributes, hasChildren, opts) {
|
|
46
|
+
// Discard whitespace-only text when element has children (formatting indentation).
|
|
47
|
+
if (opts.ignoreWS && hasChildren && text.length > 0 && isWhitespaceOnly(text)) {
|
|
48
|
+
text = "";
|
|
49
|
+
}
|
|
50
|
+
const hasText = text.length > 0;
|
|
51
|
+
// Text-only element with no attributes → collapse to string value
|
|
52
|
+
if (hasText && !hasChildren && !hasAttributes) {
|
|
53
|
+
return text;
|
|
54
|
+
}
|
|
55
|
+
// Add text content
|
|
56
|
+
if (hasText) {
|
|
57
|
+
obj[opts.textKey] = text;
|
|
58
|
+
}
|
|
59
|
+
// Empty element with no attributes → empty string (like fast-xml-parser)
|
|
60
|
+
if (!hasAttributes && !hasChildren && !hasText) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
return obj;
|
|
64
|
+
}
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Child Merging
|
|
67
|
+
// =============================================================================
|
|
68
|
+
/**
|
|
69
|
+
* Add a resolved child value into a parent object, merging repeated names
|
|
70
|
+
* into arrays.
|
|
71
|
+
*/
|
|
72
|
+
function addChildValue(parent, name, value, alwaysArray) {
|
|
73
|
+
const existing = parent[name];
|
|
74
|
+
if (existing !== undefined) {
|
|
75
|
+
if (Array.isArray(existing)) {
|
|
76
|
+
existing.push(value);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
parent[name] = [existing, value];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
parent[name] = alwaysArray ? [value] : value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XML → Plain Object (SAX-direct)
|
|
4
|
+
*
|
|
5
|
+
* Parses an XML string directly into a plain JavaScript object, bypassing the
|
|
6
|
+
* DOM tree entirely. This is the fastest path for "XML string → plain object →
|
|
7
|
+
* JSON.stringify" workflows.
|
|
8
|
+
*
|
|
9
|
+
* For converting an already-parsed {@link XmlElement} tree, use
|
|
10
|
+
* {@link toPlainObject} from `./dom` instead.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.parseXmlToObject = parseXmlToObject;
|
|
14
|
+
const sax_1 = require("./sax.js");
|
|
15
|
+
const errors_1 = require("./errors.js");
|
|
16
|
+
const to_object_shared_1 = require("./to-object-shared.js");
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// parseXmlToObject
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Parse an XML string directly into a plain JavaScript object.
|
|
22
|
+
*
|
|
23
|
+
* Unlike `parseXml` + `toPlainObject`, this function builds the plain object
|
|
24
|
+
* in a single SAX pass with **zero intermediate DOM nodes**. Use this when
|
|
25
|
+
* performance matters and you only need the plain-object output.
|
|
26
|
+
*
|
|
27
|
+
* The output format matches {@link toPlainObject}: attributes are prefixed
|
|
28
|
+
* (default `@_`), text-only elements collapse to strings, and repeated
|
|
29
|
+
* sibling names merge into arrays.
|
|
30
|
+
*
|
|
31
|
+
* @param xml - Complete XML string.
|
|
32
|
+
* @param options - Conversion and parser options.
|
|
33
|
+
* @returns A plain JavaScript object representing the root element.
|
|
34
|
+
* @throws {XmlParseError} If the XML is malformed.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const obj = parseXmlToObject('<root attr="1"><child>text</child></root>');
|
|
39
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function parseXmlToObject(xml, options) {
|
|
43
|
+
const opts = (0, to_object_shared_1.resolveOptions)(options);
|
|
44
|
+
const parser = new sax_1.SaxParser({
|
|
45
|
+
position: false,
|
|
46
|
+
fragment: options?.fragment ?? false,
|
|
47
|
+
maxDepth: options?.maxDepth,
|
|
48
|
+
maxEntityExpansions: options?.maxEntityExpansions
|
|
49
|
+
});
|
|
50
|
+
// Stack: bottom is a synthetic root frame that collects the document root.
|
|
51
|
+
const syntheticObj = Object.create(null);
|
|
52
|
+
const stack = [
|
|
53
|
+
{ obj: syntheticObj, text: "", hasChildren: false, hasAttributes: false, name: "" }
|
|
54
|
+
];
|
|
55
|
+
let error;
|
|
56
|
+
parser.on("error", err => {
|
|
57
|
+
if (!error) {
|
|
58
|
+
error = err instanceof errors_1.XmlParseError ? err : new errors_1.XmlParseError(err.message);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
parser.on("opentag", tag => {
|
|
62
|
+
const frame = {
|
|
63
|
+
obj: Object.create(null),
|
|
64
|
+
text: "",
|
|
65
|
+
hasChildren: false,
|
|
66
|
+
hasAttributes: false,
|
|
67
|
+
name: tag.name
|
|
68
|
+
};
|
|
69
|
+
// Write attributes directly into frame.obj
|
|
70
|
+
for (const key in tag.attributes) {
|
|
71
|
+
frame.obj[opts.attrPrefix + key] = tag.attributes[key];
|
|
72
|
+
frame.hasAttributes = true;
|
|
73
|
+
}
|
|
74
|
+
// Mark parent as having children
|
|
75
|
+
stack[stack.length - 1].hasChildren = true;
|
|
76
|
+
if (tag.isSelfClosing) {
|
|
77
|
+
// Resolve immediately — this element has no children or text.
|
|
78
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
stack.push(frame);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
parser.on("text", text => {
|
|
85
|
+
if (text.length > 0) {
|
|
86
|
+
stack[stack.length - 1].text += text;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
parser.on("cdata", text => {
|
|
90
|
+
if (opts.preserveCData && text.length > 0) {
|
|
91
|
+
stack[stack.length - 1].text += text;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
parser.on("closetag", tag => {
|
|
95
|
+
if (tag.isSelfClosing) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (stack.length <= 1) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const frame = stack.pop();
|
|
102
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
103
|
+
});
|
|
104
|
+
// Ignore comments and PIs — not needed for plain-object output
|
|
105
|
+
parser.write(xml);
|
|
106
|
+
parser.close();
|
|
107
|
+
if (error) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
// The synthetic root's obj should contain exactly one key (the document root).
|
|
111
|
+
return syntheticObj;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a completed frame and add it to the parent.
|
|
115
|
+
* The synthetic root (stack depth 1) never gets `alwaysArray` applied to it.
|
|
116
|
+
*/
|
|
117
|
+
function finishFrame(frame, parent, opts) {
|
|
118
|
+
const value = (0, to_object_shared_1.resolveValue)(frame.obj, frame.text, frame.hasAttributes, frame.hasChildren, opts);
|
|
119
|
+
// The document root element (child of synthetic root) should never be
|
|
120
|
+
// wrapped in an array by alwaysArray — it's always a direct value.
|
|
121
|
+
const isDocRoot = parent.name === "";
|
|
122
|
+
(0, to_object_shared_1.addChildValue)(parent.obj, frame.name, value, isDocRoot ? false : opts.alwaysArray);
|
|
123
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { SaxParser } from "./sax.js";
|
|
12
12
|
import { XmlParseError } from "./errors.js";
|
|
13
|
+
import { resolveOptions, resolveValue, addChildValue } from "./to-object-shared.js";
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// Security
|
|
15
16
|
// =============================================================================
|
|
@@ -249,4 +250,69 @@ function walk(element, visitor) {
|
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// DOM → Plain Object Conversion
|
|
255
|
+
// =============================================================================
|
|
256
|
+
/**
|
|
257
|
+
* Convert an {@link XmlElement} DOM tree to a plain JavaScript object.
|
|
258
|
+
*
|
|
259
|
+
* Produces output similar to fast-xml-parser: element names become object keys,
|
|
260
|
+
* attributes are prefixed (default `@_`), text-only elements collapse to their
|
|
261
|
+
* string value, and repeated sibling names merge into arrays.
|
|
262
|
+
*
|
|
263
|
+
* @param element - The element to convert.
|
|
264
|
+
* @param options - Conversion options.
|
|
265
|
+
* @returns A plain JavaScript object representing the element.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const doc = parseXml('<root attr="1"><child>text</child></root>');
|
|
270
|
+
* const obj = toPlainObject(doc.root);
|
|
271
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const doc = parseXml('<list><item>a</item><item>b</item></list>');
|
|
277
|
+
* const obj = toPlainObject(doc.root);
|
|
278
|
+
* // { list: { item: ["a", "b"] } }
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
function toPlainObject(element, options) {
|
|
282
|
+
const opts = resolveOptions(options);
|
|
283
|
+
function convertElement(el) {
|
|
284
|
+
const obj = Object.create(null);
|
|
285
|
+
// Add attributes — el.attributes is created via Object.create(null)
|
|
286
|
+
// by safeAttributes(), so no prototype keys to guard against.
|
|
287
|
+
let hasAttributes = false;
|
|
288
|
+
for (const key in el.attributes) {
|
|
289
|
+
obj[opts.attrPrefix + key] = el.attributes[key];
|
|
290
|
+
hasAttributes = true;
|
|
291
|
+
}
|
|
292
|
+
// Collect text and child elements in a single pass.
|
|
293
|
+
let text = "";
|
|
294
|
+
let hasChildren = false;
|
|
295
|
+
for (const child of el.children) {
|
|
296
|
+
switch (child.type) {
|
|
297
|
+
case "text":
|
|
298
|
+
text += child.value;
|
|
299
|
+
break;
|
|
300
|
+
case "cdata":
|
|
301
|
+
if (opts.preserveCData) {
|
|
302
|
+
text += child.value;
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
case "element": {
|
|
306
|
+
const value = convertElement(child);
|
|
307
|
+
addChildValue(obj, child.name, value, opts.alwaysArray);
|
|
308
|
+
hasChildren = true;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
// comments and PIs are ignored in plain-object conversion
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return resolveValue(obj, text, hasAttributes, hasChildren, opts);
|
|
315
|
+
}
|
|
316
|
+
return { [element.name]: convertElement(element) };
|
|
317
|
+
}
|
|
318
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject };
|
|
@@ -23,7 +23,8 @@ export { XmlStreamWriter } from "./stream-writer.js";
|
|
|
23
23
|
// Parsers
|
|
24
24
|
// =============================================================================
|
|
25
25
|
export { SaxParser, parseSax, saxStream } from "./sax.js";
|
|
26
|
-
export { parseXml, findChild, findChildren, textContent, attr, walk } from "./dom.js";
|
|
26
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject } from "./dom.js";
|
|
27
|
+
export { parseXmlToObject } from "./to-object.js";
|
|
27
28
|
export { query, queryAll } from "./query.js";
|
|
28
29
|
// =============================================================================
|
|
29
30
|
// Errors
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object — Shared Conversion Logic
|
|
3
|
+
*
|
|
4
|
+
* Internal module used by both `toPlainObject` (DOM path) and
|
|
5
|
+
* `parseXmlToObject` (SAX-direct path). Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveOptions(options) {
|
|
8
|
+
return {
|
|
9
|
+
attrPrefix: options?.attributePrefix ?? "@_",
|
|
10
|
+
textKey: options?.textKey ?? "#text",
|
|
11
|
+
alwaysArray: options?.alwaysArray ?? false,
|
|
12
|
+
preserveCData: options?.preserveCData ?? true,
|
|
13
|
+
ignoreWS: options?.ignoreWhitespaceText ?? true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Helpers
|
|
18
|
+
// =============================================================================
|
|
19
|
+
/** Check if a string contains only whitespace without allocating a trimmed copy. */
|
|
20
|
+
function isWhitespaceOnly(s) {
|
|
21
|
+
for (let i = 0; i < s.length; i++) {
|
|
22
|
+
const ch = s.charCodeAt(i);
|
|
23
|
+
// space, tab, newline, carriage return
|
|
24
|
+
if (ch !== 0x20 && ch !== 0x09 && ch !== 0x0a && ch !== 0x0d) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Value Resolution
|
|
32
|
+
// =============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Determine the final plain-object value for an element given its parts.
|
|
35
|
+
*
|
|
36
|
+
* Returns either:
|
|
37
|
+
* - a string (text-only / empty element)
|
|
38
|
+
* - the `obj` record (element with attributes and/or children)
|
|
39
|
+
*/
|
|
40
|
+
export function resolveValue(obj, text, hasAttributes, hasChildren, opts) {
|
|
41
|
+
// Discard whitespace-only text when element has children (formatting indentation).
|
|
42
|
+
if (opts.ignoreWS && hasChildren && text.length > 0 && isWhitespaceOnly(text)) {
|
|
43
|
+
text = "";
|
|
44
|
+
}
|
|
45
|
+
const hasText = text.length > 0;
|
|
46
|
+
// Text-only element with no attributes → collapse to string value
|
|
47
|
+
if (hasText && !hasChildren && !hasAttributes) {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
// Add text content
|
|
51
|
+
if (hasText) {
|
|
52
|
+
obj[opts.textKey] = text;
|
|
53
|
+
}
|
|
54
|
+
// Empty element with no attributes → empty string (like fast-xml-parser)
|
|
55
|
+
if (!hasAttributes && !hasChildren && !hasText) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
return obj;
|
|
59
|
+
}
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Child Merging
|
|
62
|
+
// =============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Add a resolved child value into a parent object, merging repeated names
|
|
65
|
+
* into arrays.
|
|
66
|
+
*/
|
|
67
|
+
export function addChildValue(parent, name, value, alwaysArray) {
|
|
68
|
+
const existing = parent[name];
|
|
69
|
+
if (existing !== undefined) {
|
|
70
|
+
if (Array.isArray(existing)) {
|
|
71
|
+
existing.push(value);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
parent[name] = [existing, value];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
parent[name] = alwaysArray ? [value] : value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object (SAX-direct)
|
|
3
|
+
*
|
|
4
|
+
* Parses an XML string directly into a plain JavaScript object, bypassing the
|
|
5
|
+
* DOM tree entirely. This is the fastest path for "XML string → plain object →
|
|
6
|
+
* JSON.stringify" workflows.
|
|
7
|
+
*
|
|
8
|
+
* For converting an already-parsed {@link XmlElement} tree, use
|
|
9
|
+
* {@link toPlainObject} from `./dom` instead.
|
|
10
|
+
*/
|
|
11
|
+
import { SaxParser } from "./sax.js";
|
|
12
|
+
import { XmlParseError } from "./errors.js";
|
|
13
|
+
import { resolveOptions, resolveValue, addChildValue } from "./to-object-shared.js";
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// parseXmlToObject
|
|
16
|
+
// =============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Parse an XML string directly into a plain JavaScript object.
|
|
19
|
+
*
|
|
20
|
+
* Unlike `parseXml` + `toPlainObject`, this function builds the plain object
|
|
21
|
+
* in a single SAX pass with **zero intermediate DOM nodes**. Use this when
|
|
22
|
+
* performance matters and you only need the plain-object output.
|
|
23
|
+
*
|
|
24
|
+
* The output format matches {@link toPlainObject}: attributes are prefixed
|
|
25
|
+
* (default `@_`), text-only elements collapse to strings, and repeated
|
|
26
|
+
* sibling names merge into arrays.
|
|
27
|
+
*
|
|
28
|
+
* @param xml - Complete XML string.
|
|
29
|
+
* @param options - Conversion and parser options.
|
|
30
|
+
* @returns A plain JavaScript object representing the root element.
|
|
31
|
+
* @throws {XmlParseError} If the XML is malformed.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const obj = parseXmlToObject('<root attr="1"><child>text</child></root>');
|
|
36
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function parseXmlToObject(xml, options) {
|
|
40
|
+
const opts = resolveOptions(options);
|
|
41
|
+
const parser = new SaxParser({
|
|
42
|
+
position: false,
|
|
43
|
+
fragment: options?.fragment ?? false,
|
|
44
|
+
maxDepth: options?.maxDepth,
|
|
45
|
+
maxEntityExpansions: options?.maxEntityExpansions
|
|
46
|
+
});
|
|
47
|
+
// Stack: bottom is a synthetic root frame that collects the document root.
|
|
48
|
+
const syntheticObj = Object.create(null);
|
|
49
|
+
const stack = [
|
|
50
|
+
{ obj: syntheticObj, text: "", hasChildren: false, hasAttributes: false, name: "" }
|
|
51
|
+
];
|
|
52
|
+
let error;
|
|
53
|
+
parser.on("error", err => {
|
|
54
|
+
if (!error) {
|
|
55
|
+
error = err instanceof XmlParseError ? err : new XmlParseError(err.message);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
parser.on("opentag", tag => {
|
|
59
|
+
const frame = {
|
|
60
|
+
obj: Object.create(null),
|
|
61
|
+
text: "",
|
|
62
|
+
hasChildren: false,
|
|
63
|
+
hasAttributes: false,
|
|
64
|
+
name: tag.name
|
|
65
|
+
};
|
|
66
|
+
// Write attributes directly into frame.obj
|
|
67
|
+
for (const key in tag.attributes) {
|
|
68
|
+
frame.obj[opts.attrPrefix + key] = tag.attributes[key];
|
|
69
|
+
frame.hasAttributes = true;
|
|
70
|
+
}
|
|
71
|
+
// Mark parent as having children
|
|
72
|
+
stack[stack.length - 1].hasChildren = true;
|
|
73
|
+
if (tag.isSelfClosing) {
|
|
74
|
+
// Resolve immediately — this element has no children or text.
|
|
75
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
stack.push(frame);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
parser.on("text", text => {
|
|
82
|
+
if (text.length > 0) {
|
|
83
|
+
stack[stack.length - 1].text += text;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
parser.on("cdata", text => {
|
|
87
|
+
if (opts.preserveCData && text.length > 0) {
|
|
88
|
+
stack[stack.length - 1].text += text;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
parser.on("closetag", tag => {
|
|
92
|
+
if (tag.isSelfClosing) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (stack.length <= 1) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const frame = stack.pop();
|
|
99
|
+
finishFrame(frame, stack[stack.length - 1], opts);
|
|
100
|
+
});
|
|
101
|
+
// Ignore comments and PIs — not needed for plain-object output
|
|
102
|
+
parser.write(xml);
|
|
103
|
+
parser.close();
|
|
104
|
+
if (error) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
// The synthetic root's obj should contain exactly one key (the document root).
|
|
108
|
+
return syntheticObj;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve a completed frame and add it to the parent.
|
|
112
|
+
* The synthetic root (stack depth 1) never gets `alwaysArray` applied to it.
|
|
113
|
+
*/
|
|
114
|
+
function finishFrame(frame, parent, opts) {
|
|
115
|
+
const value = resolveValue(frame.obj, frame.text, frame.hasAttributes, frame.hasChildren, opts);
|
|
116
|
+
// The document root element (child of synthetic root) should never be
|
|
117
|
+
// wrapped in an array by alwaysArray — it's always a direct value.
|
|
118
|
+
const isDocRoot = parent.name === "";
|
|
119
|
+
addChildValue(parent.obj, frame.name, value, isDocRoot ? false : opts.alwaysArray);
|
|
120
|
+
}
|
|
121
|
+
export { parseXmlToObject };
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* For large documents, prefer SAX-based streaming.
|
|
9
9
|
* This is intended for small-to-medium XML where tree access is convenient.
|
|
10
10
|
*/
|
|
11
|
-
import type { XmlDocument, XmlElement, XmlNode, XmlParseOptions } from "./types.js";
|
|
11
|
+
import type { ToPlainObjectOptions, XmlDocument, XmlElement, XmlNode, XmlParseOptions } from "./types.js";
|
|
12
12
|
/**
|
|
13
13
|
* Parse an XML string into a DOM tree.
|
|
14
14
|
*
|
|
@@ -49,4 +49,30 @@ declare function attr(element: XmlElement, name: string): string | undefined;
|
|
|
49
49
|
* Walk all descendant elements depth-first, calling visitor for each.
|
|
50
50
|
*/
|
|
51
51
|
declare function walk(element: XmlElement, visitor: (el: XmlElement) => void): void;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Convert an {@link XmlElement} DOM tree to a plain JavaScript object.
|
|
54
|
+
*
|
|
55
|
+
* Produces output similar to fast-xml-parser: element names become object keys,
|
|
56
|
+
* attributes are prefixed (default `@_`), text-only elements collapse to their
|
|
57
|
+
* string value, and repeated sibling names merge into arrays.
|
|
58
|
+
*
|
|
59
|
+
* @param element - The element to convert.
|
|
60
|
+
* @param options - Conversion options.
|
|
61
|
+
* @returns A plain JavaScript object representing the element.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const doc = parseXml('<root attr="1"><child>text</child></root>');
|
|
66
|
+
* const obj = toPlainObject(doc.root);
|
|
67
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const doc = parseXml('<list><item>a</item><item>b</item></list>');
|
|
73
|
+
* const obj = toPlainObject(doc.root);
|
|
74
|
+
* // { list: { item: ["a", "b"] } }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function toPlainObject(element: XmlElement, options?: ToPlainObjectOptions): Record<string, unknown>;
|
|
78
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject };
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* - Dual-mode: streaming (SAX parser + stream writer) and buffered (DOM parser + writer)
|
|
11
11
|
* - Shared XmlSink interface lets rendering code target both modes transparently
|
|
12
12
|
*/
|
|
13
|
-
export type { XmlAttributes, XmlNodeType, XmlElement, XmlText, XmlCData, XmlComment, XmlProcessingInstruction, XmlNode, XmlDocument, XmlSink, SaxTag, SaxEvent, SaxEventAny, SaxHandlers, SaxOptions, WritableTarget, XmlParseOptions } from "./types.js";
|
|
13
|
+
export type { XmlAttributes, XmlNodeType, XmlElement, XmlText, XmlCData, XmlComment, XmlProcessingInstruction, XmlNode, XmlDocument, XmlSink, SaxTag, SaxEvent, SaxEventAny, SaxHandlers, SaxOptions, WritableTarget, XmlParseOptions, ToPlainObjectOptions, ParseXmlToObjectOptions } from "./types.js";
|
|
14
14
|
export { xmlEncode, xmlDecode, xmlEncodeAttr, validateXmlName, encodeCData, validateCommentText } from "./encode.js";
|
|
15
15
|
export { XmlWriter, StdDocAttributes } from "./writer.js";
|
|
16
16
|
export { XmlStreamWriter } from "./stream-writer.js";
|
|
17
17
|
export { SaxParser, parseSax, saxStream } from "./sax.js";
|
|
18
|
-
export { parseXml, findChild, findChildren, textContent, attr, walk } from "./dom.js";
|
|
18
|
+
export { parseXml, findChild, findChildren, textContent, attr, walk, toPlainObject } from "./dom.js";
|
|
19
|
+
export { parseXmlToObject } from "./to-object.js";
|
|
19
20
|
export { query, queryAll } from "./query.js";
|
|
20
21
|
export { XmlError, XmlParseError, XmlWriteError, isXmlError, isXmlParseError } from "./errors.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object — Shared Conversion Logic
|
|
3
|
+
*
|
|
4
|
+
* Internal module used by both `toPlainObject` (DOM path) and
|
|
5
|
+
* `parseXmlToObject` (SAX-direct path). Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import type { ToPlainObjectOptions } from "./types.js";
|
|
8
|
+
/** Options with all defaults resolved — no more `??` checks at hot-path call sites. */
|
|
9
|
+
export interface ResolvedOptions {
|
|
10
|
+
readonly attrPrefix: string;
|
|
11
|
+
readonly textKey: string;
|
|
12
|
+
readonly alwaysArray: boolean;
|
|
13
|
+
readonly preserveCData: boolean;
|
|
14
|
+
readonly ignoreWS: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveOptions(options?: ToPlainObjectOptions): ResolvedOptions;
|
|
17
|
+
/**
|
|
18
|
+
* Determine the final plain-object value for an element given its parts.
|
|
19
|
+
*
|
|
20
|
+
* Returns either:
|
|
21
|
+
* - a string (text-only / empty element)
|
|
22
|
+
* - the `obj` record (element with attributes and/or children)
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveValue(obj: Record<string, unknown>, text: string, hasAttributes: boolean, hasChildren: boolean, opts: ResolvedOptions): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Add a resolved child value into a parent object, merging repeated names
|
|
27
|
+
* into arrays.
|
|
28
|
+
*/
|
|
29
|
+
export declare function addChildValue(parent: Record<string, unknown>, name: string, value: unknown, alwaysArray: boolean): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML → Plain Object (SAX-direct)
|
|
3
|
+
*
|
|
4
|
+
* Parses an XML string directly into a plain JavaScript object, bypassing the
|
|
5
|
+
* DOM tree entirely. This is the fastest path for "XML string → plain object →
|
|
6
|
+
* JSON.stringify" workflows.
|
|
7
|
+
*
|
|
8
|
+
* For converting an already-parsed {@link XmlElement} tree, use
|
|
9
|
+
* {@link toPlainObject} from `./dom` instead.
|
|
10
|
+
*/
|
|
11
|
+
import type { ParseXmlToObjectOptions } from "./types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Parse an XML string directly into a plain JavaScript object.
|
|
14
|
+
*
|
|
15
|
+
* Unlike `parseXml` + `toPlainObject`, this function builds the plain object
|
|
16
|
+
* in a single SAX pass with **zero intermediate DOM nodes**. Use this when
|
|
17
|
+
* performance matters and you only need the plain-object output.
|
|
18
|
+
*
|
|
19
|
+
* The output format matches {@link toPlainObject}: attributes are prefixed
|
|
20
|
+
* (default `@_`), text-only elements collapse to strings, and repeated
|
|
21
|
+
* sibling names merge into arrays.
|
|
22
|
+
*
|
|
23
|
+
* @param xml - Complete XML string.
|
|
24
|
+
* @param options - Conversion and parser options.
|
|
25
|
+
* @returns A plain JavaScript object representing the root element.
|
|
26
|
+
* @throws {XmlParseError} If the XML is malformed.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const obj = parseXmlToObject('<root attr="1"><child>text</child></root>');
|
|
31
|
+
* // { root: { "@_attr": "1", child: "text" } }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function parseXmlToObject(xml: string, options?: ParseXmlToObjectOptions): Record<string, unknown>;
|
|
35
|
+
export { parseXmlToObject };
|
|
@@ -198,6 +198,59 @@ export interface SaxOptions {
|
|
|
198
198
|
export interface WritableTarget {
|
|
199
199
|
write(chunk: string | Uint8Array): void;
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Options for `toPlainObject()`.
|
|
203
|
+
*
|
|
204
|
+
* Controls how an {@link XmlElement} DOM tree is converted to a plain
|
|
205
|
+
* JavaScript object (similar to fast-xml-parser output format).
|
|
206
|
+
*/
|
|
207
|
+
export interface ToPlainObjectOptions {
|
|
208
|
+
/**
|
|
209
|
+
* Prefix for attribute keys.
|
|
210
|
+
* Set to `""` to use bare attribute names.
|
|
211
|
+
* @default "@_"
|
|
212
|
+
*/
|
|
213
|
+
attributePrefix?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Key used for text content when an element has both text and child elements
|
|
216
|
+
* (mixed content).
|
|
217
|
+
* @default "#text"
|
|
218
|
+
*/
|
|
219
|
+
textKey?: string;
|
|
220
|
+
/**
|
|
221
|
+
* When true, always wrap child elements in arrays even if there is only one.
|
|
222
|
+
* When false (default), single children are stored as a plain value.
|
|
223
|
+
* @default false
|
|
224
|
+
*/
|
|
225
|
+
alwaysArray?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* When true, include CDATA node values (already merged to text by default
|
|
228
|
+
* in `parseXml`, only relevant with `cdataAsNodes`).
|
|
229
|
+
* @default true
|
|
230
|
+
*/
|
|
231
|
+
preserveCData?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* When true, discard whitespace-only text in elements that also have child
|
|
234
|
+
* elements (typical of pretty-printed XML indentation). Leaf elements that
|
|
235
|
+
* contain only whitespace text are **not** affected — their text is preserved.
|
|
236
|
+
* @default true
|
|
237
|
+
*/
|
|
238
|
+
ignoreWhitespaceText?: boolean;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Options for `parseXmlToObject()`.
|
|
242
|
+
*
|
|
243
|
+
* Extends {@link ToPlainObjectOptions} with SAX parser settings, since
|
|
244
|
+
* `parseXmlToObject` handles both parsing and conversion in a single pass.
|
|
245
|
+
*/
|
|
246
|
+
export interface ParseXmlToObjectOptions extends ToPlainObjectOptions {
|
|
247
|
+
/** Parse as fragment (no root element required). Default: false */
|
|
248
|
+
fragment?: boolean;
|
|
249
|
+
/** Maximum element nesting depth. Default: 256. */
|
|
250
|
+
maxDepth?: number;
|
|
251
|
+
/** Maximum total entity expansions. Default: 10000. */
|
|
252
|
+
maxEntityExpansions?: number;
|
|
253
|
+
}
|
|
201
254
|
/** Options for `parseXml()`. */
|
|
202
255
|
export interface XmlParseOptions {
|
|
203
256
|
/** Include comments in the tree. Default: false */
|