xslt-processor 4.2.1 → 4.3.1
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 +46 -1
- package/index.d.mts +16 -3
- package/index.d.ts +16 -3
- package/index.js +309 -53
- package/index.js.map +1 -1
- package/index.mjs +309 -53
- package/index.mjs.map +1 -1
- package/package.json +1 -1
- package/umd/xslt-processor.global.js +3 -3
- package/umd/xslt-processor.global.js.map +1 -1
package/README.md
CHANGED
|
@@ -89,12 +89,57 @@ const xslt = new Xslt(options);
|
|
|
89
89
|
- `cData` (`boolean`, default `true`): resolves CDATA elements in the output. Content under CDATA is resolved as text. This overrides `escape` for CDATA content.
|
|
90
90
|
- `escape` (`boolean`, default `true`): replaces symbols like `<`, `>`, `&` and `"` by the corresponding [HTML/XML entities](https://www.tutorialspoint.com/xml/xml_character_entities.htm). Can be overridden by `disable-output-escaping`, that also does the opposite, unescaping `>` and `<` by `<` and `>`, respectively.
|
|
91
91
|
- `selfClosingTags` (`boolean`, default `true`): Self-closes tags that don't have inner elements, if `true`. For instance, `<test></test>` becomes `<test />`.
|
|
92
|
-
- `outputMethod` (`string`, default `xml`): Specifies the default output method. if `<xsl:output>` is declared in your XSLT file, this will be overridden. Valid values: `xml`, `html`, `text`, `name`, `xhtml`.
|
|
92
|
+
- `outputMethod` (`string`, default `xml`): Specifies the default output method. if `<xsl:output>` is declared in your XSLT file, this will be overridden. Valid values: `xml`, `html`, `text`, `name`, `xhtml`, `json`.
|
|
93
93
|
- `parameters` (`array`, default `[]`): external parameters that you want to use.
|
|
94
94
|
- `name`: the parameter name;
|
|
95
95
|
- `namespaceUri` (optional): the namespace;
|
|
96
96
|
- `value`: the value.
|
|
97
97
|
|
|
98
|
+
#### JSON Output Format
|
|
99
|
+
|
|
100
|
+
When using `outputMethod: 'json'`, the XSLT processor will convert the resulting XML document to JSON format. This is useful for APIs and modern JavaScript applications.
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const xslt = new Xslt({ outputMethod: 'json' });
|
|
106
|
+
const xmlParser = new XmlParser();
|
|
107
|
+
|
|
108
|
+
const xmlString = `<root>
|
|
109
|
+
<users>
|
|
110
|
+
<user>Alice</user>
|
|
111
|
+
<user>Bob</user>
|
|
112
|
+
</users>
|
|
113
|
+
</root>`;
|
|
114
|
+
|
|
115
|
+
const xsltString = `<?xml version="1.0"?>
|
|
116
|
+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
117
|
+
<xsl:template match="/">
|
|
118
|
+
<xsl:copy-of select="root"/>
|
|
119
|
+
</xsl:template>
|
|
120
|
+
</xsl:stylesheet>`;
|
|
121
|
+
|
|
122
|
+
const result = await xslt.xsltProcess(
|
|
123
|
+
xmlParser.xmlParse(xmlString),
|
|
124
|
+
xmlParser.xmlParse(xsltString)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// result will be a JSON string:
|
|
128
|
+
// {"root":{"users":{"user":["Alice","Bob"]}}}
|
|
129
|
+
|
|
130
|
+
const parsed = JSON.parse(result);
|
|
131
|
+
console.log(parsed.root.users.user); // ["Alice", "Bob"]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**JSON Structure Rules:**
|
|
135
|
+
|
|
136
|
+
- Each element becomes a property in a JSON object
|
|
137
|
+
- Text-only elements become string values
|
|
138
|
+
- Elements with multiple children of the same name become arrays
|
|
139
|
+
- Empty elements are omitted from the output
|
|
140
|
+
- Attributes are prefixed with `@` (when present in the output)
|
|
141
|
+
- Mixed text and element content uses the `#text` property for text nodes
|
|
142
|
+
|
|
98
143
|
### Direct use in browsers
|
|
99
144
|
|
|
100
145
|
You can simply add a tag like this:
|
package/index.d.mts
CHANGED
|
@@ -22,6 +22,7 @@ declare class XNode {
|
|
|
22
22
|
parentNode: XNode;
|
|
23
23
|
visited: boolean;
|
|
24
24
|
escape: boolean;
|
|
25
|
+
fromXslText: boolean;
|
|
25
26
|
static _unusedXNodes: any[];
|
|
26
27
|
constructor(type: number, name: string, opt_value: any, opt_owner: any, opt_namespace?: any);
|
|
27
28
|
/**
|
|
@@ -138,7 +139,7 @@ interface XPathNode {
|
|
|
138
139
|
nodeType: number;
|
|
139
140
|
nodeName: string;
|
|
140
141
|
localName?: string;
|
|
141
|
-
|
|
142
|
+
namespaceUri?: string | null;
|
|
142
143
|
textContent?: string | null;
|
|
143
144
|
parentNode?: XPathNode | null;
|
|
144
145
|
childNodes?: ArrayLike<XPathNode>;
|
|
@@ -532,6 +533,7 @@ type XsltOptions = {
|
|
|
532
533
|
cData: boolean;
|
|
533
534
|
escape: boolean;
|
|
534
535
|
selfClosingTags: boolean;
|
|
536
|
+
outputMethod?: 'xml' | 'html' | 'text' | 'xhtml' | 'json' | 'adaptive';
|
|
535
537
|
parameters?: XsltParameter[];
|
|
536
538
|
};
|
|
537
539
|
|
|
@@ -566,7 +568,7 @@ declare class Xslt {
|
|
|
566
568
|
options: XsltOptions;
|
|
567
569
|
decimalFormatSettings: XsltDecimalFormatSettings;
|
|
568
570
|
outputDocument: XDocument;
|
|
569
|
-
outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml';
|
|
571
|
+
outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml' | 'json' | 'adaptive';
|
|
570
572
|
outputOmitXmlDeclaration: string;
|
|
571
573
|
version: string;
|
|
572
574
|
firstTemplateRan: boolean;
|
|
@@ -591,7 +593,7 @@ declare class Xslt {
|
|
|
591
593
|
* The exported entry point of the XSL-T processor.
|
|
592
594
|
* @param xmlDoc The input document root, as DOM node.
|
|
593
595
|
* @param stylesheet The stylesheet document root, as DOM node.
|
|
594
|
-
* @returns the processed document, as XML text in a string.
|
|
596
|
+
* @returns the processed document, as XML text in a string, JSON string if outputMethod is 'json', or text if outputMethod is 'text' or 'adaptive' (with text content).
|
|
595
597
|
*/
|
|
596
598
|
xsltProcess(xmlDoc: XDocument, stylesheet: XDocument): Promise<string>;
|
|
597
599
|
/**
|
|
@@ -842,6 +844,17 @@ declare class Xslt {
|
|
|
842
844
|
*/
|
|
843
845
|
protected xsltTemplate(context: ExprContext, template: XNode, output?: XNode): Promise<void>;
|
|
844
846
|
protected xsltText(context: ExprContext, template: XNode, output?: XNode): void;
|
|
847
|
+
/**
|
|
848
|
+
* Validates XSLT stylesheet/transform attributes.
|
|
849
|
+
* According to XSLT specification, validates:
|
|
850
|
+
* - Required version attribute
|
|
851
|
+
* - Valid version values (1.0, 2.0, 3.0)
|
|
852
|
+
* - Valid namespace declarations
|
|
853
|
+
* - Valid values for optional attributes (extension-element-prefixes, exclude-result-prefixes)
|
|
854
|
+
* @param stylesheetElement The `<xsl:stylesheet>` or `<xsl:transform>` element to validate.
|
|
855
|
+
* @param context The Expression Context for namespace access.
|
|
856
|
+
*/
|
|
857
|
+
protected validateStylesheetAttributes(stylesheetElement: XNode, context: ExprContext): void;
|
|
845
858
|
/**
|
|
846
859
|
* Implements `<xsl:stylesheet>` and `<xsl:transform>`, and its corresponding
|
|
847
860
|
* validations.
|
package/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ declare class XNode {
|
|
|
22
22
|
parentNode: XNode;
|
|
23
23
|
visited: boolean;
|
|
24
24
|
escape: boolean;
|
|
25
|
+
fromXslText: boolean;
|
|
25
26
|
static _unusedXNodes: any[];
|
|
26
27
|
constructor(type: number, name: string, opt_value: any, opt_owner: any, opt_namespace?: any);
|
|
27
28
|
/**
|
|
@@ -138,7 +139,7 @@ interface XPathNode {
|
|
|
138
139
|
nodeType: number;
|
|
139
140
|
nodeName: string;
|
|
140
141
|
localName?: string;
|
|
141
|
-
|
|
142
|
+
namespaceUri?: string | null;
|
|
142
143
|
textContent?: string | null;
|
|
143
144
|
parentNode?: XPathNode | null;
|
|
144
145
|
childNodes?: ArrayLike<XPathNode>;
|
|
@@ -532,6 +533,7 @@ type XsltOptions = {
|
|
|
532
533
|
cData: boolean;
|
|
533
534
|
escape: boolean;
|
|
534
535
|
selfClosingTags: boolean;
|
|
536
|
+
outputMethod?: 'xml' | 'html' | 'text' | 'xhtml' | 'json' | 'adaptive';
|
|
535
537
|
parameters?: XsltParameter[];
|
|
536
538
|
};
|
|
537
539
|
|
|
@@ -566,7 +568,7 @@ declare class Xslt {
|
|
|
566
568
|
options: XsltOptions;
|
|
567
569
|
decimalFormatSettings: XsltDecimalFormatSettings;
|
|
568
570
|
outputDocument: XDocument;
|
|
569
|
-
outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml';
|
|
571
|
+
outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml' | 'json' | 'adaptive';
|
|
570
572
|
outputOmitXmlDeclaration: string;
|
|
571
573
|
version: string;
|
|
572
574
|
firstTemplateRan: boolean;
|
|
@@ -591,7 +593,7 @@ declare class Xslt {
|
|
|
591
593
|
* The exported entry point of the XSL-T processor.
|
|
592
594
|
* @param xmlDoc The input document root, as DOM node.
|
|
593
595
|
* @param stylesheet The stylesheet document root, as DOM node.
|
|
594
|
-
* @returns the processed document, as XML text in a string.
|
|
596
|
+
* @returns the processed document, as XML text in a string, JSON string if outputMethod is 'json', or text if outputMethod is 'text' or 'adaptive' (with text content).
|
|
595
597
|
*/
|
|
596
598
|
xsltProcess(xmlDoc: XDocument, stylesheet: XDocument): Promise<string>;
|
|
597
599
|
/**
|
|
@@ -842,6 +844,17 @@ declare class Xslt {
|
|
|
842
844
|
*/
|
|
843
845
|
protected xsltTemplate(context: ExprContext, template: XNode, output?: XNode): Promise<void>;
|
|
844
846
|
protected xsltText(context: ExprContext, template: XNode, output?: XNode): void;
|
|
847
|
+
/**
|
|
848
|
+
* Validates XSLT stylesheet/transform attributes.
|
|
849
|
+
* According to XSLT specification, validates:
|
|
850
|
+
* - Required version attribute
|
|
851
|
+
* - Valid version values (1.0, 2.0, 3.0)
|
|
852
|
+
* - Valid namespace declarations
|
|
853
|
+
* - Valid values for optional attributes (extension-element-prefixes, exclude-result-prefixes)
|
|
854
|
+
* @param stylesheetElement The `<xsl:stylesheet>` or `<xsl:transform>` element to validate.
|
|
855
|
+
* @param context The Expression Context for namespace access.
|
|
856
|
+
*/
|
|
857
|
+
protected validateStylesheetAttributes(stylesheetElement: XNode, context: ExprContext): void;
|
|
845
858
|
/**
|
|
846
859
|
* Implements `<xsl:stylesheet>` and `<xsl:transform>`, and its corresponding
|
|
847
860
|
* validations.
|
package/index.js
CHANGED
|
@@ -742,12 +742,12 @@ var XPathStep = class extends XPathExpression {
|
|
|
742
742
|
if (!nsUri) {
|
|
743
743
|
return false;
|
|
744
744
|
}
|
|
745
|
-
const
|
|
745
|
+
const nodeLocalName2 = node.localName || node.nodeName && this.extractLocalName(node.nodeName);
|
|
746
746
|
const nodeNsUri = node.namespaceURI || node.namespaceUri || "";
|
|
747
|
-
return
|
|
747
|
+
return nodeLocalName2 === localName && nodeNsUri === nsUri;
|
|
748
748
|
}
|
|
749
|
-
const
|
|
750
|
-
return
|
|
749
|
+
const nodeLocalName = node.localName || this.extractLocalName(node.nodeName);
|
|
750
|
+
return nodeLocalName === testName;
|
|
751
751
|
case "node-type":
|
|
752
752
|
switch (this.nodeTest.nodeType) {
|
|
753
753
|
case "node":
|
|
@@ -806,6 +806,17 @@ var XPathStep = class extends XPathExpression {
|
|
|
806
806
|
if (Array.isArray(value)) return value.length > 0;
|
|
807
807
|
return !!value;
|
|
808
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Extract the local name from a qualified name (e.g., "ns:name" -> "name", "name" -> "name")
|
|
811
|
+
*/
|
|
812
|
+
extractLocalName(qname) {
|
|
813
|
+
if (!qname) return "";
|
|
814
|
+
const colonIndex = qname.indexOf(":");
|
|
815
|
+
if (colonIndex > 0) {
|
|
816
|
+
return qname.substring(colonIndex + 1);
|
|
817
|
+
}
|
|
818
|
+
return qname;
|
|
819
|
+
}
|
|
809
820
|
};
|
|
810
821
|
|
|
811
822
|
// src/xpath/lib/src/expressions/predicate-expression.ts
|
|
@@ -1090,7 +1101,7 @@ var XPathFunctionCall = class extends XPathExpression {
|
|
|
1090
1101
|
namespaceUri(args, context) {
|
|
1091
1102
|
var _a;
|
|
1092
1103
|
const node = this.getNodeArg(args, context);
|
|
1093
|
-
return (_a = node == null ? void 0 : node.
|
|
1104
|
+
return (_a = node == null ? void 0 : node.namespaceUri) != null ? _a : "";
|
|
1094
1105
|
}
|
|
1095
1106
|
nodeName(args, context) {
|
|
1096
1107
|
var _a;
|
|
@@ -1560,6 +1571,7 @@ var _XNode = class _XNode {
|
|
|
1560
1571
|
this.childNodes = [];
|
|
1561
1572
|
this.visited = false;
|
|
1562
1573
|
this.escape = true;
|
|
1574
|
+
this.fromXslText = false;
|
|
1563
1575
|
this.siblingPosition = -1;
|
|
1564
1576
|
this.init(type, name, opt_value, opt_owner, opt_namespace);
|
|
1565
1577
|
}
|
|
@@ -2086,23 +2098,32 @@ function xmlTransformedTextRecursive(node, buffer, options) {
|
|
|
2086
2098
|
const nodeType = node.nodeType;
|
|
2087
2099
|
const nodeValue = node.nodeValue;
|
|
2088
2100
|
if (nodeType === DOM_TEXT_NODE) {
|
|
2089
|
-
|
|
2101
|
+
const isFromXslText = node.fromXslText === true;
|
|
2102
|
+
if (node.nodeValue && (isFromXslText || node.nodeValue.trim() !== "")) {
|
|
2090
2103
|
const finalText = node.escape && options.escape ? xmlEscapeText(node.nodeValue) : xmlUnescapeText(node.nodeValue);
|
|
2091
2104
|
buffer.push(finalText);
|
|
2092
2105
|
}
|
|
2093
2106
|
} else if (nodeType === DOM_CDATA_SECTION_NODE) {
|
|
2094
|
-
if (options.
|
|
2107
|
+
if (options.outputMethod === "text") {
|
|
2108
|
+
buffer.push(nodeValue);
|
|
2109
|
+
} else if (options.cData) {
|
|
2095
2110
|
buffer.push(xmlEscapeText(nodeValue));
|
|
2096
2111
|
} else {
|
|
2097
2112
|
buffer.push(`<![CDATA[${nodeValue}]]>`);
|
|
2098
2113
|
}
|
|
2099
2114
|
} else if (nodeType == DOM_COMMENT_NODE) {
|
|
2100
|
-
|
|
2115
|
+
if (options.outputMethod !== "text") {
|
|
2116
|
+
buffer.push(`<!-- ${nodeValue} -->`);
|
|
2117
|
+
}
|
|
2101
2118
|
} else if (nodeType == DOM_ELEMENT_NODE) {
|
|
2102
|
-
if (
|
|
2103
|
-
|
|
2119
|
+
if (options.outputMethod === "text") {
|
|
2120
|
+
xmlElementLogicTextOnly(node, buffer, options);
|
|
2104
2121
|
} else {
|
|
2105
|
-
|
|
2122
|
+
if (node.nodeName !== null && node.nodeName !== void 0) {
|
|
2123
|
+
xmlElementLogicTrivial(node, buffer, options);
|
|
2124
|
+
} else {
|
|
2125
|
+
xmlElementLogicMuted(node, buffer, options);
|
|
2126
|
+
}
|
|
2106
2127
|
}
|
|
2107
2128
|
} else if (nodeType === DOM_DOCUMENT_NODE || nodeType === DOM_DOCUMENT_FRAGMENT_NODE) {
|
|
2108
2129
|
let childNodes = node.firstChild ? [] : node.childNodes;
|
|
@@ -2190,6 +2211,22 @@ function xmlElementLogicMuted(node, buffer, options) {
|
|
|
2190
2211
|
xmlTransformedTextRecursive(childNodes[i], buffer, options);
|
|
2191
2212
|
}
|
|
2192
2213
|
}
|
|
2214
|
+
function xmlElementLogicTextOnly(node, buffer, options) {
|
|
2215
|
+
let childNodes = [];
|
|
2216
|
+
if (node.firstChild) {
|
|
2217
|
+
let child = node.firstChild;
|
|
2218
|
+
while (child) {
|
|
2219
|
+
childNodes.push(child);
|
|
2220
|
+
child = child.nextSibling;
|
|
2221
|
+
}
|
|
2222
|
+
} else {
|
|
2223
|
+
childNodes = node.childNodes;
|
|
2224
|
+
}
|
|
2225
|
+
childNodes = childNodes.sort((a, b) => a.siblingPosition - b.siblingPosition);
|
|
2226
|
+
for (let i = 0; i < childNodes.length; ++i) {
|
|
2227
|
+
xmlTransformedTextRecursive(childNodes[i], buffer, options);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2193
2230
|
function xmlFullNodeName(node) {
|
|
2194
2231
|
const nodeName = node.nodeName;
|
|
2195
2232
|
if (node.prefix && nodeName.indexOf(`${node.prefix}:`) != 0) {
|
|
@@ -2213,6 +2250,157 @@ function xmlGetAttribute(node, name) {
|
|
|
2213
2250
|
}
|
|
2214
2251
|
return value;
|
|
2215
2252
|
}
|
|
2253
|
+
function nodeToJsonObject(node) {
|
|
2254
|
+
if (!node) {
|
|
2255
|
+
return null;
|
|
2256
|
+
}
|
|
2257
|
+
const nodeType = node.nodeType;
|
|
2258
|
+
if (nodeType === DOM_TEXT_NODE || nodeType === DOM_CDATA_SECTION_NODE) {
|
|
2259
|
+
const text = node.nodeValue ? node.nodeValue.trim() : "";
|
|
2260
|
+
return text.length > 0 ? text : null;
|
|
2261
|
+
}
|
|
2262
|
+
if (nodeType === DOM_COMMENT_NODE) {
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
if (nodeType === DOM_DOCUMENT_NODE || nodeType === DOM_DOCUMENT_FRAGMENT_NODE) {
|
|
2266
|
+
const children = node.childNodes || [];
|
|
2267
|
+
const childObjects = [];
|
|
2268
|
+
for (let i = 0; i < children.length; i++) {
|
|
2269
|
+
const child = children[i];
|
|
2270
|
+
const childObj = nodeToJsonObject(child);
|
|
2271
|
+
if (childObj !== null) {
|
|
2272
|
+
childObjects.push(childObj);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
if (childObjects.length === 0) {
|
|
2276
|
+
return null;
|
|
2277
|
+
} else if (childObjects.length === 1) {
|
|
2278
|
+
return childObjects[0];
|
|
2279
|
+
} else {
|
|
2280
|
+
return childObjects;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (nodeType === DOM_ELEMENT_NODE) {
|
|
2284
|
+
const obj = {};
|
|
2285
|
+
const element = node;
|
|
2286
|
+
const hasAttributes = element.attributes && element.attributes.length > 0;
|
|
2287
|
+
if (hasAttributes) {
|
|
2288
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
2289
|
+
const attr = element.attributes[i];
|
|
2290
|
+
obj["@" + attr.nodeName] = attr.nodeValue;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
const children = element.childNodes || [];
|
|
2294
|
+
let textContent = "";
|
|
2295
|
+
let hasElementChildren = false;
|
|
2296
|
+
const childElements = {};
|
|
2297
|
+
for (let i = 0; i < children.length; i++) {
|
|
2298
|
+
const child = children[i];
|
|
2299
|
+
const childType = child.nodeType;
|
|
2300
|
+
if (childType === DOM_TEXT_NODE || childType === DOM_CDATA_SECTION_NODE) {
|
|
2301
|
+
const text = child.nodeValue ? child.nodeValue.trim() : "";
|
|
2302
|
+
if (text.length > 0) {
|
|
2303
|
+
textContent += text;
|
|
2304
|
+
}
|
|
2305
|
+
} else if (childType === DOM_ELEMENT_NODE) {
|
|
2306
|
+
hasElementChildren = true;
|
|
2307
|
+
const childElement = child;
|
|
2308
|
+
const childName = childElement.localName || childElement.nodeName;
|
|
2309
|
+
const childObj = nodeToJsonObject(child);
|
|
2310
|
+
if (childObj !== null) {
|
|
2311
|
+
if (childElements[childName]) {
|
|
2312
|
+
if (!Array.isArray(childElements[childName])) {
|
|
2313
|
+
childElements[childName] = [childElements[childName]];
|
|
2314
|
+
}
|
|
2315
|
+
childElements[childName].push(childObj);
|
|
2316
|
+
} else {
|
|
2317
|
+
childElements[childName] = childObj;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
Object.assign(obj, childElements);
|
|
2323
|
+
if (!hasElementChildren && textContent.length > 0) {
|
|
2324
|
+
if (!hasAttributes && Object.keys(childElements).length === 0) {
|
|
2325
|
+
return textContent;
|
|
2326
|
+
} else {
|
|
2327
|
+
obj["#text"] = textContent;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
if (Object.keys(obj).length === 0) {
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
return obj;
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
function detectAdaptiveOutputFormat(node) {
|
|
2338
|
+
if (!node) {
|
|
2339
|
+
return "xml";
|
|
2340
|
+
}
|
|
2341
|
+
const nodeType = node.nodeType;
|
|
2342
|
+
if (nodeType === DOM_DOCUMENT_NODE || nodeType === DOM_DOCUMENT_FRAGMENT_NODE) {
|
|
2343
|
+
const children = node.childNodes || [];
|
|
2344
|
+
let elementCount = 0;
|
|
2345
|
+
let textCount = 0;
|
|
2346
|
+
let hasSignificantText = false;
|
|
2347
|
+
for (let i = 0; i < children.length; i++) {
|
|
2348
|
+
const child = children[i];
|
|
2349
|
+
if (child.nodeType === DOM_ELEMENT_NODE) {
|
|
2350
|
+
elementCount++;
|
|
2351
|
+
} else if (child.nodeType === DOM_TEXT_NODE) {
|
|
2352
|
+
const text = child.nodeValue ? child.nodeValue.trim() : "";
|
|
2353
|
+
if (text.length > 0) {
|
|
2354
|
+
textCount++;
|
|
2355
|
+
hasSignificantText = true;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
if (elementCount === 0 && hasSignificantText) {
|
|
2360
|
+
return "text";
|
|
2361
|
+
}
|
|
2362
|
+
return "xml";
|
|
2363
|
+
}
|
|
2364
|
+
if (nodeType === DOM_TEXT_NODE || nodeType === DOM_CDATA_SECTION_NODE) {
|
|
2365
|
+
const text = node.nodeValue ? node.nodeValue.trim() : "";
|
|
2366
|
+
if (text.length > 0) {
|
|
2367
|
+
return "text";
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return "xml";
|
|
2371
|
+
}
|
|
2372
|
+
function xmlToJson(node) {
|
|
2373
|
+
if (!node) {
|
|
2374
|
+
return "{}";
|
|
2375
|
+
}
|
|
2376
|
+
let rootElement = node;
|
|
2377
|
+
if (node.nodeType === DOM_DOCUMENT_NODE || node.nodeType === DOM_DOCUMENT_FRAGMENT_NODE) {
|
|
2378
|
+
const children = node.childNodes || [];
|
|
2379
|
+
for (let i = 0; i < children.length; i++) {
|
|
2380
|
+
if (children[i].nodeType === DOM_ELEMENT_NODE) {
|
|
2381
|
+
rootElement = children[i];
|
|
2382
|
+
break;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
const element = rootElement;
|
|
2387
|
+
const rootName = element.localName || element.nodeName;
|
|
2388
|
+
const jsonObj = {};
|
|
2389
|
+
const elementContent = nodeToJsonObject(rootElement);
|
|
2390
|
+
if (elementContent === null) {
|
|
2391
|
+
jsonObj[rootName] = {};
|
|
2392
|
+
} else if (typeof elementContent === "object" && !Array.isArray(elementContent)) {
|
|
2393
|
+
jsonObj[rootName] = elementContent;
|
|
2394
|
+
} else {
|
|
2395
|
+
jsonObj[rootName] = elementContent;
|
|
2396
|
+
}
|
|
2397
|
+
try {
|
|
2398
|
+
const cleaned = JSON.parse(JSON.stringify(jsonObj));
|
|
2399
|
+
return JSON.stringify(cleaned);
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
return JSON.stringify(jsonObj);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2216
2404
|
|
|
2217
2405
|
// src/dom/xml-parser.ts
|
|
2218
2406
|
var import_he2 = __toESM(require("he"));
|
|
@@ -2457,7 +2645,7 @@ var XmlParser = class {
|
|
|
2457
2645
|
} else if (!tag && char === "<") {
|
|
2458
2646
|
let text = xml.slice(start, i);
|
|
2459
2647
|
if (text && parent !== root) {
|
|
2460
|
-
domAppendChild(parent, domCreateTextNode(xmlDocument, text));
|
|
2648
|
+
domAppendChild(parent, domCreateTextNode(xmlDocument, import_he2.default.decode(text)));
|
|
2461
2649
|
}
|
|
2462
2650
|
if (xml.slice(i + 1, i + 4) === "!--") {
|
|
2463
2651
|
let endTagIndex = xml.slice(i + 4).indexOf("-->");
|
|
@@ -2601,15 +2789,6 @@ var NodeConverter = class {
|
|
|
2601
2789
|
adaptXNode(node) {
|
|
2602
2790
|
if (!node) return null;
|
|
2603
2791
|
const adapted = node;
|
|
2604
|
-
if (!("namespaceURI" in adapted)) {
|
|
2605
|
-
Object.defineProperty(adapted, "namespaceURI", {
|
|
2606
|
-
get() {
|
|
2607
|
-
return this.namespaceUri;
|
|
2608
|
-
},
|
|
2609
|
-
enumerable: true,
|
|
2610
|
-
configurable: true
|
|
2611
|
-
});
|
|
2612
|
-
}
|
|
2613
2792
|
if (!("textContent" in adapted)) {
|
|
2614
2793
|
Object.defineProperty(adapted, "textContent", {
|
|
2615
2794
|
get() {
|
|
@@ -3551,9 +3730,10 @@ var Xslt = class {
|
|
|
3551
3730
|
cData: options.cData === true,
|
|
3552
3731
|
escape: options.escape === true,
|
|
3553
3732
|
selfClosingTags: options.selfClosingTags === true,
|
|
3733
|
+
outputMethod: options.outputMethod,
|
|
3554
3734
|
parameters: options.parameters || []
|
|
3555
3735
|
};
|
|
3556
|
-
this.outputMethod = "xml";
|
|
3736
|
+
this.outputMethod = options.outputMethod || "xml";
|
|
3557
3737
|
this.outputOmitXmlDeclaration = "no";
|
|
3558
3738
|
this.stripSpacePatterns = [];
|
|
3559
3739
|
this.preserveSpacePatterns = [];
|
|
@@ -3576,7 +3756,7 @@ var Xslt = class {
|
|
|
3576
3756
|
* The exported entry point of the XSL-T processor.
|
|
3577
3757
|
* @param xmlDoc The input document root, as DOM node.
|
|
3578
3758
|
* @param stylesheet The stylesheet document root, as DOM node.
|
|
3579
|
-
* @returns the processed document, as XML text in a string.
|
|
3759
|
+
* @returns the processed document, as XML text in a string, JSON string if outputMethod is 'json', or text if outputMethod is 'text' or 'adaptive' (with text content).
|
|
3580
3760
|
*/
|
|
3581
3761
|
xsltProcess(xmlDoc, stylesheet) {
|
|
3582
3762
|
return __async(this, null, function* () {
|
|
@@ -3589,11 +3769,18 @@ var Xslt = class {
|
|
|
3589
3769
|
}
|
|
3590
3770
|
}
|
|
3591
3771
|
yield this.xsltProcessContext(expressionContext, stylesheet, this.outputDocument);
|
|
3772
|
+
if (this.outputMethod === "json") {
|
|
3773
|
+
return xmlToJson(outputDocument);
|
|
3774
|
+
}
|
|
3775
|
+
let outputMethod = this.outputMethod;
|
|
3776
|
+
if (this.outputMethod === "adaptive") {
|
|
3777
|
+
outputMethod = detectAdaptiveOutputFormat(outputDocument);
|
|
3778
|
+
}
|
|
3592
3779
|
const transformedOutputXml = xmlTransformedText(outputDocument, {
|
|
3593
3780
|
cData: this.options.cData,
|
|
3594
3781
|
escape: this.options.escape,
|
|
3595
3782
|
selfClosingTags: this.options.selfClosingTags,
|
|
3596
|
-
outputMethod
|
|
3783
|
+
outputMethod
|
|
3597
3784
|
});
|
|
3598
3785
|
return transformedOutputXml;
|
|
3599
3786
|
});
|
|
@@ -3754,6 +3941,9 @@ var Xslt = class {
|
|
|
3754
3941
|
for (let j = 0; j < modifiedContext.contextSize(); ++j) {
|
|
3755
3942
|
const currentNode = modifiedContext.nodeList[j];
|
|
3756
3943
|
if (currentNode.nodeType === DOM_TEXT_NODE) {
|
|
3944
|
+
if (!this.xsltPassText(currentNode)) {
|
|
3945
|
+
continue;
|
|
3946
|
+
}
|
|
3757
3947
|
const textNodeContext = context.clone(
|
|
3758
3948
|
[currentNode],
|
|
3759
3949
|
0
|
|
@@ -4522,6 +4712,7 @@ var Xslt = class {
|
|
|
4522
4712
|
xsltText(context, template, output) {
|
|
4523
4713
|
const text = xmlValue(template);
|
|
4524
4714
|
const node = domCreateTextNode(this.outputDocument, text);
|
|
4715
|
+
node.fromXslText = true;
|
|
4525
4716
|
const disableOutputEscaping = template.childNodes.filter(
|
|
4526
4717
|
(a) => a.nodeType === DOM_ATTRIBUTE_NODE && a.nodeName === "disable-output-escaping"
|
|
4527
4718
|
);
|
|
@@ -4529,8 +4720,80 @@ var Xslt = class {
|
|
|
4529
4720
|
node.escape = false;
|
|
4530
4721
|
}
|
|
4531
4722
|
const destinationTextNode = output || this.outputDocument;
|
|
4723
|
+
node.siblingPosition = destinationTextNode.childNodes.length;
|
|
4532
4724
|
destinationTextNode.appendChild(node);
|
|
4533
4725
|
}
|
|
4726
|
+
/**
|
|
4727
|
+
* Validates XSLT stylesheet/transform attributes.
|
|
4728
|
+
* According to XSLT specification, validates:
|
|
4729
|
+
* - Required version attribute
|
|
4730
|
+
* - Valid version values (1.0, 2.0, 3.0)
|
|
4731
|
+
* - Valid namespace declarations
|
|
4732
|
+
* - Valid values for optional attributes (extension-element-prefixes, exclude-result-prefixes)
|
|
4733
|
+
* @param stylesheetElement The `<xsl:stylesheet>` or `<xsl:transform>` element to validate.
|
|
4734
|
+
* @param context The Expression Context for namespace access.
|
|
4735
|
+
*/
|
|
4736
|
+
validateStylesheetAttributes(stylesheetElement, context) {
|
|
4737
|
+
const attributes = stylesheetElement.childNodes.filter((n) => n.nodeType === DOM_ATTRIBUTE_NODE);
|
|
4738
|
+
const validAttributes = ["version", "id", "extension-element-prefixes", "exclude-result-prefixes", "default-collation"];
|
|
4739
|
+
const validNamespaceAttributes = ["xmlns"];
|
|
4740
|
+
let versionFound = false;
|
|
4741
|
+
for (let attribute of attributes) {
|
|
4742
|
+
const nodeName = attribute.nodeName;
|
|
4743
|
+
const nodeValue = attribute.nodeValue;
|
|
4744
|
+
if (attribute.prefix === "xmlns") {
|
|
4745
|
+
context.knownNamespaces[attribute.localName] = nodeValue;
|
|
4746
|
+
continue;
|
|
4747
|
+
}
|
|
4748
|
+
if (nodeName === "xmlns") {
|
|
4749
|
+
context.knownNamespaces[""] = nodeValue;
|
|
4750
|
+
continue;
|
|
4751
|
+
}
|
|
4752
|
+
if (nodeName === "version") {
|
|
4753
|
+
versionFound = true;
|
|
4754
|
+
if (!["1.0", "2.0", "3.0"].includes(nodeValue)) {
|
|
4755
|
+
throw new Error(
|
|
4756
|
+
`XSLT version not defined or invalid. Actual resolved version: ${nodeValue || "(none)"}.`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
this.version = nodeValue;
|
|
4760
|
+
context.xsltVersion = nodeValue;
|
|
4761
|
+
continue;
|
|
4762
|
+
}
|
|
4763
|
+
if (nodeName === "extension-element-prefixes") {
|
|
4764
|
+
const prefixes = nodeValue.split(/\s+/);
|
|
4765
|
+
for (const prefix of prefixes) {
|
|
4766
|
+
if (prefix && !/^[a-zA-Z_:][\w:.-]*$/.test(prefix)) {
|
|
4767
|
+
throw new Error(`Invalid prefix in extension-element-prefixes: "${prefix}". Prefixes must be valid QNames.`);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
continue;
|
|
4771
|
+
}
|
|
4772
|
+
if (nodeName === "exclude-result-prefixes") {
|
|
4773
|
+
if (nodeValue !== "#all") {
|
|
4774
|
+
const prefixes = nodeValue.split(/\s+/);
|
|
4775
|
+
for (const prefix of prefixes) {
|
|
4776
|
+
if (prefix && !/^[a-zA-Z_:][\w:.-]*$/.test(prefix)) {
|
|
4777
|
+
throw new Error(`Invalid prefix in exclude-result-prefixes: "${prefix}". Prefixes must be valid QNames or "#all".`);
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
continue;
|
|
4782
|
+
}
|
|
4783
|
+
if (nodeName === "default-collation") {
|
|
4784
|
+
if (!nodeValue || nodeValue.trim().length === 0) {
|
|
4785
|
+
throw new Error("The default-collation attribute must contain a URI.");
|
|
4786
|
+
}
|
|
4787
|
+
continue;
|
|
4788
|
+
}
|
|
4789
|
+
if (nodeName === "id") {
|
|
4790
|
+
if (!/^[a-zA-Z_:][\w:.-]*$/.test(nodeValue)) {
|
|
4791
|
+
throw new Error(`Invalid id attribute value: "${nodeValue}". IDs must be valid NCNames.`);
|
|
4792
|
+
}
|
|
4793
|
+
continue;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4534
4797
|
/**
|
|
4535
4798
|
* Implements `<xsl:stylesheet>` and `<xsl:transform>`, and its corresponding
|
|
4536
4799
|
* validations.
|
|
@@ -4541,24 +4804,7 @@ var Xslt = class {
|
|
|
4541
4804
|
*/
|
|
4542
4805
|
xsltTransformOrStylesheet(context, template, output) {
|
|
4543
4806
|
return __async(this, null, function* () {
|
|
4544
|
-
|
|
4545
|
-
switch (stylesheetAttribute.nodeName) {
|
|
4546
|
-
case "version":
|
|
4547
|
-
this.version = stylesheetAttribute.nodeValue;
|
|
4548
|
-
if (!["1.0", "2.0", "3.0"].includes(this.version)) {
|
|
4549
|
-
throw new Error(
|
|
4550
|
-
`XSLT version not defined or invalid. Actual resolved version: ${this.version || "(none)"}.`
|
|
4551
|
-
);
|
|
4552
|
-
}
|
|
4553
|
-
context.xsltVersion = this.version;
|
|
4554
|
-
break;
|
|
4555
|
-
default:
|
|
4556
|
-
if (stylesheetAttribute.prefix === "xmlns") {
|
|
4557
|
-
context.knownNamespaces[stylesheetAttribute.localName] = stylesheetAttribute.nodeValue;
|
|
4558
|
-
}
|
|
4559
|
-
break;
|
|
4560
|
-
}
|
|
4561
|
-
}
|
|
4807
|
+
this.validateStylesheetAttributes(template, context);
|
|
4562
4808
|
let importsDone = false;
|
|
4563
4809
|
for (const child of template.childNodes) {
|
|
4564
4810
|
if (child.nodeType === DOM_ELEMENT_NODE) {
|
|
@@ -4598,16 +4844,22 @@ var Xslt = class {
|
|
|
4598
4844
|
}
|
|
4599
4845
|
}
|
|
4600
4846
|
if (matchCandidates.length > 0) {
|
|
4601
|
-
matchCandidates.
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4847
|
+
const rootPatternMatch = matchCandidates.find((c) => c.priority.matchPattern === "/");
|
|
4848
|
+
let winner;
|
|
4849
|
+
if (rootPatternMatch) {
|
|
4850
|
+
winner = rootPatternMatch;
|
|
4851
|
+
} else {
|
|
4852
|
+
matchCandidates.sort((a, b) => {
|
|
4853
|
+
if (a.priority.importPrecedence !== b.priority.importPrecedence) {
|
|
4854
|
+
return b.priority.importPrecedence - a.priority.importPrecedence;
|
|
4855
|
+
}
|
|
4856
|
+
if (a.priority.effectivePriority !== b.priority.effectivePriority) {
|
|
4857
|
+
return b.priority.effectivePriority - a.priority.effectivePriority;
|
|
4858
|
+
}
|
|
4859
|
+
return b.priority.documentOrder - a.priority.documentOrder;
|
|
4860
|
+
});
|
|
4861
|
+
winner = matchCandidates[0];
|
|
4862
|
+
}
|
|
4611
4863
|
const conflicts = matchCandidates.filter(
|
|
4612
4864
|
(t) => t.priority.importPrecedence === winner.priority.importPrecedence && t.priority.effectivePriority === winner.priority.effectivePriority
|
|
4613
4865
|
);
|
|
@@ -4745,7 +4997,11 @@ var Xslt = class {
|
|
|
4745
4997
|
return __async(this, null, function* () {
|
|
4746
4998
|
const contextClone = context.clone();
|
|
4747
4999
|
for (let i = 0; i < template.childNodes.length; ++i) {
|
|
4748
|
-
|
|
5000
|
+
const child = template.childNodes[i];
|
|
5001
|
+
if (child.nodeType === DOM_ATTRIBUTE_NODE) {
|
|
5002
|
+
continue;
|
|
5003
|
+
}
|
|
5004
|
+
yield this.xsltProcessContext(contextClone, child, output);
|
|
4749
5005
|
}
|
|
4750
5006
|
});
|
|
4751
5007
|
}
|