@ooxml-tools/xml 0.2.1 → 0.2.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/README.md CHANGED
@@ -11,45 +11,50 @@ Some XML helpers to help with OOXML development
11
11
  > [!NOTE]
12
12
  > These are all based around [`xml-js`](https://www.npmjs.com/package/xml-js) and output that modules internal format
13
13
 
14
-
15
14
  ## API
16
15
 
17
16
  ### `asXmlElement`
17
+
18
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
19
 
20
20
  This helper function just outputs an `Element` for either input
21
21
 
22
22
  ```ts
23
- asXmlElement("<name>foo</name>") // => {name: "name", text: "foo"}
24
- asXmlElement({name: "name", text: "foo"}) // => {name: "name", text: "foo"}
23
+ asXmlElement("<name>foo</name>"); // => {name: "name", text: "foo"}
24
+ asXmlElement({ name: "name", text: "foo" }); // => {name: "name", text: "foo"}
25
25
  ```
26
26
 
27
27
  > [!NOTE]
28
- > All the other functions in this document that accept XML, also accept this `Element` (using this function)
28
+ > All the other functions in this document that accept XML, also accept this `Element` (using this function)
29
29
 
30
30
  ### `safeXml`
31
+
31
32
  An XML tagged template literal with the following features
32
33
 
33
- - error if XML is incorrect
34
- - array support in substitution
34
+ - error if XML is incorrect
35
+ - array support in substitution
36
+
37
+ The following will error in development, because of mismatched start/end tags
35
38
 
36
- The following will error in development, because of mismatched start/end tags
37
-
38
39
  ```ts
39
- safeXml`<foo>hello</bar>`
40
+ safeXml`<foo>hello</bar>`;
40
41
  ```
41
42
 
42
- Substitution of arrays "just works" so you can map values in the tagged template literals
43
-
43
+ Substitution of arrays "just works" so you can map values in the tagged template literals
44
+
44
45
  ```ts
45
- const items = [1,2,3].map(n => safeXml`<name>item ${1}</name>`);
46
- const outXml = safeXml`<test>${items}</test>`
47
- assert.equal(outXml, `<test><name>item 1</name><name>item 2</name><name>item 3</name></test>`);
46
+ const items = [1, 2, 3].map((n) => safeXml`<name>item ${1}</name>`);
47
+ const outXml = safeXml`<test>${items}</test>`;
48
+ assert.equal(
49
+ outXml,
50
+ `<test><name>item 1</name><name>item 2</name><name>item 3</name></test>`,
51
+ );
48
52
  ```
49
53
 
50
54
  This also makes it easy to support <https://prettier.io/docs/en/options#embedded-language-formatting>
51
55
 
52
56
  ### `compact`
57
+
53
58
  Removes non required whitespace from the XML
54
59
 
55
60
  ```ts
@@ -62,6 +67,7 @@ assert.equal(outXml, "<test>something</test>");
62
67
  ```
63
68
 
64
69
  ### `collapseFragments`
70
+
65
71
  For XML to be valid, there must be a single root node. When composing apps it's handy for that not to be true.
66
72
 
67
73
  For example the following would error
@@ -70,7 +76,7 @@ For example the following would error
70
76
  const xml = safeXml`
71
77
  <name>foo</name>
72
78
  <name>bar</name>
73
- `
79
+ `;
74
80
  ```
75
81
 
76
82
  So instead we'd like something like a HTML fragment
@@ -81,7 +87,7 @@ const xml = safeXml`
81
87
  <name>foo</name>
82
88
  <name>bar</name>
83
89
  </XML_FRAGMENT>
84
- `
90
+ `;
85
91
  ```
86
92
 
87
93
  So we did just that, `collapseFragments` walks over a tree removing `<XML_FRAGMENT>...</XML_FRAGMENT>` nodes.
@@ -92,23 +98,27 @@ const innerBit = safeXml`
92
98
  <name>foo</name>
93
99
  <name>bar</name>
94
100
  </XML_FRAGMENT>
95
- `
101
+ `;
96
102
  const newXml = collapseFragments(
97
- safeXml`
103
+ safeXml`
98
104
  <doc>
99
105
  ${innerBit}
100
106
  </doc>
101
- `
102
- )
103
- assert.equal(newXml, `
107
+ `,
108
+ );
109
+ assert.equal(
110
+ newXml,
111
+ `
104
112
  <doc>
105
113
  <name>foo</name>
106
114
  <name>bar</name>
107
115
  </doc>
108
- `)
116
+ `,
117
+ );
109
118
  ```
110
119
 
111
120
  ### `cdata`
