Skip to content

Commit bb4e96e

Browse files
Template precedence (#56)
* New methods to work with transformed node elements. * Remaining adjustments to resolve problems with template precedence. * Testing XML to JSON. * - Disregarding empty text for XML output; - Debugging `<xsl:element>` case. * Not printing an element if a transformed name was not assigned. * Logic for `xsl:for-each`. * Transformed prefix. * Avoiding node to be printed twice. * Adjusting `xsl:attribute`. * Final fixes for `value-of`.
1 parent a8274c3 commit bb4e96e

File tree

9 files changed

+454
-170
lines changed

9 files changed

+454
-170
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"runtimeArgs": [
1212
"--inspect-brk",
1313
"${workspaceRoot}/node_modules/jest/bin/jest.js",
14-
// "root-element.test",
14+
// "xslt.test",
1515
"--runInBand"
1616
],
1717
"skipFiles": ["<node_internals>/**", "node_modules/**"],

src/dom/functions.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,28 @@ export function domGetAttributeValue(node: any, name: any) {
3636
return node.getAttributeValue(name);
3737
}
3838

39-
export function domSetAttribute(node: any, name: any, value: any) {
39+
export function domSetAttribute(node: XNode, name: any, value: any) {
4040
return node.setAttribute(name, value);
4141
}
4242

43+
export function domSetTransformedAttribute(node: XNode, name: any, value: any) {
44+
return node.setTransformedAttribute(name, value);
45+
}
46+
4347
export function domAppendChild(node: XNode, child: any) {
4448
return node.appendChild(child);
4549
}
4650

47-
export function domCreateTextNode(doc: any, text: any) {
48-
return doc.createTextNode(text);
51+
export function domAppendTransformedChild(node: XNode, child: any) {
52+
return node.appendTransformedChild(child);
53+
}
54+
55+
export function domCreateTextNode(node: XDocument, text: string) {
56+
return node.createTextNode(text);
57+
}
58+
59+
export function domCreateTransformedTextNode(node: XDocument, text: string) {
60+
return node.createTransformedTextNode(text);
4961
}
5062

5163
export function domCreateElement(doc: any, name: any) {

src/dom/xdocument.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export class XDocument extends XNode {
4545
return XNode.create(DOM_TEXT_NODE, '#text', value, this);
4646
}
4747

48+
createTransformedTextNode(value: any) {
49+
const node = XNode.create(DOM_TEXT_NODE, '#text', value, this);
50+
node.transformedNodeValue = value;
51+
return node;
52+
}
53+
4854
createAttribute(name: any) {
4955
return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);
5056
}

src/dom/xml-functions.ts

Lines changed: 166 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '../constants';
1212
import { domGetAttributeValue } from './functions';
1313
import { XNode } from './xnode';
14+
import { XDocument } from './xdocument';
1415

1516
// Returns the text value of a node; for nodes without children this
1617
// is the nodeValue, for nodes with children this is the concatenation
@@ -21,6 +22,48 @@ export function xmlValue(node: any, disallowBrowserSpecificOptimization: boolean
2122
return '';
2223
}
2324

25+
let ret = '';
26+
if (node.nodeType == DOM_TEXT_NODE || node.nodeType == DOM_CDATA_SECTION_NODE) {
27+
ret += node.nodeValue;
28+
} else if (node.nodeType == DOM_ATTRIBUTE_NODE) {
29+
ret += node.nodeValue;
30+
} else if (
31+
node.nodeType == DOM_ELEMENT_NODE ||
32+
node.nodeType == DOM_DOCUMENT_NODE ||
33+
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE
34+
) {
35+
if (!disallowBrowserSpecificOptimization) {
36+
// IE, Safari, Opera, and friends
37+
const innerText = node.innerText;
38+
if (innerText != undefined) {
39+
return innerText;
40+
}
41+
// Firefox
42+
const textContent = node.textContent;
43+
if (textContent != undefined) {
44+
return textContent;
45+
}
46+
}
47+
48+
if (node.transformedChildNodes.length > 0) {
49+
for (let i = 0; i < node.transformedChildNodes.length; ++i) {
50+
ret += xmlValue(node.transformedChildNodes[i]);
51+
}
52+
} else {
53+
for (let i = 0; i < node.childNodes.length; ++i) {
54+
ret += xmlValue(node.childNodes[i]);
55+
}
56+
}
57+
}
58+
return ret;
59+
}
60+
61+
// TODO: Give a better name to this.
62+
export function xmlValue2(node: any, disallowBrowserSpecificOptimization: boolean = false) {
63+
if (!node) {
64+
return '';
65+
}
66+
2467
let ret = '';
2568
if (node.nodeType == DOM_TEXT_NODE || node.nodeType == DOM_CDATA_SECTION_NODE) {
2669
ret += node.nodeValue;
@@ -44,22 +87,27 @@ export function xmlValue(node: any, disallowBrowserSpecificOptimization: boolean
4487
}
4588
}
4689
// pobrecito!
47-
const len = node.childNodes.length;
90+
const len = node.transformedChildNodes.length;
4891
for (let i = 0; i < len; ++i) {
49-
ret += xmlValue(node.childNodes[i]);
92+
ret += xmlValue(node.transformedChildNodes[i]);
5093
}
5194
}
5295
return ret;
5396
}
5497

55-
// Returns the representation of a node as XML text.
56-
export function xmlText(node: any, opt_cdata: boolean = false) {
98+
/**
99+
* Returns the representation of a node as XML text.
100+
* @param node The starting node.
101+
* @param opt_cdata If using CDATA configuration.
102+
* @returns The XML string.
103+
*/
104+
export function xmlText(node: XNode, opt_cdata: boolean = false) {
57105
const buf = [];
58106
xmlTextRecursive(node, buf, opt_cdata);
59107
return buf.join('');
60108
}
61109

62-
function xmlTextRecursive(node: any, buf: any[], cdata: any) {
110+
function xmlTextRecursive(node: XNode, buf: any[], cdata: any) {
63111
if (node.nodeType == DOM_TEXT_NODE) {
64112
buf.push(xmlEscapeText(node.nodeValue));
65113
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) {
@@ -95,12 +143,111 @@ function xmlTextRecursive(node: any, buf: any[], cdata: any) {
95143
}
96144
}
97145

98-
function xmlFullNodeName(n: any) {
99-
if (n.prefix && n.nodeName.indexOf(`${n.prefix}:`) != 0) {
100-
return `${n.prefix}:${n.nodeName}`;
146+
/**
147+
* Returns the representation of a node as XML text.
148+
* @param node The starting node.
149+
* @param opt_cdata If using CDATA configuration.
150+
* @returns The XML string.
151+
*/
152+
export function xmlTransformedText(node: XNode, opt_cdata: boolean = false) {
153+
const buffer = [];
154+
xmlTransformedTextRecursive(node, buffer, opt_cdata);
155+
return buffer.join('');
156+
}
157+
158+
function xmlTransformedTextRecursive(node: XNode, buffer: any[], cdata: boolean) {
159+
if (node.printed) return;
160+
const nodeType = node.transformedNodeType || node.nodeType;
161+
const nodeValue = node.transformedNodeValue || node.nodeValue;
162+
if (nodeType == DOM_TEXT_NODE) {
163+
if (node.transformedNodeValue && node.transformedNodeValue.trim() !== '') {
164+
buffer.push(xmlEscapeText(node.transformedNodeValue));
165+
}
166+
} else if (nodeType == DOM_CDATA_SECTION_NODE) {
167+
if (cdata) {
168+
buffer.push(nodeValue);
169+
} else {
170+
buffer.push(`<![CDATA[${nodeValue}]]>`);
171+
}
172+
} else if (nodeType == DOM_COMMENT_NODE) {
173+
buffer.push(`<!--${nodeValue}-->`);
174+
} else if (nodeType == DOM_ELEMENT_NODE) {
175+
// If node didn't have a transformed name, but its children
176+
// had transformations, children should be present at output.
177+
// This is called here "muted logic".
178+
if (node.transformedNodeName !== null && node.transformedNodeName !== undefined) {
179+
xmlElementLogicTrivial(node, buffer, cdata);
180+
} else {
181+
xmlElementLogicMuted(node, buffer, cdata);
182+
}
183+
} else if (nodeType == DOM_DOCUMENT_NODE || nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
184+
const childNodes = node.transformedChildNodes.concat(node.childNodes);
185+
// const childNodes = node.transformedChildNodes.length > 0 ? node.transformedChildNodes : node.childNodes;
186+
for (let i = 0; i < childNodes.length; ++i) {
187+
xmlTransformedTextRecursive(childNodes[i], buffer, cdata);
188+
}
189+
}
190+
191+
node.printed = true;
192+
}
193+
194+
/**
195+
* XML element output, trivial logic.
196+
* @param node The XML node.
197+
* @param buffer The XML buffer.
198+
* @param cdata If using CDATA configuration.
199+
*/
200+
function xmlElementLogicTrivial(node: XNode, buffer: any[], cdata: boolean) {
201+
buffer.push(`<${xmlFullNodeName(node)}`);
202+
203+
const attributes = node.transformedAttributes || node.attributes;
204+
for (let i = 0; i < attributes.length; ++i) {
205+
const attribute = attributes[i];
206+
if (!attribute) {
207+
continue;
208+
}
209+
210+
const attributeNodeName = attribute.transformedNodeName || attribute.nodeName;
211+
const attributeNodeValue = attribute.transformedNodeValue || attribute.nodeValue;
212+
if (attributeNodeName && attributeNodeValue) {
213+
buffer.push(` ${xmlFullNodeName(attribute)}="${xmlEscapeAttr(attributeNodeValue)}"`);
214+
}
215+
}
216+
217+
const childNodes = node.transformedChildNodes.length > 0 ? node.transformedChildNodes : node.childNodes;
218+
if (childNodes.length == 0) {
219+
buffer.push('/>');
220+
} else {
221+
buffer.push('>');
222+
for (let i = 0; i < childNodes.length; ++i) {
223+
xmlTransformedTextRecursive(childNodes[i], buffer, cdata);
224+
}
225+
buffer.push(`</${xmlFullNodeName(node)}>`);
226+
}
227+
}
228+
229+
/**
230+
* XML element output, muted logic.
231+
* In other words, this element should not be printed, but its
232+
* children can be printed if they have transformed values.
233+
* @param node The XML node.
234+
* @param buffer The XML buffer.
235+
* @param cdata If using CDATA configuration.
236+
*/
237+
function xmlElementLogicMuted(node: XNode, buffer: any[], cdata: boolean) {
238+
const childNodes = node.transformedChildNodes.length > 0 ? node.transformedChildNodes : node.childNodes;
239+
for (let i = 0; i < childNodes.length; ++i) {
240+
xmlTransformedTextRecursive(childNodes[i], buffer, cdata);
101241
}
242+
}
102243

103-
return n.nodeName;
244+
function xmlFullNodeName(node: XNode) {
245+
const nodeName = node.transformedNodeName || node.nodeName;
246+
if (node.transformedPrefix && nodeName.indexOf(`${node.transformedPrefix}:`) != 0) {
247+
return `${node.transformedPrefix}:${nodeName}`;
248+
}
249+
250+
return nodeName;
104251
}
105252

106253
/**
@@ -157,13 +304,17 @@ export function xmlGetAttribute(node: XNode, name: string) {
157304
* and other nodes: for the document node, the owner document is the
158305
* node itself, for all others it's the ownerDocument property.
159306
*
160-
* @param {Node} node
161-
* @return {Document}
307+
* @param {XNode} node
308+
* @return {XDocument}
162309
*/
163-
export function xmlOwnerDocument(node: any) {
164-
if (node.nodeType == DOM_DOCUMENT_NODE) {
165-
return node;
310+
export function xmlOwnerDocument(node: XNode): XDocument {
311+
if (node === null || node === undefined) {
312+
throw new Error('Node has no valid owner document.');
313+
}
314+
315+
if (node.nodeType === DOM_DOCUMENT_NODE) {
316+
return node as XDocument;
166317
}
167318

168-
return node.ownerDocument;
319+
return xmlOwnerDocument(node.ownerDocument);
169320
}

src/dom/xnode.ts

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,42 @@ export class XNode {
1414
nodeType: any;
1515
nodeName: string;
1616
nodeValue: any;
17-
transformedNodeType: any;
18-
transformedNodeName: string;
19-
transformedNodeValue: any;
17+
firstChild: XNode;
18+
lastChild: XNode;
19+
nextSibling: XNode;
20+
previousSibling: XNode;
21+
2022
ownerDocument: any;
2123
namespaceURI: any;
2224
prefix: any;
23-
localName: any;
24-
firstChild: any;
25-
lastChild: any;
26-
nextSibling: any;
27-
previousSibling: any;
28-
parentNode: any;
25+
localName: string;
26+
27+
parentNode: XNode;
28+
29+
transformedAttributes: XNode[];
30+
transformedChildNodes: XNode[];
31+
transformedNodeType: any;
32+
transformedNodeName: string;
33+
transformedNodeValue: any;
34+
transformedFirstChild: XNode;
35+
transformedLastChild: XNode;
36+
transformedNextSibling: XNode;
37+
transformedPreviousSibling: XNode;
38+
transformedPrefix: any;
39+
transformedLocalName: string;
40+
41+
transformedParentNode: XNode;
42+
43+
printed: boolean;
2944

3045
static _unusedXNodes: any[] = [];
3146

3247
constructor(type: any, name: any, opt_value: any, opt_owner: any, opt_namespace?: any) {
3348
this.attributes = [];
3449
this.childNodes = [];
50+
this.transformedAttributes = [];
51+
this.transformedChildNodes = [];
52+
this.printed = false;
3553

3654
this.init(type, name, opt_value, opt_owner, opt_namespace);
3755
}
@@ -118,7 +136,7 @@ export class XNode {
118136
return newNode;
119137
}
120138

121-
appendChild(node: any) {
139+
appendChild(node: XNode) {
122140
// firstChild
123141
if (this.childNodes.length == 0) {
124142
this.firstChild = node;
@@ -143,6 +161,31 @@ export class XNode {
143161
this.childNodes.push(node);
144162
}
145163

164+
appendTransformedChild(node: XNode) {
165+
// firstChild
166+
if (this.transformedChildNodes.length == 0) {
167+
this.transformedFirstChild = node;
168+
}
169+
170+
// previousSibling
171+
node.transformedPreviousSibling = this.lastChild;
172+
173+
// nextSibling
174+
node.transformedNextSibling = null;
175+
if (this.transformedLastChild) {
176+
this.transformedLastChild.transformedNextSibling = node;
177+
}
178+
179+
// parentNode
180+
node.transformedParentNode = this;
181+
182+
// lastChild
183+
this.transformedLastChild = node;
184+
185+
// childNodes
186+
this.transformedChildNodes.push(node);
187+
}
188+
146189
replaceChild(newNode: any, oldNode: any) {
147190
if (oldNode == newNode) {
148191
return;
@@ -262,6 +305,17 @@ export class XNode {
262305
this.attributes.push(XNode.create(DOM_ATTRIBUTE_NODE, name, value, this));
263306
}
264307

308+
setTransformedAttribute(name: any, value: any) {
309+
for (let i = 0; i < this.transformedAttributes.length; ++i) {
310+
if (this.transformedAttributes[i].nodeName == name) {
311+
this.transformedAttributes[i].nodeValue = `${value}`;
312+
return;
313+
}
314+
}
315+
316+
this.transformedAttributes.push(XNode.create(DOM_ATTRIBUTE_NODE, name, value, this));
317+
}
318+
265319
setAttributeNS(namespace: any, name: any, value: any) {
266320
for (let i = 0; i < this.attributes.length; ++i) {
267321
if (

0 commit comments

Comments
 (0)