Skip to content

Commit 3041c98

Browse files
Self closing tag (#63)
* Implementing self-closing tag option. * Updating README.md.
1 parent c36b52b commit 3041c98

File tree

6 files changed

+70
-26
lines changed

6 files changed

+70
-26
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ You can pass an `options` object to `Xslt` class:
6666
```js
6767
const options = {
6868
escape: false,
69+
selfClosingTags: true,
6970
properties: [{ name: 'myparam', value: '123' }]
7071
};
7172
const xslt = new Xslt(options);
7273
```
7374

7475
- `escape` (`boolean`, default `true`): replaces symbols like `<`, `>`, `&` and `"` by the corresponding [XML entities](https://www.tutorialspoint.com/xml/xml_character_entities.htm).
76+
- `selfClosingTags` (`boolean`, default `true`): Self-closes tags that don't have inner elements, if `true`. For instance, `<test></test>` becomes `<test />`.
7577
- `parameters` (`array`, default `[]`): external parameters that you want to use.
7678
- `name`: the parameter name;
7779
- `namespaceUri` (optional): the namespace;
@@ -82,7 +84,7 @@ const xslt = new Xslt(options);
8284
You can simply add a tag like this:
8385

8486
```html
85-
<script type="application/javascript" src="https://www.unpkg.com/xslt-processor@1.1.4/umd/xslt-processor.js"></script>
87+
<script type="application/javascript" src="https://www.unpkg.com/xslt-processor@1.1.6/umd/xslt-processor.js"></script>
8688
```
8789

8890
All the exports will live under `globalThis.XsltProcessor`. [See a usage example here](https://github.com/DesignLiquido/xslt-processor/blob/main/interactive-tests/xslt.html).

src/dom/xml-functions.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,48 +98,56 @@ export function xmlValue2(node: any, disallowBrowserSpecificOptimization: boolea
9898

9999
/**
100100
* Returns the representation of a node as XML text.
101-
* @param node The starting node.
102-
* @param opt_cdata If using CDATA configuration.
101+
* @param {XNode} node The starting node.
102+
* @param {XmlOutputOptions} options XML output options.
103103
* @returns The XML string.
104104
*/
105-
export function xmlText(node: XNode, opt_cdata: boolean = false) {
106-
const buf = [];
107-
xmlTextRecursive(node, buf, opt_cdata);
108-
return buf.join('');
105+
export function xmlText(node: XNode, options: XmlOutputOptions = {
106+
cData: false,
107+
escape: true,
108+
selfClosingTags: true
109+
}) {
110+
const buffer: string[] = [];
111+
xmlTextRecursive(node, buffer, options);
112+
return buffer.join('');
109113
}
110114

111-
function xmlTextRecursive(node: XNode, buf: any[], cdata: any) {
115+
function xmlTextRecursive(node: XNode, buffer: string[], options: XmlOutputOptions) {
112116
if (node.nodeType == DOM_TEXT_NODE) {
113-
buf.push(xmlEscapeText(node.nodeValue));
117+
buffer.push(xmlEscapeText(node.nodeValue));
114118
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) {
115-
if (cdata) {
116-
buf.push(node.nodeValue);
119+
if (options.cData) {
120+
buffer.push(node.nodeValue);
117121
} else {
118-
buf.push(`<![CDATA[${node.nodeValue}]]>`);
122+
buffer.push(`<![CDATA[${node.nodeValue}]]>`);
119123
}
120124
} else if (node.nodeType == DOM_COMMENT_NODE) {
121-
buf.push(`<!--${node.nodeValue}-->`);
125+
buffer.push(`<!--${node.nodeValue}-->`);
122126
} else if (node.nodeType == DOM_ELEMENT_NODE) {
123-
buf.push(`<${xmlFullNodeName(node)}`);
127+
buffer.push(`<${xmlFullNodeName(node)}`);
124128
for (let i = 0; i < node.attributes.length; ++i) {
125129
const a = node.attributes[i];
126130
if (a && a.nodeName && a.nodeValue) {
127-
buf.push(` ${xmlFullNodeName(a)}="${xmlEscapeAttr(a.nodeValue)}"`);
131+
buffer.push(` ${xmlFullNodeName(a)}="${xmlEscapeAttr(a.nodeValue)}"`);
128132
}
129133
}
130134

131-
if (node.childNodes.length == 0) {
132-
buf.push('/>');
135+
if (node.childNodes.length === 0) {
136+
if (options.selfClosingTags) {
137+
buffer.push('/>');
138+
} else {
139+
buffer.push(`></${xmlFullNodeName(node)}>`);
140+
}
133141
} else {
134-
buf.push('>');
142+
buffer.push('>');
135143
for (let i = 0; i < node.childNodes.length; ++i) {
136-
xmlTextRecursive(node.childNodes[i], buf, cdata);
144+
xmlTextRecursive(node.childNodes[i], buffer, options);
137145
}
138-
buf.push(`</${xmlFullNodeName(node)}>`);
146+
buffer.push(`</${xmlFullNodeName(node)}>`);
139147
}
140148
} else if (node.nodeType == DOM_DOCUMENT_NODE || node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
141149
for (let i = 0; i < node.childNodes.length; ++i) {
142-
xmlTextRecursive(node.childNodes[i], buf, cdata);
150+
xmlTextRecursive(node.childNodes[i], buffer, options);
143151
}
144152
}
145153
}
@@ -154,10 +162,11 @@ export function xmlTransformedText(
154162
node: XNode,
155163
options: XmlOutputOptions = {
156164
cData: false,
157-
escape: true
165+
escape: true,
166+
selfClosingTags: true
158167
}
159168
) {
160-
const buffer = [];
169+
const buffer: string[] = [];
161170
xmlTransformedTextRecursive(node, buffer, options);
162171
return buffer.join('');
163172
}
@@ -224,7 +233,11 @@ function xmlElementLogicTrivial(node: XNode, buffer: string[], options: XmlOutpu
224233

225234
const childNodes = node.transformedChildNodes.length > 0 ? node.transformedChildNodes : node.childNodes;
226235
if (childNodes.length === 0) {
227-
buffer.push('/>');
236+
if (options.selfClosingTags) {
237+
buffer.push('/>');
238+
} else {
239+
buffer.push(`></${xmlFullNodeName(node)}>`);
240+
}
228241
} else {
229242
buffer.push('>');
230243
for (let i = 0; i < childNodes.length; ++i) {

src/dom/xml-output-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type XmlOutputOptions = {
22
cData: boolean;
33
escape: boolean;
4+
selfClosingTags: boolean;
45
}

src/xslt/xslt-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ import { XsltParameter } from "./xslt-parameter"
22

33
export type XsltOptions = {
44
escape: boolean,
5+
selfClosingTags: boolean,
56
parameters?: XsltParameter[]
67
}

src/xslt/xslt.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class Xslt {
7575
outputMethod: string;
7676
outputOmitXmlDeclaration: string;
7777

78-
constructor(options: XsltOptions = { escape: true }) {
78+
constructor(options: XsltOptions = { escape: true, selfClosingTags: true }) {
7979
this.xPath = new XPath();
8080
this.options = options;
8181
this.outputMethod = 'xml';
@@ -103,7 +103,8 @@ export class Xslt {
103103
this.xsltProcessContext(expressionContext, stylesheet, output, parameters || []);
104104
const ret = xmlTransformedText(output, {
105105
cData: false,
106-
escape: this.options.escape
106+
escape: this.options.escape,
107+
selfClosingTags: this.options.selfClosingTags
107108
});
108109
return ret;
109110
}

tests/xml/xml.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import assert from 'assert';
2+
3+
import { dom } from 'isomorphic-jsx';
4+
import React from 'react';
5+
6+
import { xmlParse, xmlText } from '../../src/dom';
7+
8+
// Just touching the `dom`, otherwise Babel prunes the import.
9+
console.log(dom);
10+
describe('General XML', () => {
11+
it('Self-closing tags disabled', () => {
12+
const xmlString = (
13+
<root>
14+
<typeA />
15+
<typeB />
16+
</root>
17+
);
18+
19+
const outXmlString = xmlText(xmlParse(xmlString), {
20+
cData: false,
21+
selfClosingTags: false,
22+
escape: true
23+
});
24+
assert.equal(outXmlString, '<root><typeA></typeA><typeB></typeB></root>');
25+
});
26+
});

0 commit comments

Comments
 (0)