121
+
112
122
  From [the wikipedia page](https://en.wikipedia.org/wiki/CDATA)
113
123
 
114
124
  > CDATA section is a piece of element content that is marked up to be interpreted literally, as textual data, not as marked-up content.
@@ -116,28 +126,85 @@ From [the wikipedia page](https://en.wikipedia.org/wiki/CDATA)
116
126
  But `<name><![CDATA[one < two]]><name>` is ugly and hard to read, so instead
117
127
 
118
128
  ```ts
119
- safeXml`<name>${cdata("one < two")}</name>`
129
+ safeXml`<name>${cdata("one < two")}</name>`;
120
130
  ```
121
131
 
122
132
  ### `format`
133
+
123
134
  Format XML in a consistent way, useful for logging and snapshot testing
124
135
 
125
136
  ```ts
126
137
  format(`
127
138
  <test> one
128
139
  </test>
129
- `) /* =>
140
+ `); /* =>
130
141
  * <test>
131
142
  * one
132
143
  * </test>
133
144
  */
134
145
  ```
135
146
 
147
+ ## `getAllByTestId`
148
+
149
+ ```ts
150
+ const element = getAllByTestId(`
151
+ <root>
152
+ <name _testid="test">
153
+ Hello
154
+ </name>
155
+ </test>
156
+ `)
157
+ ```
158
+
159
+ ## `getByTestId`
160
+
161
+ ```ts
162
+ const element = getByTestId(`
163
+ <root>
164
+ <name _testid="test">
165
+ Hello
166
+ </name>
167
+ </test>
168
+ `)
169
+ ```
170
+
171
+ ## `getSingleTextNode`
172
+ Returns a single text node if one exists
173
+
174
+ ```ts
175
+ getSingleTextNode(`<test>hello world</test>`) // => {type: "text", text: "hello world"}
176
+ ```
177
+
178
+ Else throws an error
179
+
180
+ ```ts
181
+ getSingleTextNode(`<test>hello <foo>inner node</foo> world</test>`) // => throws Error
182
+ ```
183
+
184
+ ## `removeTestIds`
185
+
186
+ ```ts
187
+ const element = removeTestIds(`
188
+ <root>
189
+ <name _testid="test">
190
+ Hello
191
+ </name>
192
+ </test>
193
+ `) /* =>
194
+ * <root>
195
+ * <name>
196
+ * Hello
197
+ * </name>
198
+ * </test>
199
+ */
200
+ ```
201
+
202
+
203
+
136
204
  ## CI
137
205
 
138
206
  [![codecov](https://codecov.io/gh/ooxml-tools/xml/graph/badge.svg?token=N82AKMVJM7)](https://codecov.io/gh/ooxml-tools/xml)
139
207
 
140
-
141
208
  ## License
142
209
 
143
210
  MIT
package/dist/npm/index.js CHANGED
@@ -51,13 +51,6 @@ function compact(xml) {
51
51
  return js2xml(parsed);
52
52
  }
53
53
 
54
- function createFragment(elements) {
55
- return {
56
- type: "element",
57
- name: "XML_FRAGMENT",
58
- elements: elements,
59
- };
60
- }
61
54
  function _collapseFragments(orig) {
62
55
  var _a;
63
56
  let node = orig;
@@ -92,9 +85,6 @@ function _collapseFragments(orig) {
92
85
  }
93
86
  function collapseFragments(root) {
94
87
  const out = _collapseFragments(asXmlElement(root));
95
- if (Array.isArray(out)) {
96
- return createFragment(out);
97
- }
98
88
  return out;
99
89
  }
100
90
 
@@ -112,4 +102,63 @@ function format(input, { spaces = 2 } = {}) {
112
102
  });
113
103
  }
114
104
 
115
- export { asXmlElement, cdata, collapseFragments, compact, createFragment, format, safeXml };
105
+ function createFragment(elements) {
106
+ return {
107
+ type: "element",
108
+ name: "XML_FRAGMENT",
109
+ elements: elements,
110
+ };
111
+ }
112
+
113
+ const TEST_ATTR_NAME = "_testid";
114
+
115
+ function getAllByTestIdRecursive(root, id, collection) {
116
+ var _a;
117
+ const testAttrValue = (_a = root.attributes) === null || _a === void 0 ? void 0 : _a[TEST_ATTR_NAME];
118
+ if (testAttrValue === id) {
119
+ collection.push(root);
120
+ }
121
+ if (root.elements) {
122
+ for (const element of root.elements) {
123
+ getAllByTestIdRecursive(element, id, collection);
124
+ }
125
+ }
126
+ return collection;
127
+ }
128
+ function getAllByTestId(root, id) {
129
+ return getAllByTestIdRecursive(root, id, []);
130
+ }
131
+
132
+ function getByTestId(root, id) {
133
+ const elements = getAllByTestId(root, id);
134
+ if (elements.length > 1) {
135
+ throw new Error(`Only expected 1 element with ${TEST_ATTR_NAME}='${id}'`);
136
+ }
137
+ return elements[0];
138
+ }
139
+
140
+ function getSingleTextNode(element) {
141
+ if (element) {
142
+ const elements = element.elements;
143
+ if (elements && elements.length === 1 && elements[0].type === "text") {
144
+ return elements[0];
145
+ }
146
+ }
147
+ throw new Error("Not a single text node");
148
+ }
149
+
150
+ function removeTestIdsRecursive(root) {
151
+ var _a;
152
+ const attributes = root.attributes ? Object.assign({}, root.attributes) : undefined;
153
+ if (attributes) {
154
+ delete attributes[TEST_ATTR_NAME];
155
+ }
156
+ return Object.assign(Object.assign({}, root), { attributes, elements: (_a = root.elements) === null || _a === void 0 ? void 0 : _a.map((element) => {
157
+ return removeTestIdsRecursive(element);
158
+ }) });
159
+ }
160
+ function removeTestIds(root) {
161
+ return removeTestIdsRecursive(root);
162
+ }
163
+
164
+ export { asXmlElement, cdata, collapseFragments, compact, createFragment, format, getAllByTestId, getByTestId, getSingleTextNode, removeTestIds, safeXml };
@@ -1,4 +1,4 @@
1
- import { Element } from 'xml-js';
1
+ import { Element, ElementCompact } from 'xml-js';
2
2
 
3
3
  declare function asXmlElement(xml: string | Element): Element;
4
4
 
@@ -13,13 +13,22 @@ declare function cdata(input: string | number): string;
13
13
 
14
14
  declare function compact(xml: string): string;
15
15
 
16
- declare function createFragment(elements: Element[]): Element;
17
- declare function collapseFragments(root: string | Element): Element;
16
+ declare function collapseFragments(root: string | Element): Element | Element[];
18
17
 
19
18
  type FormatOptions = {
20
19
  spaces?: number;
21
20
  };
22
21
  declare function format(input: string, { spaces }?: FormatOptions): string;
23
22
 
24
- export { asXmlElement, cdata, collapseFragments, compact, createFragment, format, safeXml };
23
+ declare function createFragment(elements: Element[]): Element;
24
+
25
+ declare function getAllByTestId(root: Element | ElementCompact, id: string): (Element | ElementCompact)[];
26
+
27
+ declare function getByTestId(root: Element | ElementCompact, id: string): Element | ElementCompact | undefined;
28
+
29
+ declare function getSingleTextNode(element: Element | ElementCompact | undefined): Element;
30
+
31
+ declare function removeTestIds(root: Element | ElementCompact): Element;
32
+
33
+ export { asXmlElement, cdata, collapseFragments, compact, createFragment, format, getAllByTestId, getByTestId, getSingleTextNode, removeTestIds, safeXml };
25
34
  export type { FormatOptions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ooxml-tools/xml",
3
3
  "description": "Some XML helpers to help with OOXML development",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "license": "MIT",
6
6
  "main": "./dist/npm/index.js",
7
7
  "types": "./dist/npm/types.d.ts",
@@ -14,8 +14,7 @@
14
14
  "build": "rollup -c rollup.config.ts --configPlugin typescript"
15
15
  },
16
16
  "exports": {
17
- ".": "./dist/npm/index.js",
18
- "./commands": "./dist/npm/commands.js"
17
+ ".": "./dist/npm/index.js"
19
18
  },
20
19
  "files": [
21
20
  "./dist/npm",
@@ -29,7 +28,7 @@
29
28
  "@rollup/plugin-virtual": "^3.0.2",
30
29
  "@tsconfig/node22": "^22.0.0",
31
30
  "@types/yargs": "^17.0.32",
32
- "@vitest/coverage-v8": "^3.2.4",
31
+ "@vitest/coverage-v8": "^4.0.10",
33
32
  "prettier": "^3.4.2",
34
33
  "rollup": "^4.18.1",
35
34
  "rollup-plugin-copy": "^3.5.0",
@@ -40,7 +39,7 @@
40
39
  "tslib": "^2.6.3",
41
40
  "tsx": "^4.17.0",
42
41
  "typescript": "^5.5.4",
43
- "vitest": "^3.2.4"
42
+ "vitest": "^4.0.10"
44
43
  },
45
44
  "engines": {
46
45
  "node": ">=20.x"