@ooxml-tools/xml 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ <h1>
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://ooxml-tools.github.io/design/images/xml-dark.png">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://ooxml-tools.github.io/design/images/xml-light.png">
5
+ <img alt="@ooxml-tools/xml" height="56" src="https://ooxml-tools.github.io/design/images/xml-light.png">
6
+ </picture>
7
+ </h1>
8
+
9
+ Some XML helpers to help with OOXML development
10
+
11
+ > [!NOTE]
12
+ > These are all based around [`xml-js`](https://www.npmjs.com/package/xml-js) and output that modules internal format
13
+
14
+
15
+ ## API
16
+
17
+ ### `asXmlElement`
18
+ When working with `xml-js` it's really handy to not have to worry about when you're passing an XML string our the `xml-js` JSON type called `Element`
19
+
20
+ This helper function just outputs an `Element` for either input
21
+
22
+ ```ts
23
+ asXmlElement("<name>foo</name>") // => {name: "name", text: "foo"}
24
+ asXmlElement({name: "name", text: "foo"}) // => {name: "name", text: "foo"}
25
+ ```
26
+
27
+ > [!NOTE]
28
+ > All the other functions in this document that accept XML, also accept this `Element` (using this function)
29
+
30
+ ### `safeXml`
31
+ An XML tagged template literal with the following features
32
+
33
+ - error if XML is incorrect
34
+ - array support in substitution
35
+
36
+ The following will error in development, because of mismatched start/end tags
37
+
38
+ ```ts
39
+ safeXml`<foo>hello</bar>`
40
+ ```
41
+
42
+ Substitution of arrays "just works" so you can map values in the tagged template literals
43
+
44
+ ```ts
45
+ const outXml = safeXml`<test>${[1,2,3].map(n => safeXml`<name>item ${1}<name>`)}</test>`
46
+ assert.equal(outXml, `<test><name>item 1</name><name>item 2</name><name>item 3</name></test>`);
47
+ ```
48
+
49
+ This also makes it easy to support <https://prettier.io/docs/en/options#embedded-language-formatting>
50
+
51
+ ### `compact`
52
+ Removes non required whitespace from the XML
53
+
54
+ ```ts
55
+ const outXml = compact(`
56
+ <test>
57
+ something
58
+ </test>
59
+ `);
60
+ assert.equal(outXml, "<test>something</test>");
61
+ ```
62
+
63
+ ### `removeFragments`
64
+ For XML to be valid, there must be a single root node. When composing apps it's handy for that not to be true.
65
+
66
+ For example the following would error
67
+
68
+ ```ts
69
+ const xml = safeXml`
70
+ <name>foo</name>
71
+ <name>bar</name>
72
+ `
73
+ ```
74
+
75
+ So instead we'd like something like a HTML fragment
76
+
77
+ ```ts
78
+ const xml = safeXml`
79
+ <XML_FRAGMENT>
80
+ <name>foo</name>
81
+ <name>bar</name>
82
+ </XML_FRAGMENT>
83
+ `
84
+ ```
85
+
86
+ So we did just that, `removeFragments` walks over a tree removing `<XML_FRAGMENT>...</XML_FRAGMENT>` nodes.
87
+
88
+ ```ts
89
+ const innerBit = safeXml`
90
+ <XML_FRAGMENT>
91
+ <name>foo</name>
92
+ <name>bar</name>
93
+ </XML_FRAGMENT>
94
+ `
95
+ const newXml = removeFragments(
96
+ safeXml`
97
+ <doc>
98
+ ${innerBit}
99
+ </doc>
100
+ `
101
+ )
102
+ assert.equal(newXml, `
103
+ <doc>
104
+ <name>foo</name>
105
+ <name>bar</name>
106
+ </doc>
107
+ `)
108
+ ```
109
+
110
+ ### `cdata`
111
+ From [the wikipedia page](https://en.wikipedia.org/wiki/CDATA)
112
+
113
+ > CDATA section is a piece of element content that is marked up to be interpreted literally, as textual data, not as marked-up content.
114
+
115
+ But `<name><![CDATA[one < two]]><name>` is ugly and hard to read, so instead
116
+
117
+ ```ts
118
+ safeXml`<name>${cdata("one < two")}</name>`
119
+ ```
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,101 @@
1
+ import { xml2js, js2xml } from 'xml-js';
2
+
3
+ function asXmlElement(xml) {
4
+ if (typeof xml === "string") {
5
+ return xml2js(xml, {
6
+ compact: false,
7
+ captureSpacesBetweenElements: false,
8
+ });
9
+ }
10
+ else {
11
+ return xml;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * @param templateStrings template strings
17
+ * @param templateValues tamplate values
18
+ * @returns xml string
19
+ */
20
+ function safeXml(templateStrings, ...templateValues) {
21
+ let xml = "";
22
+ for (let i = 0; i < templateStrings.length; i++) {
23
+ if (templateValues[i] !== undefined) {
24
+ const value = templateValues[i];
25
+ const xmlPart = Array.isArray(value) ? value.join("") : value;
26
+ xml += `${templateStrings[i]}${xmlPart}`;
27
+ }
28
+ else {
29
+ xml += `${templateStrings[i]}`;
30
+ }
31
+ }
32
+ if (process.env.NODE_ENV === "development") {
33
+ // This will "throw" if the XML is invalid
34
+ xml2js(xml, {
35
+ compact: false,
36
+ captureSpacesBetweenElements: false,
37
+ });
38
+ }
39
+ return xml;
40
+ }
41
+
42
+ function cdata(input) {
43
+ return `<![CDATA[${input}]]>`;
44
+ }
45
+
46
+ function compact(xml) {
47
+ const parsed = xml2js(xml, {
48
+ compact: false,
49
+ trim: true,
50
+ });
51
+ return js2xml(parsed);
52
+ }
53
+
54
+ function createFragment(elements) {
55
+ return {
56
+ type: "element",
57
+ name: "XML_FRAGMENT",
58
+ elements: elements,
59
+ };
60
+ }
61
+ function _collapseFragments(orig) {
62
+ var _a;
63
+ let node = orig;
64
+ const cloneIfRequired = () => {
65
+ if (node === orig) {
66
+ return Object.assign(Object.assign({}, node), { elements: node.elements ? [...node.elements] : undefined });
67
+ }
68
+ return node;
69
+ };
70
+ if (node.elements) {
71
+ for (let i = node.elements.length - 1; i >= 0; i--) {
72
+ if (node.elements) {
73
+ const child = node.elements[i];
74
+ const out = _collapseFragments(child);
75
+ if (Array.isArray(out)) {
76
+ node = cloneIfRequired();
77
+ node.elements.splice(i, 1, ...out);
78
+ }
79
+ else if (out !== child) {
80
+ node = cloneIfRequired();
81
+ if (node.elements) {
82
+ node.elements[i] = out;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ if (node.name === "XML_FRAGMENT") {
89
+ return (_a = node.elements) !== null && _a !== void 0 ? _a : [];
90
+ }
91
+ return node;
92
+ }
93
+ function collapseFragments(root) {
94
+ const out = _collapseFragments(asXmlElement(root));
95
+ if (Array.isArray(out)) {
96
+ return createFragment(out);
97
+ }
98
+ return out;
99
+ }
100
+
101
+ export { asXmlElement, cdata, collapseFragments, compact, createFragment, safeXml };
@@ -0,0 +1,19 @@
1
+ import { Element } from 'xml-js';
2
+
3
+ declare function asXmlElement(xml: string | Element): Element;
4
+
5
+ /**
6
+ * @param templateStrings template strings
7
+ * @param templateValues tamplate values
8
+ * @returns xml string
9
+ */
10
+ declare function safeXml(templateStrings: TemplateStringsArray, ...templateValues: (string | number | (string | number)[])[]): string;
11
+
12
+ declare function cdata(input: string | number): string;
13
+
14
+ declare function compact(xml: string): string;
15
+
16
+ declare function createFragment(elements: Element[]): Element;
17
+ declare function collapseFragments(root: string | Element): Element;
18
+
19
+ export { asXmlElement, cdata, collapseFragments, compact, createFragment, safeXml };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@ooxml-tools/xml",
3
+ "description": "Some XML helpers to help with OOXML development",
4
+ "version": "0.1.0",
5
+ "license": "MIT",
6
+ "main": "./dist/npm/index.js",
7
+ "types": "./dist/npm/types.d.ts",
8
+ "type": "module",
9
+ "scripts": {
10
+ "lint": "npx prettier . --check",
11
+ "test": "vitest",
12
+ "lint:format": "npx prettier . --write",
13
+ "build": "rollup -c rollup.config.ts --configPlugin typescript"
14
+ },
15
+ "exports": {
16
+ ".": "./dist/npm/index.js",
17
+ "./commands": "./dist/npm/commands.js"
18
+ },
19
+ "files": [
20
+ "./dist/npm",
21
+ "./package.json",
22
+ "./README.md"
23
+ ],
24
+ "devDependencies": {
25
+ "@rollup/plugin-json": "^6.1.0",
26
+ "@rollup/plugin-typescript": "^12.1.2",
27
+ "@rollup/plugin-virtual": "^3.0.2",
28
+ "@tsconfig/node22": "^22.0.0",
29
+ "@types/yargs": "^17.0.32",
30
+ "prettier": "^3.4.2",
31
+ "rollup": "^4.18.1",
32
+ "rollup-plugin-copy": "^3.5.0",
33
+ "rollup-plugin-dts": "^6.1.1",
34
+ "rollup-plugin-preserve-shebang": "^1.0.1",
35
+ "rollup-plugin-typescript-paths": "^1.5.0",
36
+ "ts-node": "^10.9.2",
37
+ "tslib": "^2.6.3",
38
+ "tsx": "^4.17.0",
39
+ "typescript": "^5.5.4",
40
+ "vitest": "^2.1.6"
41
+ },
42
+ "engines": {
43
+ "node": ">=20.x"
44
+ },
45
+ "dependencies": {
46
+ "xml-js": "^1.6.11"
47
+ }
48
+ }