@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 +92 -25
- package/dist/npm/index.js +60 -11
- package/dist/npm/types.d.ts +13 -4
- package/package.json +4 -5
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
|
-
|
|
34
|
-
|
|
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(
|
|
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
|
-
|
|
103
|
+
safeXml`
|
|
98
104
|
<doc>
|
|
99
105
|
${innerBit}
|
|
100
106
|
</doc>
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
assert.equal(
|
|
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
|
[](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
|
-
|
|
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 };
|
package/dist/npm/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
42
|
+
"vitest": "^4.0.10"
|
|
44
43
|
},
|
|
45
44
|
"engines": {
|
|
46
45
|
"node": ">=20.x"
|