Skip to content

Commit 4c53e33

Browse files
disable-output-escaping (#58)
* Implementing initial support to recognize DTDs. * Adding `escape` as a XNode property.
1 parent ae8f4e9 commit 4c53e33

File tree

10 files changed

+121
-67
lines changed

10 files changed

+121
-67
lines changed

src/constants.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
// Based on <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/
2-
// core.html#ID-1950641247>
1+
// Based on <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247>
32
export const DOM_ELEMENT_NODE = 1;
43
export const DOM_ATTRIBUTE_NODE = 2;
54
export const DOM_TEXT_NODE = 3;
65
export const DOM_CDATA_SECTION_NODE = 4;
7-
// const DOM_ENTITY_REFERENCE_NODE = 5;
8-
// const DOM_ENTITY_NODE = 6;
6+
export const DOM_ENTITY_REFERENCE_NODE = 5;
7+
export const DOM_ENTITY_NODE = 6;
98
export const DOM_PROCESSING_INSTRUCTION_NODE = 7;
109
export const DOM_COMMENT_NODE = 8;
1110
export const DOM_DOCUMENT_NODE = 9;
12-
// const DOM_DOCUMENT_TYPE_NODE = 10;
11+
export const DOM_DOCUMENT_TYPE_NODE = 10;
1312
export const DOM_DOCUMENT_FRAGMENT_NODE = 11;
14-
// const DOM_NOTATION_NODE = 12;
13+
export const DOM_NOTATION_NODE = 12;

src/dom/functions.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function domCreateElement(doc: any, name: any) {
6464
return doc.createElement(name);
6565
}
6666

67-
export function domCreateCDATASection(doc: any, data: any) {
67+
export function domCreateCDATASection(doc: XDocument, data: any) {
6868
return doc.createCDATASection(data);
6969
}
7070

@@ -76,6 +76,10 @@ export function domCreateDocumentFragment(doc: XDocument): XNode {
7676
return doc.createDocumentFragment();
7777
}
7878

79+
export function domCreateDTDSection(doc: XDocument, data: any) {
80+
return doc.createDTDSection(data);
81+
}
82+
7983
// Traverses the element nodes in the DOM section underneath the given
8084
// node and invokes the given callbacks as methods on every element
8185
// node encountered. Function opt_pre is invoked before a node's
@@ -111,7 +115,7 @@ export function domTraverseElements(node: any, opt_pre: any, opt_post: any) {
111115

112116
// Parses the given XML string with our custom, JavaScript XML parser. Written
113117
// by Steffen Meschkat (mesch@google.com).
114-
export function xmlParse(xml: any) {
118+
export function xmlParse(xml: string) {
115119
const regex_empty = /\/$/;
116120

117121
let regex_tagname;
@@ -158,10 +162,11 @@ export function xmlParse(xml: any) {
158162
if (text.charAt(0) == '/') {
159163
stack.pop();
160164
parent = stack[stack.length - 1];
161-
} else if (text.charAt(0) == '?') {
165+
} else if (text.charAt(0) === '?') {
162166
// Ignore XML declaration and processing instructions
163-
} else if (text.charAt(0) == '!') {
164-
// Ignore malformed notation and comments
167+
} else if (text.charAt(0) === '!') {
168+
// Ignore comments
169+
// console.log(`Ignored ${text}`);
165170
} else {
166171
const empty = text.match(regex_empty);
167172
const tagname = regex_tagname.exec(text)[1];
@@ -219,6 +224,22 @@ export function xmlParse(xml: any) {
219224
domAppendChild(parent, node);
220225
i += endTagIndex + 11;
221226
}
227+
} else if (xml.slice(i + 1, i + 9) === '!DOCTYPE') {
228+
let endTagIndex = xml.slice(i + 9).indexOf('>');
229+
if (endTagIndex) {
230+
const dtdValue = xml.slice(i + 9, i + endTagIndex + 9).trimStart();
231+
// TODO: Not sure if this is a good solution.
232+
// Trying to implement this: https://github.com/DesignLiquido/xslt-processor/issues/30
233+
let node;
234+
if (parent.nodeName === 'xsl:text') {
235+
node = domCreateTextNode(xmldoc, `<!DOCTYPE ${dtdValue}>`);
236+
} else {
237+
node = domCreateDTDSection(xmldoc, dtdValue);
238+
}
239+
240+
domAppendChild(parent, node);
241+
i += endTagIndex + dtdValue.length + 5;
242+
}
222243
} else {
223244
tag = true;
224245
}

src/dom/xdocument.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DOM_COMMENT_NODE,
55
DOM_DOCUMENT_FRAGMENT_NODE,
66
DOM_DOCUMENT_NODE,
7+
DOM_DOCUMENT_TYPE_NODE,
78
DOM_ELEMENT_NODE,
89
DOM_TEXT_NODE
910
} from '../constants';
@@ -66,4 +67,8 @@ export class XDocument extends XNode {
6667
createCDATASection(data: any) {
6768
return XNode.create(DOM_CDATA_SECTION_NODE, '#cdata-section', data, this);
6869
}
70+
71+
createDTDSection(data: any) {
72+
return XNode.create(DOM_DOCUMENT_TYPE_NODE, '#dtd-section', data, this);
73+
}
6974
}

src/dom/xml-functions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ function xmlTransformedTextRecursive(node: XNode, buffer: any[], cdata: boolean)
161161
const nodeValue = node.transformedNodeValue || node.nodeValue;
162162
if (nodeType == DOM_TEXT_NODE) {
163163
if (node.transformedNodeValue && node.transformedNodeValue.trim() !== '') {
164-
buffer.push(xmlEscapeText(node.transformedNodeValue));
164+
const finalText = node.escape ?
165+
xmlEscapeText(node.transformedNodeValue) :
166+
node.transformedNodeValue;
167+
buffer.push(finalText);
165168
}
166169
} else if (nodeType == DOM_CDATA_SECTION_NODE) {
167170
if (cdata) {

src/dom/xnode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class XNode {
4242
transformedParentNode: XNode;
4343

4444
printed: boolean;
45+
escape: boolean;
4546

4647
static _unusedXNodes: any[] = [];
4748

@@ -52,6 +53,7 @@ export class XNode {
5253
this.transformedAttributes = [];
5354
this.transformedChildNodes = [];
5455
this.printed = false;
56+
this.escape = true;
5557

5658
this.init(type, name, opt_value, opt_owner, opt_namespace);
5759
}

src/xslt.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ export class Xslt {
303303
case 'text':
304304
text = xmlValue(template);
305305
node = domCreateTransformedTextNode(outputDocument, text);
306+
const disableOutputEscaping = template.attributes.filter(a => a.nodeName === 'disable-output-escaping');
307+
if (disableOutputEscaping.length > 0 && disableOutputEscaping[0].nodeValue === 'yes') {
308+
node.escape = false;
309+
}
306310
output.appendTransformedChild(node);
307311
break;
308312
case 'value-of':

tests/escape.test.tsx renamed to tests/xml/escape.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from 'assert';
22

3-
import { xmlParse, xmlText } from '../src/dom';
3+
import { xmlParse, xmlText } from '../../src/dom';
44

55
describe('escape', () => {
66
it('accepts already escaped ampersand', () => {

tests/xml-to-json.test.tsx renamed to tests/xml/xml-to-json.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-disable no-useless-escape */
22
import assert from 'assert';
33

4-
import { Xslt } from '../src/xslt';
5-
import { xmlParse } from '../src/dom';
4+
import { Xslt } from '../../src/xslt';
5+
import { xmlParse } from '../../src/dom';
66

77
describe('xml-to-json', () => {
88
it('xml-to-json() without namespace test', () => {

tests/xmltoken.test.tsx renamed to tests/xml/xmltoken.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
XML11_ENTITY_REF,
2020
XML11_ATT_VALUE,
2121
XML_NC_NAME
22-
} from '../src/dom/xmltoken';
22+
} from '../../src/dom/xmltoken';
2323

2424
// Test if regexp matches the str and RegExp.exec returns exactly the match.
2525
const assertOk = (comment: any, regexp: any, str: any, match: any) => {

tests/xslt.test.tsx

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,61 +28,81 @@ const xmlString = (
2828
);
2929

3030
describe('xslt', () => {
31-
it('handles for-each sort', () => {
32-
const xsltForEachSort = (
33-
<xsl:stylesheet>
34-
<xsl:template match="/">
35-
<xsl:for-each select="//item">
36-
<xsl:sort select="@pos" />
37-
<xsl:value-of select="." />
38-
</xsl:for-each>
39-
</xsl:template>
40-
</xsl:stylesheet>
41-
);
42-
43-
const xsltClass = new Xslt();
44-
const xml = xmlParse(xmlString);
45-
const xslt = xmlParse(xsltForEachSort);
46-
const html = xsltClass.xsltProcess(xml, xslt);
47-
assert.equal(html, 'CAB');
48-
});
49-
50-
it('handles for-each sort ascending', () => {
51-
const xsltForEachSortAscending = (
52-
<xsl:stylesheet>
53-
<xsl:template match="/">
54-
<xsl:for-each select="//item">
55-
<xsl:sort select="." order="ascending" />
56-
<xsl:value-of select="." />
57-
</xsl:for-each>
58-
</xsl:template>
59-
</xsl:stylesheet>
60-
);
61-
62-
const xsltClass = new Xslt();
63-
const xml = xmlParse(xmlString);
64-
const xslt = xmlParse(xsltForEachSortAscending);
65-
const html = xsltClass.xsltProcess(xml, xslt);
66-
assert.equal(html, 'ABC');
31+
describe('xsl:for-each', () => {
32+
it('handles for-each sort', () => {
33+
const xsltForEachSort = (
34+
<xsl:stylesheet>
35+
<xsl:template match="/">
36+
<xsl:for-each select="//item">
37+
<xsl:sort select="@pos" />
38+
<xsl:value-of select="." />
39+
</xsl:for-each>
40+
</xsl:template>
41+
</xsl:stylesheet>
42+
);
43+
44+
const xsltClass = new Xslt();
45+
const xml = xmlParse(xmlString);
46+
const xslt = xmlParse(xsltForEachSort);
47+
const html = xsltClass.xsltProcess(xml, xslt);
48+
assert.equal(html, 'CAB');
49+
});
50+
51+
it('handles for-each sort ascending', () => {
52+
const xsltForEachSortAscending = (
53+
<xsl:stylesheet>
54+
<xsl:template match="/">
55+
<xsl:for-each select="//item">
56+
<xsl:sort select="." order="ascending" />
57+
<xsl:value-of select="." />
58+
</xsl:for-each>
59+
</xsl:template>
60+
</xsl:stylesheet>
61+
);
62+
63+
const xsltClass = new Xslt();
64+
const xml = xmlParse(xmlString);
65+
const xslt = xmlParse(xsltForEachSortAscending);
66+
const html = xsltClass.xsltProcess(xml, xslt);
67+
assert.equal(html, 'ABC');
68+
});
69+
70+
it('handles for-each sort descending', () => {
71+
const xsltForEachSortDescending = (
72+
<xsl:stylesheet>
73+
<xsl:template match="/">
74+
<xsl:for-each select="//item">
75+
<xsl:sort select="." order="descending" />
76+
<xsl:value-of select="." />
77+
</xsl:for-each>
78+
</xsl:template>
79+
</xsl:stylesheet>
80+
);
81+
82+
const xsltClass = new Xslt();
83+
const xml = xmlParse(xmlString);
84+
const xslt = xmlParse(xsltForEachSortDescending);
85+
const html = xsltClass.xsltProcess(xml, xslt);
86+
assert.equal(html, 'CBA');
87+
});
6788
});
6889

69-
it('handles for-each sort descending', () => {
70-
const xsltForEachSortDescending = (
71-
<xsl:stylesheet>
90+
describe('xsl:text', () => {
91+
it('disable-output-escaping', () => {
92+
const xml = <anything></anything>;
93+
const xslt = <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
94+
<xsl:output method="html" indent="yes" />
7295
<xsl:template match="/">
73-
<xsl:for-each select="//item">
74-
<xsl:sort select="." order="descending" />
75-
<xsl:value-of select="." />
76-
</xsl:for-each>
96+
<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text>
7797
</xsl:template>
78-
</xsl:stylesheet>
79-
);
80-
81-
const xsltClass = new Xslt();
82-
const xml = xmlParse(xmlString);
83-
const xslt = xmlParse(xsltForEachSortDescending);
84-
const html = xsltClass.xsltProcess(xml, xslt);
85-
assert.equal(html, 'CBA');
98+
</xsl:stylesheet>;
99+
100+
const xsltClass = new Xslt();
101+
const parsedXml = xmlParse(xml);
102+
const parsedXslt = xmlParse(xslt);
103+
const html = xsltClass.xsltProcess(parsedXml, parsedXslt);
104+
assert.equal(html, '<!DOCTYPE html>');
105+
});
86106
});
87107

88108
it('applies templates', () => {

0 commit comments

Comments
 (0)