Some XML helpers to help with OOXML development
Note
These are all based around xml-js
and output that modules internal format
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
This helper function just outputs an Element
for either input
asXmlElement("<name>foo</name>"); // => {name: "name", text: "foo"}
asXmlElement({ name: "name", text: "foo" }); // => {name: "name", text: "foo"}
Note
All the other functions in this document that accept XML, also accept this Element
(using this function)
An XML tagged template literal with the following features
- error if XML is incorrect
- array support in substitution
The following will error in development, because of mismatched start/end tags
safeXml`<foo>hello</bar>`;
Substitution of arrays "just works" so you can map values in the tagged template literals
const items = [1, 2, 3].map((n) => safeXml`<name>item ${1}</name>`);
const outXml = safeXml`<test>${items}</test>`;
assert.equal(
outXml,
`<test><name>item 1</name><name>item 2</name><name>item 3</name></test>`,
);
This also makes it easy to support https://prettier.io/docs/en/options#embedded-language-formatting
Removes non required whitespace from the XML
const outXml = compact(`
<test>
something
</test>
`);
assert.equal(outXml, "<test>something</test>");
For XML to be valid, there must be a single root node. When composing apps it's handy for that not to be true.
For example the following would error
const xml = safeXml`
<name>foo</name>
<name>bar</name>
`;
So instead we'd like something like a HTML fragment
const xml = safeXml`
<XML_FRAGMENT>
<name>foo</name>
<name>bar</name>
</XML_FRAGMENT>
`;
So we did just that, collapseFragments
walks over a tree removing <XML_FRAGMENT>...</XML_FRAGMENT>
nodes.
const innerBit = safeXml`
<XML_FRAGMENT>
<name>foo</name>
<name>bar</name>
</XML_FRAGMENT>
`;
const newXml = collapseFragments(
safeXml`
<doc>
${innerBit}
</doc>
`,
);
assert.equal(
newXml,
`
<doc>
<name>foo</name>
<name>bar</name>
</doc>
`,
);
From the wikipedia page
CDATA section is a piece of element content that is marked up to be interpreted literally, as textual data, not as marked-up content.
But <name><![CDATA[one < two]]><name>
is ugly and hard to read, so instead
safeXml`<name>${cdata("one < two")}</name>`;
Format XML in a consistent way, useful for logging and snapshot testing
format(`
<test> one
</test>
`); /* =>
* <test>
* one
* </test>
*/
const element = getAllByTestId(`
<root>
<name _testid="test">
Hello
</name>
</test>
`)
const element = getByTestId(`
<root>
<name _testid="test">
Hello
</name>
</test>
`)
Returns a single text node if one exists
getSingleTextNode(`<test>hello world</test>`) // => {type: "text", text: "hello world"}
Else throws an error
getSingleTextNode(`<test>hello <foo>inner node</foo> world</test>`) // => throws Error
const element = removeTestIds(`
<root>
<name _testid="test">
Hello
</name>
</test>
`) /* =>
* <root>
* <name>
* Hello
* </name>
* </test>
*/
MIT