@office-open/xml 0.5.0 → 0.5.2

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/index.mjs CHANGED
@@ -1,8 +1,499 @@
1
- import{allChildren as e,attr as t,attrBool as n,attrNum as r,childCount as i,childText as a,children as o,collectText as s,colorAttr as c,findChild as l,findDeep as u,hasChild as d,textOf as f}from"./utils.mjs";const p={"&":`&amp;`,'"':`&quot;`,"'":`&apos;`,"<":`&lt;`,">":`&gt;`},m=/([&"<>'])/g;function h(e){return e.replace(m,e=>p[e])}function g(e){return String(e).replace(/&(?!amp;|lt;|gt;|quot;|apos;)/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&apos;`)}function _(e){let t=``;for(let[n,r]of Object.entries(e))r!==void 0&&(t+=` ${n}="${typeof r==`string`?h(r):r}"`);return t}function v(e,t){let n=y(t),r=[];if(n.declaration){let e=n.declaration===!0?{}:n.declaration,t=e.encoding||`UTF-8`,i=e.standalone,a=`<?xml version="1.0" encoding="`+t+`"`;i&&(a+=` standalone="`+i+`"`),a+=`?>`,r.push(a),n.indent&&r.push(`
2
- `)}let i=Array.isArray(e)?e:[e];for(let e=0;e<i.length;e++){let t=Object.keys(i[e]);r.push(b(t[0],i[e][t[0]],n.indent,0)),n.indent&&e<i.length-1&&r.push(`
3
- `)}return r.join(``)}function y(e){let t=typeof e==`object`&&!Array.isArray(e)?e:{indent:e},n=``;return t.indent&&(n=t.indent===!0?` `:String(t.indent)),{indent:n,declaration:t.declaration}}function b(e,t,n,r){let i=[],a=[],o=[],s=!1;if(t==null){let t=i.length?` `+i.join(` `):``;return`${n?n.repeat(r):``}<${e}${t}/>`}if(typeof t==`object`){if(t._attr)for(let e of Object.keys(t._attr))i.push(`${e}="${h(String(t._attr[e]))}"`);if(t._attributes)for(let e of Object.keys(t._attributes))i.push(`${e}="${h(String(t._attributes[e]))}"`);if(t._cdata){let e=String(t._cdata).replace(/\]\]>/g,`]]]]><![CDATA[>`);a.push(`<![CDATA[${e}]]>`)}if(Array.isArray(t))if(t.length===0)s=!0;else for(let e of t)if(e&&typeof e==`object`&&`_attr`in e)for(let t of Object.keys(e._attr))i.push(`${t}="${h(String(e._attr[t]))}"`);else if(e&&typeof e==`object`&&`_attributes`in e)for(let t of Object.keys(e._attributes))i.push(`${t}="${h(String(e._attributes[t]))}"`);else if(e&&typeof e==`object`){let t=Object.keys(e);o.push(b(t[0],e[t[0]],n,r+1))}else e!=null&&a.push(h(String(e)))}else a.push(h(String(t)));let c=n?n.repeat(r):``,l=i.length?` `+i.join(` `):``;if(a.length+o.length===0)return s?`${c}<${e}${l}></${e}>`:`${c}<${e}${l}/>`;if(o.length===0&&a.length===1)return n?`${c}<${e}${l}>${a[0]}</${e}>`:`<${e}${l}>${a[0]}</${e}>`;let u=[];u.push(`${c}<${e}${l}>`),n&&u.push(`
4
- `);let d=n?n.repeat(r+1):``;for(let e of a)u.push(`${d}${e}`),n&&u.push(`
5
- `);for(let e of o)u.push(e),n&&u.push(`
6
- `);return u.push(`${c}</${e}>`),u.join(``)}const x={"&amp;":`&`,"&lt;":`<`,"&gt;":`>`,"&quot;":`"`,"&apos;":`'`},S=/&(amp|lt|gt|quot|apos);/g;function C(e){return e.replace(S,e=>x[e])}function w(e){let t=Number(e);if(!isNaN(t))return t;let n=e.toLowerCase();return n===`true`?!0:n===`false`?!1:e}function T(e,t){let n=t?.captureSpacesBetweenElements??!1,r=t?.trim??!1,i=t?.ignoreDeclaration??!1,a=t?.ignoreText??!1,o=t?.ignoreComment??!1,s=t?.ignoreCdata??!1,c=t?.ignoreDoctype??!1,l=t?.nativeTypeAttributes??!1,u={},d=[u],f=0,p=e.length;for(;f<p;){if(!n&&A(e.charCodeAt(f))){f++;continue}if(e.charCodeAt(f)!==60){let t=f;for(;f<p&&e.charCodeAt(f)!==60;)f++;let i=C(e.slice(t,f));if(r&&(i=i.trim()),a)continue;i.length>0&&(n||i.trim().length>0)&&k(d[d.length-1],`text`,i);continue}if(f++,e.charCodeAt(f)===63){let t=e.indexOf(`?>`,f+1);if(t===-1)break;let n=e.slice(f+1,t);f=t+2;let r=n.match(/^xml\s+(.*)$/s);if(r&&!i){u.declaration||={};let e=O(r[1]);if(l)for(let t of Object.keys(e))e[t]=w(e[t]);u.declaration.attributes=e}continue}if(e.charCodeAt(f)===33&&e.slice(f,f+3)===`!--`){let t=e.indexOf(`-->`,f+3);if(t===-1)break;let n=e.slice(f+3,t);f=t+3,o||(r?k(d[d.length-1],`comment`,n.trim()):k(d[d.length-1],`comment`,n));continue}if(e.charCodeAt(f)===33&&e.slice(f,f+8)===`![CDATA[`){let t=e.indexOf(`]]>`,f+8);if(t===-1)break;let n=e.slice(f+8,t);f=t+3,s||(r?k(d[d.length-1],`cdata`,n.trim()):k(d[d.length-1],`cdata`,n));continue}if(e.charCodeAt(f)===33&&e.slice(f,f+9)===`!DOCTYPE`){let t=e.indexOf(`>`,f+9);if(t===-1)break;let n=e.slice(f+9,t).trim();f=t+1,c||k(d[d.length-1],`doctype`,n);continue}if(e.charCodeAt(f)===47){let t=e.indexOf(`>`,f+1);if(t===-1)break;f=t+1,d.pop();continue}let t=E(e,f),m=e.slice(f,t),h=t,g=D(e,h);if(h=g.pos,l)for(let e of Object.keys(g.attrs))g.attrs[e]=w(g.attrs[e]);let _=e.charCodeAt(h)===47;_?h+=2:h++;let v={type:`element`,name:m};Object.keys(g.attrs).length>0&&(v.attributes=g.attrs);let y=d[d.length-1];y.elements||=[],y.elements.push(v),_||d.push(v),f=h}if(u.elements){let e=u.elements;delete u.elements,u.elements=e,delete u.text}return u}function E(e,t){let n=t,r=e.length;for(;n<r;){let t=e.charCodeAt(n);if(t===32||t===9||t===10||t===13||t===47||t===62)return n;n++}return n}function D(e,t){let n={},r=t,i=e.length;for(;r<i;){for(;r<i&&A(e.charCodeAt(r));)r++;if(r>=i||e.charCodeAt(r)===62||e.charCodeAt(r)===47)break;let t=r;for(;r<i&&e.charCodeAt(r)!==61&&!(e.charCodeAt(r)===62||e.charCodeAt(r)===47);)r++;let a=e.slice(t,r);if(e.charCodeAt(r)!==61)break;for(r++;r<i&&A(e.charCodeAt(r));)r++;let o=e.charCodeAt(r);if(o!==34&&o!==39)break;r++;let s=r;for(;r<i&&e.charCodeAt(r)!==o;)r++;n[a]=e.slice(s,r),r++}return{attrs:n,pos:r}}function O(e){let t={},n=0,r=e.length;for(;n<r;){for(;n<r&&A(e.charCodeAt(n));)n++;if(n>=r)break;let i=n;for(;n<r&&e.charCodeAt(n)!==61&&!A(e.charCodeAt(n));)n++;let a=e.slice(i,n);for(;n<r&&A(e.charCodeAt(n));)n++;if(n>=r||e.charCodeAt(n)!==61)break;for(n++;n<r&&A(e.charCodeAt(n));)n++;let o=e.charCodeAt(n);if(o!==34&&o!==39)break;n++;let s=n;for(;n<r&&e.charCodeAt(n)!==o;)n++;t[a]=e.slice(s,n),n++}return t}function k(e,t,n){e.elements||=[];let r={type:t};r[t]=n,e.elements.push(r)}function A(e){return e===32||e===9||e===10||e===13}function j(e,t){let n=N(t),r=[];return e.declaration&&!n.ignoreDeclaration&&r.push(F(e.declaration)),e.elements?.length&&r.push(R(e.elements,n,0,!r.length)),r.join(``)}function M(e,t){return j(e,t)}function N(e){if(!e)return{spaces:``,ignoreDeclaration:!1,ignoreText:!1,ignoreComment:!1,ignoreCdata:!1,ignoreDoctype:!1,fullTagEmptyElement:!1,indentText:!1,indentCdata:!1};let t=``;return e.spaces!=null&&(t=typeof e.spaces==`number`?` `.repeat(e.spaces):e.spaces),{spaces:t,ignoreDeclaration:e.ignoreDeclaration??!1,ignoreText:e.ignoreText??!1,ignoreComment:e.ignoreComment??!1,ignoreCdata:e.ignoreCdata??!1,ignoreDoctype:e.ignoreDoctype??!1,fullTagEmptyElement:e.fullTagEmptyElement??!1,indentText:e.indentText??!1,indentCdata:e.indentCdata??!1,attributeValueFn:e.attributeValueFn}}function P(e,t,n){return(!n&&e?`
7
- `:``)+e.repeat(t)}function F(e){let t=e.attributes;if(!t)return`<?xml version="1.0"?>`;let n=`<?xml version="1.0"`;return t.encoding&&(n+=` encoding="${t.encoding}"`),t.standalone&&(n+=` standalone="${t.standalone}"`),n+`?>`}function I(e,t,n,r){let i=[];for(let a of Object.keys(e)){let o=e[a];if(o==null)continue;let s=String(o).replace(/"/g,`&quot;`);r&&(s=r(s,a,t,n)),i.push(` ${a}="${s}"`)}return i.join(``)}function L(e,t,n){if(!e.name)return``;let r=e.name,i=e.attributes?I(e.attributes,r,e,t.attributeValueFn):``;if(!((e.elements?.length??0)>0||e.attributes?.[`xml:space`]===`preserve`||t.fullTagEmptyElement))return`<${r}${i}/>`;let a=[];a.push(`<${r}${i}>`);let o=e.elements?.some(e=>e.type===`element`)??!1;return e.elements?.length&&a.push(R(e.elements,t,n+1,!1)),t.spaces&&o&&a.push(`
8
- `+t.spaces.repeat(n)),a.push(`</${r}>`),a.join(``)}function R(e,t,n,r){let i=``;for(let a=0;a<e.length;a++){let o=e[a],s=r&&a===0;switch(o.type){case`element`:i+=P(t.spaces,n,s),i+=L(o,t,n);break;case`text`:if(t.ignoreText)continue;t.indentText&&(i+=P(t.spaces,n,s)),i+=z(o.text);break;case`cdata`:if(t.ignoreCdata)continue;t.indentCdata&&(i+=P(t.spaces,n,s)),i+=B(o.cdata);break;case`comment`:if(t.ignoreComment)continue;i+=P(t.spaces,n,s),i+=V(o.comment);break;case`doctype`:if(t.ignoreDoctype)continue;i+=P(t.spaces,n,s),i+=H(o.doctype);break;default:break}}return i}function z(e){return e==null?``:String(e).replace(/&amp;/g,`&`).replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}function B(e){return e==null?``:`<![CDATA[${e.replace(/\]\]>/g,`]]]]><![CDATA[>`)}]]>`}function V(e){return e==null?``:`<!--${e}-->`}function H(e){return e==null?``:`<!DOCTYPE ${e}>`}function U(e){let t=Object.keys(e)[0],n=e[t],r={type:`element`,name:t};if(n==null)return r;if(typeof n==`string`||typeof n==`number`||typeof n==`boolean`)return r.elements=[{type:`text`,text:String(n)}],r;if(Array.isArray(n)){let e=[];for(let t of n)t&&typeof t==`object`&&`_attr`in t?r.attributes=t._attr:t&&typeof t==`object`?Object.keys(t)[0]===`_cdata`?e.push({type:`cdata`,cdata:String(t._cdata)}):e.push(U(t)):t!=null&&e.push({type:`text`,text:String(t)});return e.length>0&&(r.elements=e),r}return typeof n==`object`&&(n._attr&&(r.attributes=n._attr),n._cdata&&(r.elements=[{type:`cdata`,cdata:String(n._cdata)}])),r}function W(e,t){return JSON.stringify(T(e,t))}export{e as allChildren,t as attr,n as attrBool,r as attrNum,_ as attrs,i as childCount,a as childText,o as children,s as collectText,c as colorAttr,g as escapeAttributeValue,h as escapeXml,l as findChild,u as findDeep,d as hasChild,j as js2xml,M as json2xml,w as nativeTypeValue,O as parseAttributes,f as textOf,U as toElement,C as unescapeXml,v as xml,T as xml2js,W as xml2json};
1
+ import { allChildren, attr, attrBool, attrNum, childCount, childText, children, collectText, colorAttr, findChild, findDeep, hasChild, textOf } from "./utils.mjs";
2
+ //#region src/escape.ts
3
+ const XML_CHAR_MAP = {
4
+ "&": "&amp;",
5
+ "\"": "&quot;",
6
+ "'": "&apos;",
7
+ "<": "&lt;",
8
+ ">": "&gt;"
9
+ };
10
+ const XML_CHAR_PATTERN = /([&"<>'])/g;
11
+ /** Escape text content for XML */
12
+ function escapeXml(str) {
13
+ return str.replace(XML_CHAR_PATTERN, (ch) => XML_CHAR_MAP[ch]);
14
+ }
15
+ /**
16
+ * Escape attribute value matching xml-js's js2xml behavior.
17
+ * Handles already-escaped entities to prevent double-escaping.
18
+ */
19
+ function escapeAttributeValue(str) {
20
+ return String(str).replace(/&(?!amp;|lt;|gt;|quot;|apos;)/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
21
+ }
22
+ /**
23
+ * Build an XML attribute string fragment from a record.
24
+ * `undefined` values are automatically skipped.
25
+ * String values are escaped via `escapeXml`.
26
+ *
27
+ * @example
28
+ * attrs({ id: 1, name: "foo", hidden: undefined })
29
+ * // => ' id="1" name="foo"'
30
+ */
31
+ function attrs(record) {
32
+ let s = "";
33
+ for (const [k, v] of Object.entries(record)) if (v !== void 0) s += ` ${k}="${typeof v === "string" ? escapeXml(v) : v}"`;
34
+ return s;
35
+ }
36
+ //#endregion
37
+ //#region src/serialize.ts
38
+ const DEFAULT_INDENT = " ";
39
+ function xml(input, options) {
40
+ const opts = normalizeOptions$1(options);
41
+ const parts = [];
42
+ if (opts.declaration) {
43
+ const declOpts = opts.declaration === true ? {} : opts.declaration;
44
+ const enc = declOpts.encoding || "UTF-8";
45
+ const sa = declOpts.standalone;
46
+ let decl = "<?xml version=\"1.0\" encoding=\"" + enc + "\"";
47
+ if (sa) decl += " standalone=\"" + sa + "\"";
48
+ decl += "?>";
49
+ parts.push(decl);
50
+ if (opts.indent) parts.push("\n");
51
+ }
52
+ const items = Array.isArray(input) ? input : [input];
53
+ for (let i = 0; i < items.length; i++) {
54
+ const keys = Object.keys(items[i]);
55
+ parts.push(formatElement(keys[0], items[i][keys[0]], opts.indent, 0));
56
+ if (opts.indent && i < items.length - 1) parts.push("\n");
57
+ }
58
+ return parts.join("");
59
+ }
60
+ function normalizeOptions$1(options) {
61
+ const opts = typeof options === "object" && !Array.isArray(options) ? options : { indent: options };
62
+ let indent = "";
63
+ if (opts.indent) indent = opts.indent === true ? DEFAULT_INDENT : String(opts.indent);
64
+ return {
65
+ indent,
66
+ declaration: opts.declaration
67
+ };
68
+ }
69
+ /**
70
+ * Single-pass XML formatter: directly converts IXmlableObject to string,
71
+ * eliminating the intermediate ResolvedElement tree.
72
+ */
73
+ function formatElement(name, values, indent, depth) {
74
+ const attrParts = [];
75
+ const textParts = [];
76
+ const elemParts = [];
77
+ let emptyArray = false;
78
+ if (values == null) {
79
+ const attrStr = attrParts.length ? " " + attrParts.join(" ") : "";
80
+ return `${indent ? indent.repeat(depth) : ""}<${name}${attrStr}/>`;
81
+ }
82
+ if (typeof values === "object") {
83
+ if (values._attr) for (const key of Object.keys(values._attr)) attrParts.push(`${key}="${escapeXml(String(values._attr[key]))}"`);
84
+ if (values._attributes) for (const key of Object.keys(values._attributes)) attrParts.push(`${key}="${escapeXml(String(values._attributes[key]))}"`);
85
+ if (values._cdata) {
86
+ const escaped = String(values._cdata).replace(/\]\]>/g, "]]]]><![CDATA[>");
87
+ textParts.push(`<![CDATA[${escaped}]]>`);
88
+ }
89
+ if (Array.isArray(values)) {
90
+ if (values.length === 0) emptyArray = true;
91
+ else for (const value of values) if (value && typeof value === "object" && "_attr" in value) for (const key of Object.keys(value._attr)) attrParts.push(`${key}="${escapeXml(String(value._attr[key]))}"`);
92
+ else if (value && typeof value === "object" && "_attributes" in value) for (const key of Object.keys(value._attributes)) attrParts.push(`${key}="${escapeXml(String(value._attributes[key]))}"`);
93
+ else if (value && typeof value === "object") {
94
+ const childKeys = Object.keys(value);
95
+ elemParts.push(formatElement(childKeys[0], value[childKeys[0]], indent, depth + 1));
96
+ } else if (value != null) textParts.push(escapeXml(String(value)));
97
+ }
98
+ } else textParts.push(escapeXml(String(values)));
99
+ const ind = indent ? indent.repeat(depth) : "";
100
+ const attrStr = attrParts.length ? " " + attrParts.join(" ") : "";
101
+ if (textParts.length + elemParts.length === 0) return emptyArray ? `${ind}<${name}${attrStr}></${name}>` : `${ind}<${name}${attrStr}/>`;
102
+ if (elemParts.length === 0 && textParts.length === 1) return indent ? `${ind}<${name}${attrStr}>${textParts[0]}</${name}>` : `<${name}${attrStr}>${textParts[0]}</${name}>`;
103
+ const parts = [];
104
+ parts.push(`${ind}<${name}${attrStr}>`);
105
+ if (indent) parts.push("\n");
106
+ const childIndent = indent ? indent.repeat(depth + 1) : "";
107
+ for (const t of textParts) {
108
+ parts.push(`${childIndent}${t}`);
109
+ if (indent) parts.push("\n");
110
+ }
111
+ for (const e of elemParts) {
112
+ parts.push(e);
113
+ if (indent) parts.push("\n");
114
+ }
115
+ parts.push(`${ind}</${name}>`);
116
+ return parts.join("");
117
+ }
118
+ //#endregion
119
+ //#region src/parse.ts
120
+ const ENTITY_MAP = {
121
+ "&amp;": "&",
122
+ "&lt;": "<",
123
+ "&gt;": ">",
124
+ "&quot;": "\"",
125
+ "&apos;": "'"
126
+ };
127
+ const ENTITY_PATTERN = /&(amp|lt|gt|quot|apos);/g;
128
+ function unescapeXml(str) {
129
+ return str.replace(ENTITY_PATTERN, (match) => ENTITY_MAP[match]);
130
+ }
131
+ function nativeTypeValue(value) {
132
+ const n = Number(value);
133
+ if (!isNaN(n)) return n;
134
+ const lower = value.toLowerCase();
135
+ if (lower === "true") return true;
136
+ if (lower === "false") return false;
137
+ return value;
138
+ }
139
+ function xml2js(xmlString, options) {
140
+ const captureSpaces = options?.captureSpacesBetweenElements ?? false;
141
+ const trim = options?.trim ?? false;
142
+ const ignoreDeclaration = options?.ignoreDeclaration ?? false;
143
+ const ignoreText = options?.ignoreText ?? false;
144
+ const ignoreComment = options?.ignoreComment ?? false;
145
+ const ignoreCdata = options?.ignoreCdata ?? false;
146
+ const ignoreDoctype = options?.ignoreDoctype ?? false;
147
+ const nativeTypeAttributes = options?.nativeTypeAttributes ?? false;
148
+ const result = {};
149
+ const stack = [result];
150
+ let i = 0;
151
+ const len = xmlString.length;
152
+ while (i < len) {
153
+ if (!captureSpaces && isWhitespace(xmlString.charCodeAt(i))) {
154
+ i++;
155
+ continue;
156
+ }
157
+ if (xmlString.charCodeAt(i) !== 60) {
158
+ const start = i;
159
+ while (i < len && xmlString.charCodeAt(i) !== 60) i++;
160
+ let text = unescapeXml(xmlString.slice(start, i));
161
+ if (trim) text = text.trim();
162
+ if (ignoreText) continue;
163
+ if (text.length > 0) {
164
+ if (captureSpaces || text.trim().length > 0) addField(stack[stack.length - 1], "text", text);
165
+ }
166
+ continue;
167
+ }
168
+ i++;
169
+ if (xmlString.charCodeAt(i) === 63) {
170
+ const end = xmlString.indexOf("?>", i + 1);
171
+ if (end === -1) break;
172
+ const body = xmlString.slice(i + 1, end);
173
+ i = end + 2;
174
+ const xmlMatch = body.match(/^xml\s+(.*)$/s);
175
+ if (xmlMatch) {
176
+ if (!ignoreDeclaration) {
177
+ if (!result.declaration) result.declaration = {};
178
+ const attrs = parseAttributes(xmlMatch[1]);
179
+ if (nativeTypeAttributes) for (const key of Object.keys(attrs)) attrs[key] = nativeTypeValue(attrs[key]);
180
+ result.declaration.attributes = attrs;
181
+ }
182
+ }
183
+ continue;
184
+ }
185
+ if (xmlString.charCodeAt(i) === 33 && xmlString.slice(i, i + 3) === "!--") {
186
+ const end = xmlString.indexOf("-->", i + 3);
187
+ if (end === -1) break;
188
+ const comment = xmlString.slice(i + 3, end);
189
+ i = end + 3;
190
+ if (!ignoreComment) if (trim) addField(stack[stack.length - 1], "comment", comment.trim());
191
+ else addField(stack[stack.length - 1], "comment", comment);
192
+ continue;
193
+ }
194
+ if (xmlString.charCodeAt(i) === 33 && xmlString.slice(i, i + 8) === "![CDATA[") {
195
+ const end = xmlString.indexOf("]]>", i + 8);
196
+ if (end === -1) break;
197
+ const cdata = xmlString.slice(i + 8, end);
198
+ i = end + 3;
199
+ if (!ignoreCdata) if (trim) addField(stack[stack.length - 1], "cdata", cdata.trim());
200
+ else addField(stack[stack.length - 1], "cdata", cdata);
201
+ continue;
202
+ }
203
+ if (xmlString.charCodeAt(i) === 33 && xmlString.slice(i, i + 9) === "!DOCTYPE") {
204
+ const end = xmlString.indexOf(">", i + 9);
205
+ if (end === -1) break;
206
+ const doctype = xmlString.slice(i + 9, end).trim();
207
+ i = end + 1;
208
+ if (!ignoreDoctype) addField(stack[stack.length - 1], "doctype", doctype);
209
+ continue;
210
+ }
211
+ if (xmlString.charCodeAt(i) === 47) {
212
+ const end = xmlString.indexOf(">", i + 1);
213
+ if (end === -1) break;
214
+ i = end + 1;
215
+ stack.pop();
216
+ continue;
217
+ }
218
+ const tagNameEnd = findTagNameEnd(xmlString, i);
219
+ const tagName = xmlString.slice(i, tagNameEnd);
220
+ let pos = tagNameEnd;
221
+ const attributes = parseAttributesFromXml(xmlString, pos);
222
+ pos = attributes.pos;
223
+ if (nativeTypeAttributes) for (const key of Object.keys(attributes.attrs)) attributes.attrs[key] = nativeTypeValue(attributes.attrs[key]);
224
+ const isSelfClosing = xmlString.charCodeAt(pos) === 47;
225
+ if (isSelfClosing) pos += 2;
226
+ else pos++;
227
+ const element = {
228
+ type: "element",
229
+ name: tagName
230
+ };
231
+ if (Object.keys(attributes.attrs).length > 0) element.attributes = attributes.attrs;
232
+ const parent = stack[stack.length - 1];
233
+ if (!parent.elements) parent.elements = [];
234
+ parent.elements.push(element);
235
+ if (!isSelfClosing) stack.push(element);
236
+ i = pos;
237
+ }
238
+ if (result.elements) {
239
+ const temp = result.elements;
240
+ delete result.elements;
241
+ result.elements = temp;
242
+ delete result.text;
243
+ }
244
+ return result;
245
+ }
246
+ function findTagNameEnd(str, start) {
247
+ let i = start;
248
+ const len = str.length;
249
+ while (i < len) {
250
+ const ch = str.charCodeAt(i);
251
+ if (ch === 32 || ch === 9 || ch === 10 || ch === 13 || ch === 47 || ch === 62) return i;
252
+ i++;
253
+ }
254
+ return i;
255
+ }
256
+ function parseAttributesFromXml(str, start) {
257
+ const attrs = {};
258
+ let i = start;
259
+ const len = str.length;
260
+ while (i < len) {
261
+ while (i < len && isWhitespace(str.charCodeAt(i))) i++;
262
+ if (i >= len || str.charCodeAt(i) === 62 || str.charCodeAt(i) === 47) break;
263
+ const nameStart = i;
264
+ while (i < len && str.charCodeAt(i) !== 61) {
265
+ if (str.charCodeAt(i) === 62 || str.charCodeAt(i) === 47) break;
266
+ i++;
267
+ }
268
+ const name = str.slice(nameStart, i);
269
+ if (str.charCodeAt(i) !== 61) break;
270
+ i++;
271
+ while (i < len && isWhitespace(str.charCodeAt(i))) i++;
272
+ const quote = str.charCodeAt(i);
273
+ if (quote !== 34 && quote !== 39) break;
274
+ i++;
275
+ const valueStart = i;
276
+ while (i < len && str.charCodeAt(i) !== quote) i++;
277
+ attrs[name] = str.slice(valueStart, i);
278
+ i++;
279
+ }
280
+ return {
281
+ attrs,
282
+ pos: i
283
+ };
284
+ }
285
+ function parseAttributes(str) {
286
+ const result = {};
287
+ let i = 0;
288
+ const len = str.length;
289
+ while (i < len) {
290
+ while (i < len && isWhitespace(str.charCodeAt(i))) i++;
291
+ if (i >= len) break;
292
+ const nameStart = i;
293
+ while (i < len && str.charCodeAt(i) !== 61) {
294
+ if (isWhitespace(str.charCodeAt(i))) break;
295
+ i++;
296
+ }
297
+ const name = str.slice(nameStart, i);
298
+ while (i < len && isWhitespace(str.charCodeAt(i))) i++;
299
+ if (i >= len || str.charCodeAt(i) !== 61) break;
300
+ i++;
301
+ while (i < len && isWhitespace(str.charCodeAt(i))) i++;
302
+ const quote = str.charCodeAt(i);
303
+ if (quote !== 34 && quote !== 39) break;
304
+ i++;
305
+ const valueStart = i;
306
+ while (i < len && str.charCodeAt(i) !== quote) i++;
307
+ result[name] = str.slice(valueStart, i);
308
+ i++;
309
+ }
310
+ return result;
311
+ }
312
+ function addField(parent, type, value) {
313
+ if (!parent.elements) parent.elements = [];
314
+ const element = { type };
315
+ element[type] = value;
316
+ parent.elements.push(element);
317
+ }
318
+ function isWhitespace(ch) {
319
+ return ch === 32 || ch === 9 || ch === 10 || ch === 13;
320
+ }
321
+ //#endregion
322
+ //#region src/stringify.ts
323
+ function js2xml(js, options) {
324
+ const opts = normalizeOptions(options);
325
+ const parts = [];
326
+ if (js.declaration && !opts.ignoreDeclaration) parts.push(writeDeclaration(js.declaration));
327
+ if (js.elements?.length) parts.push(writeElements(js.elements, opts, 0, !parts.length));
328
+ return parts.join("");
329
+ }
330
+ /** Alias for js2xml — xml-js compatible export */
331
+ function json2xml(json, options) {
332
+ return js2xml(json, options);
333
+ }
334
+ function normalizeOptions(options) {
335
+ if (!options) return {
336
+ spaces: "",
337
+ ignoreDeclaration: false,
338
+ ignoreText: false,
339
+ ignoreComment: false,
340
+ ignoreCdata: false,
341
+ ignoreDoctype: false,
342
+ fullTagEmptyElement: false,
343
+ indentText: false,
344
+ indentCdata: false
345
+ };
346
+ let spaces = "";
347
+ if (options.spaces != null) spaces = typeof options.spaces === "number" ? " ".repeat(options.spaces) : options.spaces;
348
+ return {
349
+ spaces,
350
+ ignoreDeclaration: options.ignoreDeclaration ?? false,
351
+ ignoreText: options.ignoreText ?? false,
352
+ ignoreComment: options.ignoreComment ?? false,
353
+ ignoreCdata: options.ignoreCdata ?? false,
354
+ ignoreDoctype: options.ignoreDoctype ?? false,
355
+ fullTagEmptyElement: options.fullTagEmptyElement ?? false,
356
+ indentText: options.indentText ?? false,
357
+ indentCdata: options.indentCdata ?? false,
358
+ attributeValueFn: options.attributeValueFn
359
+ };
360
+ }
361
+ function writeIndentation(spaces, depth, firstLine) {
362
+ return (!firstLine && spaces ? "\n" : "") + spaces.repeat(depth);
363
+ }
364
+ function writeDeclaration(declaration) {
365
+ const attrs = declaration.attributes;
366
+ if (!attrs) return "<?xml version=\"1.0\"?>";
367
+ let result = "<?xml version=\"1.0\"";
368
+ if (attrs.encoding) result += ` encoding="${attrs.encoding}"`;
369
+ if (attrs.standalone) result += ` standalone="${attrs.standalone}"`;
370
+ return result + "?>";
371
+ }
372
+ function writeAttributes(attributes, elementName, element, attributeValueFn) {
373
+ const parts = [];
374
+ for (const key of Object.keys(attributes)) {
375
+ const value = attributes[key];
376
+ if (value === null || value === void 0) continue;
377
+ let attr = String(value).replace(/"/g, "&quot;");
378
+ if (attributeValueFn) attr = attributeValueFn(attr, key, elementName, element);
379
+ parts.push(` ${key}="${attr}"`);
380
+ }
381
+ return parts.join("");
382
+ }
383
+ function writeElement(element, opts, depth) {
384
+ if (!element.name) return "";
385
+ const name = element.name;
386
+ const attrStr = element.attributes ? writeAttributes(element.attributes, name, element, opts.attributeValueFn) : "";
387
+ if (!((element.elements?.length ?? 0) > 0 || element.attributes?.["xml:space"] === "preserve" || opts.fullTagEmptyElement)) return `<${name}${attrStr}/>`;
388
+ const parts = [];
389
+ parts.push(`<${name}${attrStr}>`);
390
+ const hasChildElements = element.elements?.some((e) => e.type === "element") ?? false;
391
+ if (element.elements?.length) parts.push(writeElements(element.elements, opts, depth + 1, false));
392
+ if (opts.spaces && hasChildElements) parts.push("\n" + opts.spaces.repeat(depth));
393
+ parts.push(`</${name}>`);
394
+ return parts.join("");
395
+ }
396
+ function writeElements(elements, opts, depth, firstLine) {
397
+ let result = "";
398
+ for (let i = 0; i < elements.length; i++) {
399
+ const element = elements[i];
400
+ const isFirst = firstLine && i === 0;
401
+ switch (element.type) {
402
+ case "element":
403
+ result += writeIndentation(opts.spaces, depth, isFirst);
404
+ result += writeElement(element, opts, depth);
405
+ break;
406
+ case "text":
407
+ if (opts.ignoreText) continue;
408
+ if (opts.indentText) result += writeIndentation(opts.spaces, depth, isFirst);
409
+ result += writeText(element.text);
410
+ break;
411
+ case "cdata":
412
+ if (opts.ignoreCdata) continue;
413
+ if (opts.indentCdata) result += writeIndentation(opts.spaces, depth, isFirst);
414
+ result += writeCdata(element.cdata);
415
+ break;
416
+ case "comment":
417
+ if (opts.ignoreComment) continue;
418
+ result += writeIndentation(opts.spaces, depth, isFirst);
419
+ result += writeComment(element.comment);
420
+ break;
421
+ case "doctype":
422
+ if (opts.ignoreDoctype) continue;
423
+ result += writeIndentation(opts.spaces, depth, isFirst);
424
+ result += writeDoctype(element.doctype);
425
+ break;
426
+ default: break;
427
+ }
428
+ }
429
+ return result;
430
+ }
431
+ function writeText(text) {
432
+ if (text == null) return "";
433
+ return String(text).replace(/&amp;/g, "&").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
434
+ }
435
+ function writeCdata(cdata) {
436
+ if (cdata == null) return "";
437
+ return `<![CDATA[${cdata.replace(/\]\]>/g, "]]]]><![CDATA[>")}]]>`;
438
+ }
439
+ function writeComment(comment) {
440
+ if (comment == null) return "";
441
+ return `<!--${comment}-->`;
442
+ }
443
+ function writeDoctype(doctype) {
444
+ if (doctype == null) return "";
445
+ return `<!DOCTYPE ${doctype}>`;
446
+ }
447
+ //#endregion
448
+ //#region src/convert.ts
449
+ /**
450
+ * Convert XmlObject (node-xml format) directly to Element (xml-js format).
451
+ * Eliminates the redundant xml() → xml2js() bridge path.
452
+ */
453
+ function toElement(xmlObject) {
454
+ const tagName = Object.keys(xmlObject)[0];
455
+ const value = xmlObject[tagName];
456
+ const element = {
457
+ type: "element",
458
+ name: tagName
459
+ };
460
+ if (value == null) return element;
461
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
462
+ element.elements = [{
463
+ type: "text",
464
+ text: String(value)
465
+ }];
466
+ return element;
467
+ }
468
+ if (Array.isArray(value)) {
469
+ const children = [];
470
+ for (const item of value) if (item && typeof item === "object" && "_attr" in item) element.attributes = item._attr;
471
+ else if (item && typeof item === "object") if (Object.keys(item)[0] === "_cdata") children.push({
472
+ type: "cdata",
473
+ cdata: String(item._cdata)
474
+ });
475
+ else children.push(toElement(item));
476
+ else if (item != null) children.push({
477
+ type: "text",
478
+ text: String(item)
479
+ });
480
+ if (children.length > 0) element.elements = children;
481
+ return element;
482
+ }
483
+ if (typeof value === "object") {
484
+ if (value._attr) element.attributes = value._attr;
485
+ if (value._cdata) element.elements = [{
486
+ type: "cdata",
487
+ cdata: String(value._cdata)
488
+ }];
489
+ }
490
+ return element;
491
+ }
492
+ //#endregion
493
+ //#region src/json.ts
494
+ /** Convert XML string to JSON string — xml-js compatible export */
495
+ function xml2json(xml, options) {
496
+ return JSON.stringify(xml2js(xml, options));
497
+ }
498
+ //#endregion
499
+ export { allChildren, attr, attrBool, attrNum, attrs, childCount, childText, children, collectText, colorAttr, escapeAttributeValue, escapeXml, findChild, findDeep, hasChild, js2xml, json2xml, nativeTypeValue, parseAttributes, textOf, toElement, unescapeXml, xml, xml2js, xml2json };
package/dist/utils.mjs CHANGED
@@ -1 +1,116 @@
1
- function e(e,t){return e?.elements?.find(e=>e.name===t)}function t(e,t){return e?.elements?.filter(e=>e.name===t)??[]}function n(e){return e?.elements??[]}function r(t,n){return i(e(t,n))}function i(e){return e?e.text!==void 0&&typeof e.text==`string`?e.text:e.elements&&e.elements.length>0?e.elements.map(e=>typeof e.text==`string`?e.text:``).join(``):``:``}function a(e){if(!e)return``;let t=[];return o(e,t),t.join(``)}function o(e,t){if(e&&(e.text!==void 0&&typeof e.text==`string`&&t.push(e.text),e.elements))for(let n of e.elements)o(n,t)}function s(e,t){let n=e?.attributes?.[t];return n===void 0?void 0:String(n)}function c(e,t){let n=e?.attributes?.[t];if(n===void 0)return;let r=Number(n);return isNaN(r)?void 0:r}function l(e,t){let n=e?.attributes?.[t];if(n===void 0)return;if(typeof n==`boolean`)return n;let r=String(n).toLowerCase();if(r===`true`||r===`1`)return!0;if(r===`false`||r===`0`)return!1}function u(e,t){let n=e?.attributes?.[t];if(!(n===void 0||n===``)&&typeof n!=`boolean`)return typeof n==`number`?String(n).padStart(6,`0`):n===`auto`?`auto`:(/^[0-9A-Fa-f]{6}$/.test(n),n)}function d(e,t){return e?.elements?.some(e=>e.name===t)??!1}function f(e,t){let n=[];if(!e)return n;for(let r of e.elements??[])r.name===t&&n.push(r),n.push(...f(r,t));return n}function p(e){return e?.elements?.length??0}export{n as allChildren,s as attr,l as attrBool,c as attrNum,p as childCount,r as childText,t as children,a as collectText,u as colorAttr,e as findChild,f as findDeep,d as hasChild,i as textOf};
1
+ //#region src/utils.ts
2
+ /**
3
+ * Find the first direct child element with the given name.
4
+ */
5
+ function findChild(parent, name) {
6
+ return parent?.elements?.find((e) => e.name === name);
7
+ }
8
+ /**
9
+ * Get all direct child elements matching the given name.
10
+ */
11
+ function children(parent, name) {
12
+ return parent?.elements?.filter((e) => e.name === name) ?? [];
13
+ }
14
+ /**
15
+ * Get all direct child elements.
16
+ */
17
+ function allChildren(parent) {
18
+ return parent?.elements ?? [];
19
+ }
20
+ /**
21
+ * Get text content of the first child element with the given name.
22
+ */
23
+ function childText(parent, name) {
24
+ return textOf(findChild(parent, name));
25
+ }
26
+ /**
27
+ * Get text content of an element.
28
+ * Handles cases where text may be directly on .text or in a child element.
29
+ */
30
+ function textOf(element) {
31
+ if (!element) return "";
32
+ if (element.text !== void 0 && typeof element.text === "string") return element.text;
33
+ if (element.elements && element.elements.length > 0) return element.elements.map((e) => typeof e.text === "string" ? e.text : "").join("");
34
+ return "";
35
+ }
36
+ /**
37
+ * Collect text from all direct text nodes within an element.
38
+ */
39
+ function collectText(element) {
40
+ if (!element) return "";
41
+ const parts = [];
42
+ collectTextRecursive(element, parts);
43
+ return parts.join("");
44
+ }
45
+ function collectTextRecursive(element, parts) {
46
+ if (!element) return;
47
+ if (element.text !== void 0 && typeof element.text === "string") parts.push(element.text);
48
+ if (element.elements) for (const child of element.elements) collectTextRecursive(child, parts);
49
+ }
50
+ /**
51
+ * Get an attribute value as a string.
52
+ */
53
+ function attr(element, name) {
54
+ const v = element?.attributes?.[name];
55
+ return v !== void 0 ? String(v) : void 0;
56
+ }
57
+ /**
58
+ * Get an attribute value as a number.
59
+ */
60
+ function attrNum(element, name) {
61
+ const v = element?.attributes?.[name];
62
+ if (v === void 0) return void 0;
63
+ const n = Number(v);
64
+ return isNaN(n) ? void 0 : n;
65
+ }
66
+ /**
67
+ * Get an attribute value as a boolean.
68
+ */
69
+ function attrBool(element, name) {
70
+ const v = element?.attributes?.[name];
71
+ if (v === void 0) return void 0;
72
+ if (typeof v === "boolean") return v;
73
+ const lower = String(v).toLowerCase();
74
+ if (lower === "true" || lower === "1") return true;
75
+ if (lower === "false" || lower === "0") return false;
76
+ }
77
+ /**
78
+ * Get a hex color attribute, handling nativeTypeValue coercion.
79
+ * nativeTypeAttributes converts "000000" → 0 (number); this recovers
80
+ * the original 6-digit hex string by zero-padding numeric values.
81
+ */
82
+ function colorAttr(element, name) {
83
+ const raw = element?.attributes?.[name];
84
+ if (raw === void 0 || raw === "") return void 0;
85
+ if (typeof raw === "boolean") return void 0;
86
+ if (typeof raw === "number") return String(raw).padStart(6, "0");
87
+ if (raw === "auto") return "auto";
88
+ if (/^[0-9A-Fa-f]{6}$/.test(raw)) return raw;
89
+ return raw;
90
+ }
91
+ /**
92
+ * Check if an element has a specific child element.
93
+ */
94
+ function hasChild(parent, name) {
95
+ return parent?.elements?.some((e) => e.name === name) ?? false;
96
+ }
97
+ /**
98
+ * Find deep descendant elements matching the given name.
99
+ */
100
+ function findDeep(parent, name) {
101
+ const result = [];
102
+ if (!parent) return result;
103
+ for (const child of parent.elements ?? []) {
104
+ if (child.name === name) result.push(child);
105
+ result.push(...findDeep(child, name));
106
+ }
107
+ return result;
108
+ }
109
+ /**
110
+ * Get the number of direct child elements.
111
+ */
112
+ function childCount(parent) {
113
+ return parent?.elements?.length ?? 0;
114
+ }
115
+ //#endregion
116
+ export { allChildren, attr, attrBool, attrNum, childCount, childText, children, collectText, colorAttr, findChild, findDeep, hasChild, textOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-open/xml",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "XML parsing and serialization for Office Open XML. Zero dependencies, drop-in replacement for xml + xml-js.",
5
5
  "keywords": [
6
6
  "office-open",