@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.
@@ -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
- export { parseXml, findChild, findChildren, textContent, attr, walk };
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
- export { parseXml, findChild, findChildren, textContent, attr, walk };
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
- export { parseXml, findChild, findChildren, textContent, attr, walk };
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 };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v7.2.0
2
+ * @cj-tech-master/excelts v7.3.0
3
3
  * TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v7.2.0
2
+ * @cj-tech-master/excelts v7.3.0
3
3
  * TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -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
- export { parseXml, findChild, findChildren, textContent, attr, walk };
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 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cj-tech-master/excelts",
3
- "version": "7.2.0",
3
+ "version": "7.3.0",
4
4
  "description": "TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",