@tradik/xslt-processor 1.0.3 → 1.1.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 +290 -45
- package/bin/lib/options.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +115 -0
- package/bin/xslt.js +68 -162
- package/dist/xslt-processor.browser.js +2074 -159
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +6 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +2078 -158
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +299 -0
- package/dist/xslt-processor.d.ts +92 -4
- package/dist/xslt-processor.js +2073 -157
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +26 -15
- package/src/XSLTProcessor.js +177 -8
- package/src/index.js +11 -5
- package/src/xpath/evaluator.js +48 -7
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine.js +471 -179
- package/src/xslt/formatNumber.js +220 -0
- package/src/xslt/functions.js +191 -0
- package/src/xslt/index.js +31 -0
- package/src/xslt/keys.js +141 -0
- package/src/xslt/literalResult.js +167 -0
- package/src/xslt/number.js +178 -0
- package/src/xslt/numberFormat.js +155 -0
- package/src/xslt/resultTree.js +74 -0
- package/src/xslt/serializer/baseWriter.js +283 -0
- package/src/xslt/serializer/constants.js +78 -0
- package/src/xslt/serializer/escape.js +98 -0
- package/src/xslt/serializer/htmlSerializer.js +141 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +103 -0
- package/src/xslt/serializer/textSerializer.js +29 -0
- package/src/xslt/serializer/xmlSerializer.js +127 -0
- package/src/xslt/serializer.js +57 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/whitespace.js +184 -0
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- package/src/xslt/engine.test.js +0 -3130
|
@@ -33,9 +33,13 @@ var XsltProcessorLib = (() => {
|
|
|
33
33
|
isBrowser: () => isBrowser,
|
|
34
34
|
isNativeXSLTSupported: () => isNativeXSLTSupported,
|
|
35
35
|
isNode: () => isNode,
|
|
36
|
+
isRawText: () => isRawText,
|
|
37
|
+
markRawText: () => markRawText,
|
|
36
38
|
parseXPath: () => parse,
|
|
39
|
+
resolveOutputSettings: () => resolveOutputSettings,
|
|
37
40
|
selectFirstXPath: () => selectFirst,
|
|
38
|
-
selectXPath: () => select
|
|
41
|
+
selectXPath: () => select,
|
|
42
|
+
serializeResult: () => serializeResult
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
// src/xpath/tokenizer.js
|
|
@@ -821,12 +825,22 @@ var XsltProcessorLib = (() => {
|
|
|
821
825
|
"__lookupSetter__"
|
|
822
826
|
]);
|
|
823
827
|
var XPathContext = class _XPathContext {
|
|
824
|
-
|
|
828
|
+
/**
|
|
829
|
+
* @param {Node} node - The context node
|
|
830
|
+
* @param {number} [position] - The context position (1-based)
|
|
831
|
+
* @param {number} [size] - The context size
|
|
832
|
+
* @param {Object} [variables] - Variable bindings by name
|
|
833
|
+
* @param {Object} [namespaces] - Namespace bindings by prefix
|
|
834
|
+
* @param {*} [hostContext] - Opaque context of the host language (XSLT),
|
|
835
|
+
* carried through unchanged so host defined functions can reach it
|
|
836
|
+
*/
|
|
837
|
+
constructor(node, position = 1, size = 1, variables = {}, namespaces = {}, hostContext = null) {
|
|
825
838
|
this.node = node;
|
|
826
839
|
this.position = position;
|
|
827
840
|
this.size = size;
|
|
828
841
|
this.variables = variables;
|
|
829
842
|
this.namespaces = namespaces;
|
|
843
|
+
this.hostContext = hostContext;
|
|
830
844
|
}
|
|
831
845
|
clone(overrides = {}) {
|
|
832
846
|
return new _XPathContext(
|
|
@@ -834,13 +848,17 @@ var XsltProcessorLib = (() => {
|
|
|
834
848
|
overrides.position ?? this.position,
|
|
835
849
|
overrides.size ?? this.size,
|
|
836
850
|
overrides.variables ?? this.variables,
|
|
837
|
-
overrides.namespaces ?? this.namespaces
|
|
851
|
+
overrides.namespaces ?? this.namespaces,
|
|
852
|
+
overrides.hostContext ?? this.hostContext
|
|
838
853
|
);
|
|
839
854
|
}
|
|
840
855
|
};
|
|
841
856
|
var XPathEvaluator = class {
|
|
842
857
|
constructor(options = {}) {
|
|
843
|
-
this.functions =
|
|
858
|
+
this.functions = Object.assign(
|
|
859
|
+
/* @__PURE__ */ Object.create(null),
|
|
860
|
+
this.initCoreFunctions()
|
|
861
|
+
);
|
|
844
862
|
this.maxRecursionDepth = options.maxRecursionDepth ?? XPathLimits.MAX_RECURSION_DEPTH;
|
|
845
863
|
this.maxResultSize = options.maxResultSize ?? XPathLimits.MAX_RESULT_SIZE;
|
|
846
864
|
this.maxStringLength = options.maxStringLength ?? XPathLimits.MAX_STRING_LENGTH;
|
|
@@ -1190,7 +1208,7 @@ var XsltProcessorLib = (() => {
|
|
|
1190
1208
|
case "node":
|
|
1191
1209
|
return true;
|
|
1192
1210
|
case "text":
|
|
1193
|
-
return node.nodeType === 3;
|
|
1211
|
+
return node.nodeType === 3 || node.nodeType === 4;
|
|
1194
1212
|
case "comment":
|
|
1195
1213
|
return node.nodeType === 8;
|
|
1196
1214
|
case "processing-instruction":
|
|
@@ -1231,13 +1249,31 @@ var XsltProcessorLib = (() => {
|
|
|
1231
1249
|
}
|
|
1232
1250
|
return context.variables[name];
|
|
1233
1251
|
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Register additional functions, for example the XSLT function library.
|
|
1254
|
+
*
|
|
1255
|
+
* Existing names are overwritten, so a host language can also specialise a
|
|
1256
|
+
* core function. Each function is called as `fn(args, context)` with the
|
|
1257
|
+
* evaluator as `this`.
|
|
1258
|
+
*
|
|
1259
|
+
* @param {Object<string, Function>} functions - Functions by name
|
|
1260
|
+
* @returns {XPathEvaluator} This evaluator, to allow chaining
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* evaluator.registerFunctions({ 'my:double': (args, ctx) => 2 });
|
|
1264
|
+
*/
|
|
1265
|
+
registerFunctions(functions) {
|
|
1266
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
1267
|
+
this.functions[name] = fn;
|
|
1268
|
+
}
|
|
1269
|
+
return this;
|
|
1270
|
+
}
|
|
1234
1271
|
evalFunctionCall(ast, context) {
|
|
1235
1272
|
const name = ast.prefix ? `${ast.prefix}:${ast.name}` : ast.name;
|
|
1236
|
-
|
|
1237
|
-
if (!fn) {
|
|
1273
|
+
if (!Object.hasOwn(this.functions, name)) {
|
|
1238
1274
|
throw new Error(`Unknown function: ${name}`);
|
|
1239
1275
|
}
|
|
1240
|
-
return
|
|
1276
|
+
return this.functions[name].call(this, ast.args, context);
|
|
1241
1277
|
}
|
|
1242
1278
|
// Type conversion functions
|
|
1243
1279
|
toBoolean(value) {
|
|
@@ -1294,7 +1330,7 @@ var XsltProcessorLib = (() => {
|
|
|
1294
1330
|
case 11: {
|
|
1295
1331
|
let text = "";
|
|
1296
1332
|
const walker = (n) => {
|
|
1297
|
-
if (n.nodeType === 3) {
|
|
1333
|
+
if (n.nodeType === 3 || n.nodeType === 4) {
|
|
1298
1334
|
text += n.nodeValue || "";
|
|
1299
1335
|
} else if (n.childNodes) {
|
|
1300
1336
|
for (const child of n.childNodes) {
|
|
@@ -1599,8 +1635,1486 @@ var XsltProcessorLib = (() => {
|
|
|
1599
1635
|
}
|
|
1600
1636
|
};
|
|
1601
1637
|
|
|
1638
|
+
// src/xslt/elements.js
|
|
1639
|
+
var XSLT_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
|
|
1640
|
+
var XSLT_ELEMENTS = Object.freeze([
|
|
1641
|
+
"apply-imports",
|
|
1642
|
+
"apply-templates",
|
|
1643
|
+
"attribute",
|
|
1644
|
+
"call-template",
|
|
1645
|
+
"choose",
|
|
1646
|
+
"comment",
|
|
1647
|
+
"copy",
|
|
1648
|
+
"copy-of",
|
|
1649
|
+
"element",
|
|
1650
|
+
"fallback",
|
|
1651
|
+
"for-each",
|
|
1652
|
+
"if",
|
|
1653
|
+
"message",
|
|
1654
|
+
"number",
|
|
1655
|
+
"otherwise",
|
|
1656
|
+
"param",
|
|
1657
|
+
"processing-instruction",
|
|
1658
|
+
"sort",
|
|
1659
|
+
"text",
|
|
1660
|
+
"value-of",
|
|
1661
|
+
"variable",
|
|
1662
|
+
"when",
|
|
1663
|
+
"with-param"
|
|
1664
|
+
]);
|
|
1665
|
+
var ELEMENT_SET = new Set(XSLT_ELEMENTS);
|
|
1666
|
+
function isXsltElementAvailable(localName) {
|
|
1667
|
+
return ELEMENT_SET.has(localName);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/xslt/formatNumber.js
|
|
1671
|
+
var DEFAULT_DECIMAL_FORMAT = Object.freeze({
|
|
1672
|
+
decimalSeparator: ".",
|
|
1673
|
+
groupingSeparator: ",",
|
|
1674
|
+
percent: "%",
|
|
1675
|
+
perMille: "\u2030",
|
|
1676
|
+
zeroDigit: "0",
|
|
1677
|
+
digit: "#",
|
|
1678
|
+
patternSeparator: ";",
|
|
1679
|
+
infinity: "Infinity",
|
|
1680
|
+
nan: "NaN",
|
|
1681
|
+
minusSign: "-"
|
|
1682
|
+
});
|
|
1683
|
+
function splitSubPatterns(pattern, format) {
|
|
1684
|
+
const index = pattern.indexOf(format.patternSeparator);
|
|
1685
|
+
if (index === -1) return { positive: pattern, negative: null };
|
|
1686
|
+
return {
|
|
1687
|
+
positive: pattern.substring(0, index),
|
|
1688
|
+
negative: pattern.substring(index + format.patternSeparator.length)
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function parseSubPattern(subPattern, format) {
|
|
1692
|
+
const special = /* @__PURE__ */ new Set([
|
|
1693
|
+
format.digit,
|
|
1694
|
+
format.zeroDigit,
|
|
1695
|
+
format.groupingSeparator,
|
|
1696
|
+
format.decimalSeparator
|
|
1697
|
+
]);
|
|
1698
|
+
let start = 0;
|
|
1699
|
+
while (start < subPattern.length && !special.has(subPattern[start])) start++;
|
|
1700
|
+
let end = start;
|
|
1701
|
+
while (end < subPattern.length && special.has(subPattern[end])) end++;
|
|
1702
|
+
const prefix = subPattern.substring(0, start);
|
|
1703
|
+
const numeric = subPattern.substring(start, end);
|
|
1704
|
+
const suffix = subPattern.substring(end);
|
|
1705
|
+
const decimalIndex = numeric.indexOf(format.decimalSeparator);
|
|
1706
|
+
const integerPart = decimalIndex === -1 ? numeric : numeric.substring(0, decimalIndex);
|
|
1707
|
+
const fractionPart = decimalIndex === -1 ? "" : numeric.substring(decimalIndex + 1);
|
|
1708
|
+
const groupingIndex = integerPart.lastIndexOf(format.groupingSeparator);
|
|
1709
|
+
const affixes = prefix + suffix;
|
|
1710
|
+
let multiplier = 1;
|
|
1711
|
+
if (affixes.includes(format.percent)) multiplier = 100;
|
|
1712
|
+
else if (affixes.includes(format.perMille)) multiplier = 1e3;
|
|
1713
|
+
return {
|
|
1714
|
+
prefix,
|
|
1715
|
+
suffix,
|
|
1716
|
+
multiplier,
|
|
1717
|
+
minInteger: countOccurrences(integerPart, format.zeroDigit),
|
|
1718
|
+
minFraction: countOccurrences(fractionPart, format.zeroDigit),
|
|
1719
|
+
maxFraction: Math.min(fractionPart.length, 100),
|
|
1720
|
+
groupingSize: groupingIndex === -1 ? 0 : integerPart.length - groupingIndex - 1
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function countOccurrences(text, char) {
|
|
1724
|
+
let total = 0;
|
|
1725
|
+
for (const current of text) {
|
|
1726
|
+
if (current === char) total++;
|
|
1727
|
+
}
|
|
1728
|
+
return total;
|
|
1729
|
+
}
|
|
1730
|
+
function applyGrouping(digits, size, separator) {
|
|
1731
|
+
if (size <= 0 || digits.length <= size) return digits;
|
|
1732
|
+
let result = "";
|
|
1733
|
+
for (let i = 0; i < digits.length; i++) {
|
|
1734
|
+
const fromEnd = digits.length - i;
|
|
1735
|
+
if (i > 0 && fromEnd % size === 0) result += separator;
|
|
1736
|
+
result += digits[i];
|
|
1737
|
+
}
|
|
1738
|
+
return result;
|
|
1739
|
+
}
|
|
1740
|
+
function translateDigits(text, zeroDigit) {
|
|
1741
|
+
const offset = zeroDigit.codePointAt(0) - 48;
|
|
1742
|
+
if (offset === 0) return text;
|
|
1743
|
+
return text.replaceAll(
|
|
1744
|
+
/\d/g,
|
|
1745
|
+
(digit) => String.fromCodePoint(digit.codePointAt(0) + offset)
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
function formatMagnitude(magnitude, spec, format) {
|
|
1749
|
+
const fixed = magnitude.toFixed(spec.maxFraction);
|
|
1750
|
+
const [rawInteger, rawFraction = ""] = fixed.split(".");
|
|
1751
|
+
let fraction = rawFraction;
|
|
1752
|
+
while (fraction.length > spec.minFraction && fraction.endsWith("0")) {
|
|
1753
|
+
fraction = fraction.slice(0, -1);
|
|
1754
|
+
}
|
|
1755
|
+
let integer = rawInteger.padStart(spec.minInteger, "0");
|
|
1756
|
+
if (spec.minInteger === 0 && integer === "0" && fraction.length > 0) {
|
|
1757
|
+
integer = "";
|
|
1758
|
+
}
|
|
1759
|
+
integer = applyGrouping(integer, spec.groupingSize, format.groupingSeparator);
|
|
1760
|
+
const body = fraction.length > 0 ? integer + format.decimalSeparator + fraction : integer;
|
|
1761
|
+
return translateDigits(body, format.zeroDigit);
|
|
1762
|
+
}
|
|
1763
|
+
function formatNumber(value, pattern, decimalFormat = DEFAULT_DECIMAL_FORMAT) {
|
|
1764
|
+
const format = { ...DEFAULT_DECIMAL_FORMAT, ...decimalFormat };
|
|
1765
|
+
if (typeof value !== "number" || Number.isNaN(value)) return format.nan;
|
|
1766
|
+
const subPatterns = splitSubPatterns(pattern, format);
|
|
1767
|
+
const positive = parseSubPattern(subPatterns.positive, format);
|
|
1768
|
+
const isNegative = value < 0;
|
|
1769
|
+
let spec = positive;
|
|
1770
|
+
let prefix = positive.prefix;
|
|
1771
|
+
let suffix = positive.suffix;
|
|
1772
|
+
if (isNegative) {
|
|
1773
|
+
if (subPatterns.negative !== null) {
|
|
1774
|
+
spec = parseSubPattern(subPatterns.negative, format);
|
|
1775
|
+
prefix = spec.prefix;
|
|
1776
|
+
suffix = spec.suffix;
|
|
1777
|
+
} else {
|
|
1778
|
+
prefix = format.minusSign + positive.prefix;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
const magnitude = Math.abs(value) * spec.multiplier;
|
|
1782
|
+
const body = Number.isFinite(magnitude) ? formatMagnitude(magnitude, spec, format) : format.infinity;
|
|
1783
|
+
return prefix + body + suffix;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// src/xslt/functions.js
|
|
1787
|
+
var VENDOR = "@tradik/xslt-processor";
|
|
1788
|
+
var VENDOR_URL = "https://github.com/spagu/XSLT-Processor";
|
|
1789
|
+
var SYSTEM_PROPERTIES = Object.freeze({
|
|
1790
|
+
"xsl:version": "1",
|
|
1791
|
+
"xsl:vendor": VENDOR,
|
|
1792
|
+
"xsl:vendor-url": VENDOR_URL
|
|
1793
|
+
});
|
|
1794
|
+
function ownerDocumentOf(node) {
|
|
1795
|
+
return node.ownerDocument || node;
|
|
1796
|
+
}
|
|
1797
|
+
function toStringList(evaluator, stringify, value) {
|
|
1798
|
+
if (Array.isArray(value)) {
|
|
1799
|
+
return value.map((node) => evaluator.getStringValue(node));
|
|
1800
|
+
}
|
|
1801
|
+
return [stringify(value)];
|
|
1802
|
+
}
|
|
1803
|
+
function splitQName(qname) {
|
|
1804
|
+
const colon = qname.indexOf(":");
|
|
1805
|
+
if (colon === -1) return { prefix: null, localName: qname };
|
|
1806
|
+
return {
|
|
1807
|
+
prefix: qname.substring(0, colon),
|
|
1808
|
+
localName: qname.substring(colon + 1)
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
function createXsltFunctions(engine) {
|
|
1812
|
+
const evaluator = engine.xpathEvaluator;
|
|
1813
|
+
const stringify = evaluator.toString.bind(evaluator);
|
|
1814
|
+
const evaluate2 = (arg, ctx) => evaluator.evaluate(arg, ctx);
|
|
1815
|
+
const asString = (arg, ctx) => stringify(evaluate2(arg, ctx));
|
|
1816
|
+
return {
|
|
1817
|
+
/**
|
|
1818
|
+
* `document(object, base?)` - load external XML documents.
|
|
1819
|
+
*
|
|
1820
|
+
* An empty URI denotes the stylesheet itself. Without a document loader, or
|
|
1821
|
+
* when the loader returns null, the result is an empty node-set. The
|
|
1822
|
+
* optional second argument is read as a base URI string.
|
|
1823
|
+
*/
|
|
1824
|
+
document: (args, ctx) => {
|
|
1825
|
+
const baseUri = args.length > 1 ? asString(args[1], ctx) : engine.baseUri;
|
|
1826
|
+
const uris = toStringList(evaluator, stringify, evaluate2(args[0], ctx));
|
|
1827
|
+
const result = [];
|
|
1828
|
+
for (const uri of uris) {
|
|
1829
|
+
const doc = engine.loadDocument(uri, baseUri || engine.baseUri);
|
|
1830
|
+
if (doc && !result.includes(doc)) result.push(doc);
|
|
1831
|
+
}
|
|
1832
|
+
return result;
|
|
1833
|
+
},
|
|
1834
|
+
/** `key(name, value)` - look up nodes through an `xsl:key` index. */
|
|
1835
|
+
key: (args, ctx) => {
|
|
1836
|
+
const name = asString(args[0], ctx);
|
|
1837
|
+
const values = toStringList(evaluator, stringify, evaluate2(args[1], ctx));
|
|
1838
|
+
return engine.keyRegistry.lookup(name, values, ownerDocumentOf(ctx.node));
|
|
1839
|
+
},
|
|
1840
|
+
/** `format-number(number, pattern, decimalFormat?)`. */
|
|
1841
|
+
"format-number": (args, ctx) => {
|
|
1842
|
+
const value = evaluator.toNumber(evaluate2(args[0], ctx));
|
|
1843
|
+
const pattern = asString(args[1], ctx);
|
|
1844
|
+
const formatName = args.length > 2 ? asString(args[2], ctx) : "";
|
|
1845
|
+
const format = engine.decimalFormats[formatName] || DEFAULT_DECIMAL_FORMAT;
|
|
1846
|
+
return formatNumber(value, pattern, format);
|
|
1847
|
+
},
|
|
1848
|
+
/** `current()` - the XSLT current node, not the XPath context node. */
|
|
1849
|
+
current: (args, ctx) => {
|
|
1850
|
+
const currentNode = ctx.hostContext?.currentNode;
|
|
1851
|
+
return currentNode ? [currentNode] : [ctx.node];
|
|
1852
|
+
},
|
|
1853
|
+
/** `generate-id(node-set?)` - a stable id for the life of the transform. */
|
|
1854
|
+
"generate-id": (args, ctx) => {
|
|
1855
|
+
let node = ctx.node;
|
|
1856
|
+
if (args.length > 0) {
|
|
1857
|
+
const nodeSet = evaluate2(args[0], ctx);
|
|
1858
|
+
node = Array.isArray(nodeSet) ? nodeSet[0] : nodeSet;
|
|
1859
|
+
}
|
|
1860
|
+
return node ? engine.generateId(node) : "";
|
|
1861
|
+
},
|
|
1862
|
+
/** `system-property(name)` - XSLT version and vendor information. */
|
|
1863
|
+
"system-property": (args, ctx) => {
|
|
1864
|
+
const name = asString(args[0], ctx);
|
|
1865
|
+
return Object.hasOwn(SYSTEM_PROPERTIES, name) ? SYSTEM_PROPERTIES[name] : "";
|
|
1866
|
+
},
|
|
1867
|
+
/** `function-available(name)` - reflects the evaluator function table. */
|
|
1868
|
+
"function-available": (args, ctx) => {
|
|
1869
|
+
const name = asString(args[0], ctx);
|
|
1870
|
+
return Object.hasOwn(evaluator.functions, name);
|
|
1871
|
+
},
|
|
1872
|
+
/** `element-available(name)` - reflects the XSLT elements the engine runs. */
|
|
1873
|
+
"element-available": (args, ctx) => {
|
|
1874
|
+
const { prefix, localName } = splitQName(asString(args[0], ctx));
|
|
1875
|
+
if (!prefix) return false;
|
|
1876
|
+
const namespaceUri = ctx.namespaces[prefix] ?? (prefix === "xsl" ? XSLT_NAMESPACE : null);
|
|
1877
|
+
return namespaceUri === XSLT_NAMESPACE && isXsltElementAvailable(localName);
|
|
1878
|
+
},
|
|
1879
|
+
/**
|
|
1880
|
+
* `unparsed-entity-uri(name)` - always empty.
|
|
1881
|
+
*
|
|
1882
|
+
* Unparsed entity declarations are not exposed by the DOM, so this
|
|
1883
|
+
* processor cannot resolve them; returning the empty string keeps
|
|
1884
|
+
* stylesheets that call the function working.
|
|
1885
|
+
*/
|
|
1886
|
+
"unparsed-entity-uri": () => ""
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// src/xslt/keys.js
|
|
1891
|
+
var KeyIndexRegistry = class {
|
|
1892
|
+
/**
|
|
1893
|
+
* @param {Object} options - Registry configuration
|
|
1894
|
+
* @param {Object<string, {match: string, use: string}>} options.keys - Declared keys by name
|
|
1895
|
+
* @param {(node: Node, pattern: string) => boolean} options.matchesPattern - XSLT pattern matcher
|
|
1896
|
+
* @param {(node: Node, expression: string) => string[]} options.evaluateUse - `use` evaluator returning key values
|
|
1897
|
+
*/
|
|
1898
|
+
constructor({ keys, matchesPattern, evaluateUse }) {
|
|
1899
|
+
this.keys = keys;
|
|
1900
|
+
this.matchesPattern = matchesPattern;
|
|
1901
|
+
this.evaluateUse = evaluateUse;
|
|
1902
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Drop every cached index, for example after the key declarations changed.
|
|
1906
|
+
*
|
|
1907
|
+
* @returns {void}
|
|
1908
|
+
*
|
|
1909
|
+
* @example
|
|
1910
|
+
* registry.clear();
|
|
1911
|
+
*/
|
|
1912
|
+
clear() {
|
|
1913
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Look up the nodes indexed under one or more key values.
|
|
1917
|
+
*
|
|
1918
|
+
* @param {string} name - The key name
|
|
1919
|
+
* @param {string|string[]} values - One key value, or several to union
|
|
1920
|
+
* @param {Document} doc - The document to search
|
|
1921
|
+
* @returns {Node[]} Matching nodes in document order, without duplicates
|
|
1922
|
+
* @throws {Error} When the key name was never declared
|
|
1923
|
+
*
|
|
1924
|
+
* @example
|
|
1925
|
+
* registry.lookup('byId', 'a1', xmlDoc);
|
|
1926
|
+
*/
|
|
1927
|
+
lookup(name, values, doc) {
|
|
1928
|
+
if (!Object.hasOwn(this.keys, name)) {
|
|
1929
|
+
throw new Error(`Undefined key: ${name}`);
|
|
1930
|
+
}
|
|
1931
|
+
const index = this.getIndex(name, doc);
|
|
1932
|
+
const wanted = Array.isArray(values) ? values : [values];
|
|
1933
|
+
const result = [];
|
|
1934
|
+
for (const value of wanted) {
|
|
1935
|
+
for (const node of index.get(value) || []) {
|
|
1936
|
+
if (!result.includes(node)) result.push(node);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return result;
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Get (building if needed) the index of one key for one document.
|
|
1943
|
+
*
|
|
1944
|
+
* @param {string} name - The key name
|
|
1945
|
+
* @param {Document} doc - The document being indexed
|
|
1946
|
+
* @returns {Map<string, Node[]>} Key value to nodes
|
|
1947
|
+
*/
|
|
1948
|
+
getIndex(name, doc) {
|
|
1949
|
+
let byName = this.cache.get(doc);
|
|
1950
|
+
if (!byName) {
|
|
1951
|
+
byName = /* @__PURE__ */ new Map();
|
|
1952
|
+
this.cache.set(doc, byName);
|
|
1953
|
+
}
|
|
1954
|
+
let index = byName.get(name);
|
|
1955
|
+
if (!index) {
|
|
1956
|
+
index = this.buildIndex(name, doc);
|
|
1957
|
+
byName.set(name, index);
|
|
1958
|
+
}
|
|
1959
|
+
return index;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Build the index of one key for one document.
|
|
1963
|
+
*
|
|
1964
|
+
* @param {string} name - The key name
|
|
1965
|
+
* @param {Document} doc - The document being indexed
|
|
1966
|
+
* @returns {Map<string, Node[]>} Key value to nodes
|
|
1967
|
+
*/
|
|
1968
|
+
buildIndex(name, doc) {
|
|
1969
|
+
const { match, use } = this.keys[name];
|
|
1970
|
+
const index = /* @__PURE__ */ new Map();
|
|
1971
|
+
for (const node of documentOrderNodes(doc)) {
|
|
1972
|
+
if (!this.matchesPattern(node, match)) continue;
|
|
1973
|
+
for (const value of this.evaluateUse(node, use)) {
|
|
1974
|
+
const bucket = index.get(value);
|
|
1975
|
+
if (bucket) bucket.push(node);
|
|
1976
|
+
else index.set(value, [node]);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return index;
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
function* documentOrderNodes(root) {
|
|
1983
|
+
const stack = [root];
|
|
1984
|
+
while (stack.length > 0) {
|
|
1985
|
+
const current = stack.pop();
|
|
1986
|
+
yield current;
|
|
1987
|
+
if (current.nodeType === 1 && current.attributes) {
|
|
1988
|
+
for (const attribute of current.attributes) yield attribute;
|
|
1989
|
+
}
|
|
1990
|
+
const children = current.childNodes;
|
|
1991
|
+
if (children) {
|
|
1992
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/xslt/number.js
|
|
1998
|
+
var COUNTABLE_NODE_TYPES = /* @__PURE__ */ new Set([1, 3, 4, 7, 8]);
|
|
1999
|
+
function matchesDefaultCount(candidate, node) {
|
|
2000
|
+
if (candidate.nodeType !== node.nodeType) return false;
|
|
2001
|
+
if (candidate.nodeType === 1) return candidate.nodeName === node.nodeName;
|
|
2002
|
+
return true;
|
|
2003
|
+
}
|
|
2004
|
+
function createCountPredicate(node, count, matcher) {
|
|
2005
|
+
if (count) return (candidate) => matcher(candidate, count);
|
|
2006
|
+
return (candidate) => matchesDefaultCount(candidate, node);
|
|
2007
|
+
}
|
|
2008
|
+
function createFromPredicate(from, matcher) {
|
|
2009
|
+
if (!from) return () => false;
|
|
2010
|
+
return (candidate) => matcher(candidate, from);
|
|
2011
|
+
}
|
|
2012
|
+
function siblingPosition(node, isCounted) {
|
|
2013
|
+
let position = 1;
|
|
2014
|
+
let sibling = node.previousSibling;
|
|
2015
|
+
while (sibling) {
|
|
2016
|
+
if (COUNTABLE_NODE_TYPES.has(sibling.nodeType) && isCounted(sibling)) {
|
|
2017
|
+
position++;
|
|
2018
|
+
}
|
|
2019
|
+
sibling = sibling.previousSibling;
|
|
2020
|
+
}
|
|
2021
|
+
return position;
|
|
2022
|
+
}
|
|
2023
|
+
function nodesUpToTarget(target) {
|
|
2024
|
+
const root = target.ownerDocument || target;
|
|
2025
|
+
const result = [];
|
|
2026
|
+
const stack = [root];
|
|
2027
|
+
while (stack.length > 0) {
|
|
2028
|
+
const current = stack.pop();
|
|
2029
|
+
result.push(current);
|
|
2030
|
+
if (current === target) break;
|
|
2031
|
+
const children = current.childNodes;
|
|
2032
|
+
if (children) {
|
|
2033
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
return result;
|
|
2037
|
+
}
|
|
2038
|
+
function countSingle(node, isCounted, isFrom) {
|
|
2039
|
+
let current = node;
|
|
2040
|
+
while (current && current.nodeType !== 9) {
|
|
2041
|
+
if (isFrom(current)) return [];
|
|
2042
|
+
if (isCounted(current)) return [siblingPosition(current, isCounted)];
|
|
2043
|
+
current = current.parentNode;
|
|
2044
|
+
}
|
|
2045
|
+
return [];
|
|
2046
|
+
}
|
|
2047
|
+
function countMultiple(node, isCounted, isFrom) {
|
|
2048
|
+
const numbers = [];
|
|
2049
|
+
let current = node;
|
|
2050
|
+
while (current && current.nodeType !== 9) {
|
|
2051
|
+
if (isFrom(current)) break;
|
|
2052
|
+
if (isCounted(current)) {
|
|
2053
|
+
numbers.unshift(siblingPosition(current, isCounted));
|
|
2054
|
+
}
|
|
2055
|
+
current = current.parentNode;
|
|
2056
|
+
}
|
|
2057
|
+
return numbers;
|
|
2058
|
+
}
|
|
2059
|
+
function countAny(node, isCounted, isFrom) {
|
|
2060
|
+
let total = 0;
|
|
2061
|
+
for (const candidate of nodesUpToTarget(node)) {
|
|
2062
|
+
if (!COUNTABLE_NODE_TYPES.has(candidate.nodeType)) continue;
|
|
2063
|
+
if (isFrom(candidate)) {
|
|
2064
|
+
total = 0;
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
if (isCounted(candidate)) total++;
|
|
2068
|
+
}
|
|
2069
|
+
return total > 0 ? [total] : [];
|
|
2070
|
+
}
|
|
2071
|
+
function countXsltNumber(node, options, matcher) {
|
|
2072
|
+
const { level = "single", count = null, from = null } = options;
|
|
2073
|
+
const isCounted = createCountPredicate(node, count, matcher);
|
|
2074
|
+
const isFrom = createFromPredicate(from, matcher);
|
|
2075
|
+
if (level === "any") return countAny(node, isCounted, isFrom);
|
|
2076
|
+
if (level === "multiple") return countMultiple(node, isCounted, isFrom);
|
|
2077
|
+
return countSingle(node, isCounted, isFrom);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// src/xslt/numberFormat.js
|
|
2081
|
+
function toAlphabetic(value, upperCase) {
|
|
2082
|
+
let remaining = value;
|
|
2083
|
+
let result = "";
|
|
2084
|
+
while (remaining > 0) {
|
|
2085
|
+
const index = (remaining - 1) % 26;
|
|
2086
|
+
result = String.fromCodePoint((upperCase ? 65 : 97) + index) + result;
|
|
2087
|
+
remaining = Math.floor((remaining - 1) / 26);
|
|
2088
|
+
}
|
|
2089
|
+
return result;
|
|
2090
|
+
}
|
|
2091
|
+
var ROMAN_NUMERALS = Object.freeze([
|
|
2092
|
+
["M", 1e3],
|
|
2093
|
+
["CM", 900],
|
|
2094
|
+
["D", 500],
|
|
2095
|
+
["CD", 400],
|
|
2096
|
+
["C", 100],
|
|
2097
|
+
["XC", 90],
|
|
2098
|
+
["L", 50],
|
|
2099
|
+
["XL", 40],
|
|
2100
|
+
["X", 10],
|
|
2101
|
+
["IX", 9],
|
|
2102
|
+
["V", 5],
|
|
2103
|
+
["IV", 4],
|
|
2104
|
+
["I", 1]
|
|
2105
|
+
]);
|
|
2106
|
+
function toRoman(value) {
|
|
2107
|
+
let remaining = value;
|
|
2108
|
+
let result = "";
|
|
2109
|
+
for (const [numeral, amount] of ROMAN_NUMERALS) {
|
|
2110
|
+
while (remaining >= amount) {
|
|
2111
|
+
result += numeral;
|
|
2112
|
+
remaining -= amount;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return result;
|
|
2116
|
+
}
|
|
2117
|
+
function formatToken(value, token) {
|
|
2118
|
+
if (/^\d+$/.test(token)) {
|
|
2119
|
+
return String(value).padStart(token.length, "0");
|
|
2120
|
+
}
|
|
2121
|
+
if (value <= 0) return String(value);
|
|
2122
|
+
switch (token) {
|
|
2123
|
+
case "a":
|
|
2124
|
+
return toAlphabetic(value, false);
|
|
2125
|
+
case "A":
|
|
2126
|
+
return toAlphabetic(value, true);
|
|
2127
|
+
case "i":
|
|
2128
|
+
return toRoman(value).toLowerCase();
|
|
2129
|
+
case "I":
|
|
2130
|
+
return toRoman(value);
|
|
2131
|
+
default:
|
|
2132
|
+
return String(value);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
function parseFormat(format) {
|
|
2136
|
+
const parts = format.match(/[a-zA-Z0-9]+|[^a-zA-Z0-9]+/g) || [];
|
|
2137
|
+
const isToken = (part) => /^[a-zA-Z0-9]+$/.test(part);
|
|
2138
|
+
const tokens = [];
|
|
2139
|
+
const separators = [];
|
|
2140
|
+
let prefix = "";
|
|
2141
|
+
let suffix = "";
|
|
2142
|
+
for (const part of parts) {
|
|
2143
|
+
if (isToken(part)) tokens.push(part);
|
|
2144
|
+
else if (tokens.length === 0) prefix = part;
|
|
2145
|
+
else separators.push(part);
|
|
2146
|
+
}
|
|
2147
|
+
if (parts.length > 0 && tokens.length > 0 && !isToken(parts.at(-1))) {
|
|
2148
|
+
suffix = separators.pop();
|
|
2149
|
+
}
|
|
2150
|
+
if (tokens.length === 0) tokens.push("1");
|
|
2151
|
+
return { prefix, suffix, tokens, separators };
|
|
2152
|
+
}
|
|
2153
|
+
function formatXsltNumber(numbers, format = "1") {
|
|
2154
|
+
if (numbers.length === 0) return "";
|
|
2155
|
+
const { prefix, suffix, tokens, separators } = parseFormat(format);
|
|
2156
|
+
let result = prefix;
|
|
2157
|
+
numbers.forEach((value, index) => {
|
|
2158
|
+
if (index > 0) {
|
|
2159
|
+
const separator = separators[index - 1] ?? separators.at(-1) ?? ".";
|
|
2160
|
+
result += separator;
|
|
2161
|
+
}
|
|
2162
|
+
result += formatToken(value, tokens[index] ?? tokens.at(-1));
|
|
2163
|
+
});
|
|
2164
|
+
return result + suffix;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// src/xslt/uri.js
|
|
2168
|
+
var ABSOLUTE_URI_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
2169
|
+
function resolveUri(href, baseUri) {
|
|
2170
|
+
if (!href) return href;
|
|
2171
|
+
if (!baseUri || isAbsoluteUri(href) || href.startsWith("/")) {
|
|
2172
|
+
return href;
|
|
2173
|
+
}
|
|
2174
|
+
const lastSlash = baseUri.lastIndexOf("/");
|
|
2175
|
+
const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
|
|
2176
|
+
return baseDir + href;
|
|
2177
|
+
}
|
|
2178
|
+
function isAbsoluteUri(uri) {
|
|
2179
|
+
return ABSOLUTE_URI_PATTERN.test(uri);
|
|
2180
|
+
}
|
|
2181
|
+
function stripFragment(uri) {
|
|
2182
|
+
if (typeof uri !== "string") return "";
|
|
2183
|
+
const hash = uri.indexOf("#");
|
|
2184
|
+
return hash === -1 ? uri : uri.substring(0, hash);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
// src/xslt/whitespace.js
|
|
2188
|
+
var DOCUMENT_TYPE_NODE = 10;
|
|
2189
|
+
function nameTestPriority(nameTest) {
|
|
2190
|
+
if (nameTest === "*") return -0.5;
|
|
2191
|
+
if (nameTest.endsWith(":*")) return -0.25;
|
|
2192
|
+
return 0;
|
|
2193
|
+
}
|
|
2194
|
+
function matchesNameTest(element, nameTest) {
|
|
2195
|
+
if (nameTest === "*") return true;
|
|
2196
|
+
if (nameTest.endsWith(":*")) {
|
|
2197
|
+
const prefix = nameTest.slice(0, -2);
|
|
2198
|
+
return element.nodeName.startsWith(`${prefix}:`);
|
|
2199
|
+
}
|
|
2200
|
+
return element.nodeName === nameTest || element.localName === nameTest;
|
|
2201
|
+
}
|
|
2202
|
+
var WhitespaceFilter = class {
|
|
2203
|
+
/**
|
|
2204
|
+
* @param {string[]} [stripSpace] - Name tests from `xsl:strip-space`
|
|
2205
|
+
* @param {string[]} [preserveSpace] - Name tests from `xsl:preserve-space`
|
|
2206
|
+
*/
|
|
2207
|
+
constructor(stripSpace = [], preserveSpace = []) {
|
|
2208
|
+
this.stripSpace = stripSpace;
|
|
2209
|
+
this.preserveSpace = preserveSpace;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Whether the filter can remove anything at all.
|
|
2213
|
+
*
|
|
2214
|
+
* @returns {boolean} True when at least one `xsl:strip-space` was declared
|
|
2215
|
+
*
|
|
2216
|
+
* @example
|
|
2217
|
+
* new WhitespaceFilter(['*']).isActive(); // true
|
|
2218
|
+
*/
|
|
2219
|
+
isActive() {
|
|
2220
|
+
return this.stripSpace.length > 0;
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Whether whitespace-only children of an element are stripped.
|
|
2224
|
+
*
|
|
2225
|
+
* The most specific name test wins; `xsl:preserve-space` wins ties.
|
|
2226
|
+
*
|
|
2227
|
+
* @param {Element} element - The parent element
|
|
2228
|
+
* @returns {boolean} True when whitespace-only text children are removed
|
|
2229
|
+
*
|
|
2230
|
+
* @example
|
|
2231
|
+
* new WhitespaceFilter(['*'], ['pre']).isStripped(preElement); // false
|
|
2232
|
+
*/
|
|
2233
|
+
isStripped(element) {
|
|
2234
|
+
let stripPriority = -Infinity;
|
|
2235
|
+
let preservePriority = -Infinity;
|
|
2236
|
+
for (const nameTest of this.stripSpace) {
|
|
2237
|
+
if (matchesNameTest(element, nameTest)) {
|
|
2238
|
+
stripPriority = Math.max(stripPriority, nameTestPriority(nameTest));
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
for (const nameTest of this.preserveSpace) {
|
|
2242
|
+
if (matchesNameTest(element, nameTest)) {
|
|
2243
|
+
preservePriority = Math.max(
|
|
2244
|
+
preservePriority,
|
|
2245
|
+
nameTestPriority(nameTest)
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return stripPriority > -Infinity && stripPriority > preservePriority;
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
function hasXmlSpacePreserve(node) {
|
|
2253
|
+
let current = node;
|
|
2254
|
+
while (current?.nodeType === 1) {
|
|
2255
|
+
const value = current.getAttribute("xml:space");
|
|
2256
|
+
if (value === "preserve") return true;
|
|
2257
|
+
if (value === "default") return false;
|
|
2258
|
+
current = current.parentNode;
|
|
2259
|
+
}
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
function pruneWhitespace(root, filter) {
|
|
2263
|
+
const doomed = [];
|
|
2264
|
+
const stack = [root];
|
|
2265
|
+
while (stack.length > 0) {
|
|
2266
|
+
const current = stack.pop();
|
|
2267
|
+
if ((current.nodeType === 3 || current.nodeType === 4) && current.nodeValue !== null && current.nodeValue.trim() === "" && current.parentNode?.nodeType === 1 && filter.isStripped(current.parentNode) && !hasXmlSpacePreserve(current.parentNode)) {
|
|
2268
|
+
doomed.push(current);
|
|
2269
|
+
}
|
|
2270
|
+
const children = current.childNodes;
|
|
2271
|
+
if (children) {
|
|
2272
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
for (const node of doomed) node.remove();
|
|
2276
|
+
}
|
|
2277
|
+
function stripWhitespaceNodes(sourceNode, filter, targetDoc) {
|
|
2278
|
+
let root;
|
|
2279
|
+
if (sourceNode.nodeType === 9) {
|
|
2280
|
+
root = targetDoc;
|
|
2281
|
+
for (const child of Array.from(sourceNode.childNodes)) {
|
|
2282
|
+
if (child.nodeType === DOCUMENT_TYPE_NODE) continue;
|
|
2283
|
+
targetDoc.appendChild(targetDoc.importNode(child, true));
|
|
2284
|
+
}
|
|
2285
|
+
} else {
|
|
2286
|
+
root = targetDoc.importNode(sourceNode, true);
|
|
2287
|
+
targetDoc.appendChild(root);
|
|
2288
|
+
}
|
|
2289
|
+
pruneWhitespace(root, filter);
|
|
2290
|
+
return root;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// src/xslt/literalResult.js
|
|
2294
|
+
function lookupNamespaceUri(node, prefix) {
|
|
2295
|
+
if (typeof node.lookupNamespaceURI === "function") {
|
|
2296
|
+
const found = node.lookupNamespaceURI(prefix);
|
|
2297
|
+
if (found) return found;
|
|
2298
|
+
}
|
|
2299
|
+
const attributeName = prefix ? `xmlns:${prefix}` : "xmlns";
|
|
2300
|
+
let current = node;
|
|
2301
|
+
while (current?.nodeType === 1) {
|
|
2302
|
+
const value = current.getAttribute(attributeName);
|
|
2303
|
+
if (value) return value;
|
|
2304
|
+
current = current.parentNode;
|
|
2305
|
+
}
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
var NamespaceAliasMap = class {
|
|
2309
|
+
constructor() {
|
|
2310
|
+
this.byUri = /* @__PURE__ */ new Map();
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Record one `xsl:namespace-alias` declaration.
|
|
2314
|
+
*
|
|
2315
|
+
* @param {Element} node - The `xsl:namespace-alias` element
|
|
2316
|
+
* @returns {void}
|
|
2317
|
+
*
|
|
2318
|
+
* @example
|
|
2319
|
+
* aliases.add(namespaceAliasElement);
|
|
2320
|
+
*/
|
|
2321
|
+
add(node) {
|
|
2322
|
+
const stylesheetPrefix = node.getAttribute("stylesheet-prefix");
|
|
2323
|
+
const resultPrefix = node.getAttribute("result-prefix");
|
|
2324
|
+
if (!stylesheetPrefix || !resultPrefix) return;
|
|
2325
|
+
const fromUri = lookupNamespaceUri(
|
|
2326
|
+
node,
|
|
2327
|
+
stylesheetPrefix === "#default" ? null : stylesheetPrefix
|
|
2328
|
+
);
|
|
2329
|
+
if (!fromUri) return;
|
|
2330
|
+
const isDefaultResult = resultPrefix === "#default";
|
|
2331
|
+
const toUri = lookupNamespaceUri(
|
|
2332
|
+
node,
|
|
2333
|
+
isDefaultResult ? null : resultPrefix
|
|
2334
|
+
);
|
|
2335
|
+
this.byUri.set(fromUri, {
|
|
2336
|
+
uri: toUri,
|
|
2337
|
+
prefix: isDefaultResult ? null : resultPrefix
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Whether any alias was declared.
|
|
2342
|
+
*
|
|
2343
|
+
* @returns {boolean} True when at least one alias is known
|
|
2344
|
+
*
|
|
2345
|
+
* @example
|
|
2346
|
+
* aliases.isEmpty();
|
|
2347
|
+
*/
|
|
2348
|
+
isEmpty() {
|
|
2349
|
+
return this.byUri.size === 0;
|
|
2350
|
+
}
|
|
2351
|
+
/**
|
|
2352
|
+
* Apply aliasing to a literal result name.
|
|
2353
|
+
*
|
|
2354
|
+
* @param {string|null} namespaceUri - The namespace of the stylesheet node
|
|
2355
|
+
* @param {string} localName - The local name of the stylesheet node
|
|
2356
|
+
* @returns {{namespaceUri: (string|null), qname: string}|null} The aliased name, or null when no alias applies
|
|
2357
|
+
*
|
|
2358
|
+
* @example
|
|
2359
|
+
* aliases.resolve('http://www.w3.org/1999/XSL/TransformAlias', 'stylesheet');
|
|
2360
|
+
* // { namespaceUri: 'http://www.w3.org/1999/XSL/Transform', qname: 'xsl:stylesheet' }
|
|
2361
|
+
*/
|
|
2362
|
+
resolve(namespaceUri, localName) {
|
|
2363
|
+
const alias = namespaceUri ? this.byUri.get(namespaceUri) : void 0;
|
|
2364
|
+
if (!alias) return null;
|
|
2365
|
+
return {
|
|
2366
|
+
namespaceUri: alias.uri,
|
|
2367
|
+
qname: alias.prefix ? `${alias.prefix}:${localName}` : localName
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
function shouldCopyAttribute(attribute, xsltNamespace) {
|
|
2372
|
+
if (attribute.namespaceURI === xsltNamespace) return false;
|
|
2373
|
+
if (attribute.name === "xmlns" || attribute.name.startsWith("xmlns:")) {
|
|
2374
|
+
return false;
|
|
2375
|
+
}
|
|
2376
|
+
return !attribute.name.startsWith("xsl:");
|
|
2377
|
+
}
|
|
2378
|
+
function getXsltAttribute(node, localName, xsltNamespace) {
|
|
2379
|
+
if (!node.attributes) return null;
|
|
2380
|
+
for (const attribute of node.attributes) {
|
|
2381
|
+
const matchesNamespace = attribute.namespaceURI === xsltNamespace && (attribute.localName || attribute.name) === localName;
|
|
2382
|
+
if (matchesNamespace || attribute.name === `xsl:${localName}`) {
|
|
2383
|
+
return attribute.value;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/xslt/resultTree.js
|
|
2390
|
+
function createResultDocument(ownerDocument) {
|
|
2391
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
2392
|
+
}
|
|
2393
|
+
function importResultNode(node, targetDoc) {
|
|
2394
|
+
const copy = targetDoc.importNode(node, false);
|
|
2395
|
+
if (node._disableOutputEscaping) {
|
|
2396
|
+
copy._disableOutputEscaping = true;
|
|
2397
|
+
}
|
|
2398
|
+
if (node.childNodes) {
|
|
2399
|
+
for (const child of node.childNodes) {
|
|
2400
|
+
copy.appendChild(importResultNode(child, targetDoc));
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return copy;
|
|
2404
|
+
}
|
|
2405
|
+
function importResultFragment(fragment, targetDoc) {
|
|
2406
|
+
if (fragment.ownerDocument === targetDoc) return fragment;
|
|
2407
|
+
const imported = targetDoc.createDocumentFragment();
|
|
2408
|
+
for (const child of fragment.childNodes) {
|
|
2409
|
+
imported.appendChild(importResultNode(child, targetDoc));
|
|
2410
|
+
}
|
|
2411
|
+
return imported;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// src/xslt/templatePriority.js
|
|
2415
|
+
var NAME = String.raw`[A-Za-z_][\w.-]*`;
|
|
2416
|
+
var QNAME = `(?:${NAME}:)?${NAME}`;
|
|
2417
|
+
var QNAME_PATTERN = new RegExp(`^(?:child::|attribute::|@)?${QNAME}$`);
|
|
2418
|
+
var PREFIX_WILDCARD_PATTERN = new RegExp(
|
|
2419
|
+
String.raw`^(?:child::|attribute::|@)?${NAME}:\*$`
|
|
2420
|
+
);
|
|
2421
|
+
var NODE_TEST_PATTERN = /^(?:child::|attribute::|@)?(?:\*|node\(\)|text\(\)|comment\(\)|processing-instruction\(\))$/;
|
|
2422
|
+
var PI_LITERAL_PATTERN = /^(?:child::)?processing-instruction\(\s*(?:"[^"]*"|'[^']*')\s*\)$/;
|
|
2423
|
+
function calculatePriority(pattern) {
|
|
2424
|
+
if (!pattern) return 0.5;
|
|
2425
|
+
if (NODE_TEST_PATTERN.test(pattern)) return -0.5;
|
|
2426
|
+
if (PREFIX_WILDCARD_PATTERN.test(pattern)) return -0.25;
|
|
2427
|
+
if (QNAME_PATTERN.test(pattern) || PI_LITERAL_PATTERN.test(pattern)) return 0;
|
|
2428
|
+
return 0.5;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/xslt/serializer/constants.js
|
|
2432
|
+
var NODE_TYPE = {
|
|
2433
|
+
ELEMENT: 1,
|
|
2434
|
+
TEXT: 3,
|
|
2435
|
+
CDATA_SECTION: 4,
|
|
2436
|
+
PROCESSING_INSTRUCTION: 7,
|
|
2437
|
+
COMMENT: 8,
|
|
2438
|
+
DOCUMENT: 9,
|
|
2439
|
+
DOCUMENT_FRAGMENT: 11
|
|
2440
|
+
};
|
|
2441
|
+
var XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
|
|
2442
|
+
var XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
|
|
2443
|
+
var TEXT_MODE = {
|
|
2444
|
+
ESCAPE: "escape",
|
|
2445
|
+
CDATA: "cdata",
|
|
2446
|
+
RAW: "raw"
|
|
2447
|
+
};
|
|
2448
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2449
|
+
"area",
|
|
2450
|
+
"base",
|
|
2451
|
+
"br",
|
|
2452
|
+
"col",
|
|
2453
|
+
"embed",
|
|
2454
|
+
"hr",
|
|
2455
|
+
"img",
|
|
2456
|
+
"input",
|
|
2457
|
+
"link",
|
|
2458
|
+
"meta",
|
|
2459
|
+
"param",
|
|
2460
|
+
"source",
|
|
2461
|
+
"track",
|
|
2462
|
+
"wbr"
|
|
2463
|
+
]);
|
|
2464
|
+
var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
|
|
2465
|
+
var PRESERVE_SPACE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2466
|
+
"pre",
|
|
2467
|
+
"script",
|
|
2468
|
+
"style",
|
|
2469
|
+
"textarea"
|
|
2470
|
+
]);
|
|
2471
|
+
var INDENT_UNIT = " ";
|
|
2472
|
+
|
|
2473
|
+
// src/xslt/serializer/settings.js
|
|
2474
|
+
function isYes(value) {
|
|
2475
|
+
return value === true || String(value).toLowerCase() === "yes";
|
|
2476
|
+
}
|
|
2477
|
+
function toNameSet(value) {
|
|
2478
|
+
if (Array.isArray(value)) {
|
|
2479
|
+
return new Set(value);
|
|
2480
|
+
}
|
|
2481
|
+
if (typeof value === "string") {
|
|
2482
|
+
return new Set(value.split(/\s+/).filter(Boolean));
|
|
2483
|
+
}
|
|
2484
|
+
return /* @__PURE__ */ new Set();
|
|
2485
|
+
}
|
|
2486
|
+
function findRootElement(node) {
|
|
2487
|
+
if (!node) {
|
|
2488
|
+
return null;
|
|
2489
|
+
}
|
|
2490
|
+
if (node.nodeType === NODE_TYPE.ELEMENT) {
|
|
2491
|
+
return node;
|
|
2492
|
+
}
|
|
2493
|
+
for (const child of node.childNodes || []) {
|
|
2494
|
+
if (child.nodeType === NODE_TYPE.ELEMENT) {
|
|
2495
|
+
return child;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return null;
|
|
2499
|
+
}
|
|
2500
|
+
function detectOutputMethod(node) {
|
|
2501
|
+
const root = findRootElement(node);
|
|
2502
|
+
const isHtmlRoot = root && !root.namespaceURI && root.localName.toLowerCase() === "html";
|
|
2503
|
+
return isHtmlRoot ? "html" : "xml";
|
|
2504
|
+
}
|
|
2505
|
+
function resolveOutputSettings(outputSettings, node) {
|
|
2506
|
+
const raw = outputSettings || {};
|
|
2507
|
+
const declared = typeof raw.method === "string" ? raw.method.trim() : "";
|
|
2508
|
+
const method = declared && declared !== "auto" ? declared.toLowerCase() : detectOutputMethod(node);
|
|
2509
|
+
return {
|
|
2510
|
+
method,
|
|
2511
|
+
version: raw.version || "1.0",
|
|
2512
|
+
encoding: raw.encoding || "UTF-8",
|
|
2513
|
+
standalone: raw.standalone || null,
|
|
2514
|
+
indent: isYes(raw.indent),
|
|
2515
|
+
omitXmlDeclaration: isYes(raw.omitXmlDeclaration),
|
|
2516
|
+
doctypePublic: raw.doctypePublic || null,
|
|
2517
|
+
doctypeSystem: raw.doctypeSystem || null,
|
|
2518
|
+
mediaType: raw.mediaType || null,
|
|
2519
|
+
cdataSectionElements: toNameSet(raw.cdataSectionElements)
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// src/xslt/serializer/escape.js
|
|
2524
|
+
var XML_TEXT_ESCAPES = { "&": "&", "<": "<" };
|
|
2525
|
+
var HTML_TEXT_ESCAPES = { "&": "&", "<": "<", ">": ">" };
|
|
2526
|
+
var XML_ATTRIBUTE_ESCAPES = {
|
|
2527
|
+
"&": "&",
|
|
2528
|
+
"<": "<",
|
|
2529
|
+
">": ">",
|
|
2530
|
+
'"': """,
|
|
2531
|
+
" ": "	",
|
|
2532
|
+
"\n": " ",
|
|
2533
|
+
"\r": " "
|
|
2534
|
+
};
|
|
2535
|
+
var HTML_ATTRIBUTE_ESCAPES = {
|
|
2536
|
+
"&": "&",
|
|
2537
|
+
"<": "<",
|
|
2538
|
+
">": ">",
|
|
2539
|
+
'"': """
|
|
2540
|
+
};
|
|
2541
|
+
function escapeWith(value, pattern, escapes) {
|
|
2542
|
+
return String(value).replaceAll(pattern, (character) => escapes[character]);
|
|
2543
|
+
}
|
|
2544
|
+
function escapeXmlText(value) {
|
|
2545
|
+
return escapeWith(value, /[&<]/g, XML_TEXT_ESCAPES).replaceAll(
|
|
2546
|
+
"]]>",
|
|
2547
|
+
"]]>"
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
function escapeXmlAttribute(value) {
|
|
2551
|
+
return escapeWith(value, /[&<>"\t\n\r]/g, XML_ATTRIBUTE_ESCAPES);
|
|
2552
|
+
}
|
|
2553
|
+
function escapeHtmlText(value) {
|
|
2554
|
+
return escapeWith(value, /[&<>]/g, HTML_TEXT_ESCAPES);
|
|
2555
|
+
}
|
|
2556
|
+
function escapeHtmlAttribute(value) {
|
|
2557
|
+
return escapeWith(value, /[&<>"]/g, HTML_ATTRIBUTE_ESCAPES);
|
|
2558
|
+
}
|
|
2559
|
+
function wrapCdata(value) {
|
|
2560
|
+
return `<![CDATA[${String(value).replaceAll("]]>", "]]]]><![CDATA[>")}]]>`;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// src/xslt/serializer/namespaces.js
|
|
2564
|
+
function createNamespaceScope() {
|
|
2565
|
+
return /* @__PURE__ */ new Map([
|
|
2566
|
+
["", ""],
|
|
2567
|
+
["xml", XML_NAMESPACE]
|
|
2568
|
+
]);
|
|
2569
|
+
}
|
|
2570
|
+
function collectNamespaceDeclarations(element, scope) {
|
|
2571
|
+
const declarations = [];
|
|
2572
|
+
let next = scope;
|
|
2573
|
+
const declare = (prefix, uri) => {
|
|
2574
|
+
if (next.get(prefix) === uri) {
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (next === scope) {
|
|
2578
|
+
next = new Map(scope);
|
|
2579
|
+
}
|
|
2580
|
+
next.set(prefix, uri);
|
|
2581
|
+
declarations.push({ prefix, uri });
|
|
2582
|
+
};
|
|
2583
|
+
declare(element.prefix || "", element.namespaceURI || "");
|
|
2584
|
+
const attributes = Array.from(element.attributes || []);
|
|
2585
|
+
for (const attribute of attributes) {
|
|
2586
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE && attribute.prefix) {
|
|
2587
|
+
declare(attribute.prefix, attribute.namespaceURI || "");
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
for (const attribute of attributes) {
|
|
2591
|
+
if (attribute.namespaceURI === XMLNS_NAMESPACE) {
|
|
2592
|
+
declare(attribute.prefix ? attribute.localName : "", attribute.value);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
return { declarations, scope: next };
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// src/xslt/serializer/indent.js
|
|
2599
|
+
function isWhitespaceOnlyText(node) {
|
|
2600
|
+
return !/\S/.test(node.nodeValue || "");
|
|
2601
|
+
}
|
|
2602
|
+
function getIndentableChildren(element) {
|
|
2603
|
+
const indentable = [];
|
|
2604
|
+
for (const child of element.childNodes) {
|
|
2605
|
+
if (child.nodeType === NODE_TYPE.TEXT) {
|
|
2606
|
+
if (isWhitespaceOnlyText(child)) {
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
return null;
|
|
2610
|
+
}
|
|
2611
|
+
if (child.nodeType !== NODE_TYPE.ELEMENT && child.nodeType !== NODE_TYPE.COMMENT && child.nodeType !== NODE_TYPE.PROCESSING_INSTRUCTION) {
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
indentable.push(child);
|
|
2615
|
+
}
|
|
2616
|
+
return indentable.length > 0 ? indentable : null;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
// src/xslt/serializer/rawText.js
|
|
2620
|
+
var rawTextNodes = /* @__PURE__ */ new WeakSet();
|
|
2621
|
+
function markRawText(node) {
|
|
2622
|
+
if (node) {
|
|
2623
|
+
rawTextNodes.add(node);
|
|
2624
|
+
}
|
|
2625
|
+
return node;
|
|
2626
|
+
}
|
|
2627
|
+
function isRawText(node) {
|
|
2628
|
+
if (!node) {
|
|
2629
|
+
return false;
|
|
2630
|
+
}
|
|
2631
|
+
return rawTextNodes.has(node) || node._disableOutputEscaping === true;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// src/xslt/serializer/baseWriter.js
|
|
2635
|
+
var BaseWriter = class {
|
|
2636
|
+
/**
|
|
2637
|
+
* @param {object} settings - Normalized output settings
|
|
2638
|
+
* @param {{xhtml?: boolean}} [options] - Dialect options
|
|
2639
|
+
*/
|
|
2640
|
+
constructor(settings, options = {}) {
|
|
2641
|
+
this.settings = settings;
|
|
2642
|
+
this.xhtml = options.xhtml === true;
|
|
2643
|
+
this.parts = [];
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Serialize a result tree node.
|
|
2647
|
+
*
|
|
2648
|
+
* @param {Node} node - Document, fragment or element to serialize
|
|
2649
|
+
* @returns {string} Serialized output
|
|
2650
|
+
*/
|
|
2651
|
+
serialize(node) {
|
|
2652
|
+
this.parts = [];
|
|
2653
|
+
this.writeProlog(node);
|
|
2654
|
+
this.writeNode(node, createNamespaceScope(), 0, TEXT_MODE.ESCAPE);
|
|
2655
|
+
return this.parts.join("");
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Write the XML declaration and the document type declaration.
|
|
2659
|
+
*
|
|
2660
|
+
* @param {Node} node - Result tree root
|
|
2661
|
+
* @returns {void}
|
|
2662
|
+
*/
|
|
2663
|
+
writeProlog(node) {
|
|
2664
|
+
if (this.emitsXmlDeclaration) {
|
|
2665
|
+
const { version, encoding, standalone } = this.settings;
|
|
2666
|
+
const standalonePart = standalone ? ` standalone="${standalone}"` : "";
|
|
2667
|
+
this.parts.push(
|
|
2668
|
+
`<?xml version="${version}" encoding="${encoding}"${standalonePart}?>
|
|
2669
|
+
`
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
const doctype = this.doctypeMarkup(findRootElement(node));
|
|
2673
|
+
if (doctype) {
|
|
2674
|
+
this.parts.push(`${doctype}
|
|
2675
|
+
`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
/**
|
|
2679
|
+
* Write any result tree node.
|
|
2680
|
+
*
|
|
2681
|
+
* @param {Node} node - Node to write
|
|
2682
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2683
|
+
* @param {number} depth - Current indentation depth
|
|
2684
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2685
|
+
* @returns {void}
|
|
2686
|
+
*/
|
|
2687
|
+
writeNode(node, scope, depth, textMode) {
|
|
2688
|
+
switch (node.nodeType) {
|
|
2689
|
+
case NODE_TYPE.ELEMENT:
|
|
2690
|
+
this.writeElement(node, scope, depth);
|
|
2691
|
+
break;
|
|
2692
|
+
case NODE_TYPE.TEXT:
|
|
2693
|
+
case NODE_TYPE.CDATA_SECTION:
|
|
2694
|
+
this.writeText(node, textMode);
|
|
2695
|
+
break;
|
|
2696
|
+
case NODE_TYPE.COMMENT:
|
|
2697
|
+
this.parts.push(`<!--${node.nodeValue}-->`);
|
|
2698
|
+
break;
|
|
2699
|
+
case NODE_TYPE.PROCESSING_INSTRUCTION:
|
|
2700
|
+
this.writeProcessingInstruction(node);
|
|
2701
|
+
break;
|
|
2702
|
+
case NODE_TYPE.DOCUMENT:
|
|
2703
|
+
case NODE_TYPE.DOCUMENT_FRAGMENT:
|
|
2704
|
+
this.writeChildNodes(node, scope, depth, textMode);
|
|
2705
|
+
break;
|
|
2706
|
+
default:
|
|
2707
|
+
break;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Write every child of a node without adding whitespace.
|
|
2712
|
+
*
|
|
2713
|
+
* @param {Node} node - Parent node
|
|
2714
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2715
|
+
* @param {number} depth - Current indentation depth
|
|
2716
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2717
|
+
* @returns {void}
|
|
2718
|
+
*/
|
|
2719
|
+
writeChildNodes(node, scope, depth, textMode) {
|
|
2720
|
+
for (const child of node.childNodes) {
|
|
2721
|
+
this.writeNode(child, scope, depth, textMode);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Write an element with its namespaces, attributes and children.
|
|
2726
|
+
*
|
|
2727
|
+
* @param {Element} element - Element to write
|
|
2728
|
+
* @param {Map<string, string>} scope - Namespace scope inherited from the parent
|
|
2729
|
+
* @param {number} depth - Current indentation depth
|
|
2730
|
+
* @returns {void}
|
|
2731
|
+
*/
|
|
2732
|
+
writeElement(element, scope, depth) {
|
|
2733
|
+
const namespaces = this.emitsNamespaces ? collectNamespaceDeclarations(element, scope) : { declarations: [], scope };
|
|
2734
|
+
const name = element.nodeName;
|
|
2735
|
+
this.parts.push(
|
|
2736
|
+
`<${name}${this.namespaceMarkup(namespaces.declarations)}` + this.attributesMarkup(element)
|
|
2737
|
+
);
|
|
2738
|
+
if (!element.firstChild) {
|
|
2739
|
+
this.parts.push(this.emptyElementMarkup(element, name));
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
this.parts.push(">");
|
|
2743
|
+
this.writeElementChildren(element, namespaces.scope, depth);
|
|
2744
|
+
this.parts.push(`</${name}>`);
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Write the children of an element, indenting element-only content.
|
|
2748
|
+
*
|
|
2749
|
+
* @param {Element} element - Parent element
|
|
2750
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2751
|
+
* @param {number} depth - Depth of the parent element
|
|
2752
|
+
* @returns {void}
|
|
2753
|
+
*/
|
|
2754
|
+
writeElementChildren(element, scope, depth) {
|
|
2755
|
+
const textMode = this.childTextMode(element);
|
|
2756
|
+
const indentable = this.indentableChildren(element, textMode);
|
|
2757
|
+
if (!indentable) {
|
|
2758
|
+
this.writeChildNodes(element, scope, depth, textMode);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
const childIndent = `
|
|
2762
|
+
${INDENT_UNIT.repeat(depth + 1)}`;
|
|
2763
|
+
for (const child of indentable) {
|
|
2764
|
+
this.parts.push(childIndent);
|
|
2765
|
+
this.writeNode(child, scope, depth + 1, textMode);
|
|
2766
|
+
}
|
|
2767
|
+
this.parts.push(`
|
|
2768
|
+
${INDENT_UNIT.repeat(depth)}`);
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Determine the children to indent inside an element.
|
|
2772
|
+
*
|
|
2773
|
+
* @param {Element} element - Parent element
|
|
2774
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2775
|
+
* @returns {Node[]|null} Children to indent, or null when indenting is off
|
|
2776
|
+
*/
|
|
2777
|
+
indentableChildren(element, textMode) {
|
|
2778
|
+
if (!this.settings.indent || textMode !== TEXT_MODE.ESCAPE) {
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
if (!this.allowsIndentInside(element)) {
|
|
2782
|
+
return null;
|
|
2783
|
+
}
|
|
2784
|
+
return getIndentableChildren(element);
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Build the namespace declaration markup of an element.
|
|
2788
|
+
*
|
|
2789
|
+
* @param {Array<{prefix: string, uri: string}>} declarations - Declarations
|
|
2790
|
+
* @returns {string} Attribute markup, starting with a space when non-empty
|
|
2791
|
+
*/
|
|
2792
|
+
namespaceMarkup(declarations) {
|
|
2793
|
+
return declarations.map(({ prefix, uri }) => {
|
|
2794
|
+
const name = prefix ? `xmlns:${prefix}` : "xmlns";
|
|
2795
|
+
return ` ${name}="${this.escapeAttribute(uri)}"`;
|
|
2796
|
+
}).join("");
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Build the attribute markup of an element, skipping namespace declarations.
|
|
2800
|
+
*
|
|
2801
|
+
* @param {Element} element - Element being written
|
|
2802
|
+
* @returns {string} Attribute markup, starting with a space when non-empty
|
|
2803
|
+
*/
|
|
2804
|
+
attributesMarkup(element) {
|
|
2805
|
+
let markup = "";
|
|
2806
|
+
for (const attribute of Array.from(element.attributes || [])) {
|
|
2807
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE) {
|
|
2808
|
+
markup += this.attributeMarkup(attribute);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
return markup;
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Build the markup of a single attribute.
|
|
2815
|
+
*
|
|
2816
|
+
* @param {Attr} attribute - Attribute to write
|
|
2817
|
+
* @returns {string} Attribute markup, starting with a space
|
|
2818
|
+
*/
|
|
2819
|
+
attributeMarkup(attribute) {
|
|
2820
|
+
return ` ${attribute.name}="${this.escapeAttribute(attribute.value)}"`;
|
|
2821
|
+
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Write a character data node.
|
|
2824
|
+
*
|
|
2825
|
+
* Nodes produced with `disable-output-escaping="yes"` are written verbatim.
|
|
2826
|
+
*
|
|
2827
|
+
* @param {Node} node - Text or CDATA section node
|
|
2828
|
+
* @param {string} textMode - {@link TEXT_MODE} requested by the parent
|
|
2829
|
+
* @returns {void}
|
|
2830
|
+
*/
|
|
2831
|
+
writeText(node, textMode) {
|
|
2832
|
+
const value = node.nodeValue || "";
|
|
2833
|
+
if (isRawText(node)) {
|
|
2834
|
+
this.parts.push(value);
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
const mode = this.resolveTextMode(node, textMode);
|
|
2838
|
+
if (mode === TEXT_MODE.CDATA) {
|
|
2839
|
+
this.parts.push(wrapCdata(value));
|
|
2840
|
+
} else if (mode === TEXT_MODE.RAW) {
|
|
2841
|
+
this.parts.push(value);
|
|
2842
|
+
} else {
|
|
2843
|
+
this.parts.push(this.escapeText(value));
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Resolve the effective text mode of a character data node.
|
|
2848
|
+
*
|
|
2849
|
+
* @param {Node} node - Text or CDATA section node
|
|
2850
|
+
* @param {string} textMode - {@link TEXT_MODE} requested by the parent
|
|
2851
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2852
|
+
*/
|
|
2853
|
+
resolveTextMode(node, textMode) {
|
|
2854
|
+
if (textMode !== TEXT_MODE.ESCAPE) {
|
|
2855
|
+
return textMode;
|
|
2856
|
+
}
|
|
2857
|
+
return node.nodeType === NODE_TYPE.CDATA_SECTION ? this.cdataNodeMode : TEXT_MODE.ESCAPE;
|
|
2858
|
+
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Write a processing instruction node.
|
|
2861
|
+
*
|
|
2862
|
+
* @param {ProcessingInstruction} node - Node to write
|
|
2863
|
+
* @returns {void}
|
|
2864
|
+
*/
|
|
2865
|
+
writeProcessingInstruction(node) {
|
|
2866
|
+
const data = node.nodeValue || "";
|
|
2867
|
+
const separator = data ? " " : "";
|
|
2868
|
+
this.parts.push(`<?${node.target}${separator}${data}${this.piTerminator}`);
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2871
|
+
|
|
2872
|
+
// src/xslt/serializer/xmlSerializer.js
|
|
2873
|
+
var XmlWriter = class extends BaseWriter {
|
|
2874
|
+
/**
|
|
2875
|
+
* Whether an XML declaration has to be written.
|
|
2876
|
+
* @returns {boolean} True when the declaration is not omitted
|
|
2877
|
+
*/
|
|
2878
|
+
get emitsXmlDeclaration() {
|
|
2879
|
+
return !this.settings.omitXmlDeclaration;
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Whether namespace declarations have to be written.
|
|
2883
|
+
* @returns {boolean} Always true for XML output
|
|
2884
|
+
*/
|
|
2885
|
+
get emitsNamespaces() {
|
|
2886
|
+
return true;
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Terminator of a processing instruction.
|
|
2890
|
+
* @returns {string} The XML processing instruction terminator
|
|
2891
|
+
*/
|
|
2892
|
+
get piTerminator() {
|
|
2893
|
+
return "?>";
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* How a source CDATA section node has to be written.
|
|
2897
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2898
|
+
*/
|
|
2899
|
+
get cdataNodeMode() {
|
|
2900
|
+
return TEXT_MODE.CDATA;
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Build the document type declaration for the xml output method.
|
|
2904
|
+
*
|
|
2905
|
+
* @param {Element|null} rootElement - Result document element
|
|
2906
|
+
* @returns {string} Doctype markup, or an empty string when not applicable
|
|
2907
|
+
*/
|
|
2908
|
+
doctypeMarkup(rootElement) {
|
|
2909
|
+
const { doctypePublic, doctypeSystem } = this.settings;
|
|
2910
|
+
if (!rootElement || !doctypeSystem) {
|
|
2911
|
+
return "";
|
|
2912
|
+
}
|
|
2913
|
+
const name = rootElement.nodeName;
|
|
2914
|
+
return doctypePublic ? `<!DOCTYPE ${name} PUBLIC "${doctypePublic}" "${doctypeSystem}">` : `<!DOCTYPE ${name} SYSTEM "${doctypeSystem}">`;
|
|
2915
|
+
}
|
|
2916
|
+
/**
|
|
2917
|
+
* Determine how the character data children of an element are written.
|
|
2918
|
+
*
|
|
2919
|
+
* @param {Element} element - Parent element
|
|
2920
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2921
|
+
*/
|
|
2922
|
+
childTextMode(element) {
|
|
2923
|
+
const names = this.settings.cdataSectionElements;
|
|
2924
|
+
return names.has(element.nodeName) || names.has(element.localName) ? TEXT_MODE.CDATA : TEXT_MODE.ESCAPE;
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Whether the content of an element may be re-indented.
|
|
2928
|
+
*
|
|
2929
|
+
* @param {Element} _element - Element being inspected
|
|
2930
|
+
* @returns {boolean} Always true for XML output
|
|
2931
|
+
*/
|
|
2932
|
+
allowsIndentInside(_element) {
|
|
2933
|
+
return true;
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Build the markup closing an element that has no children.
|
|
2937
|
+
*
|
|
2938
|
+
* @param {Element} element - Empty element
|
|
2939
|
+
* @param {string} _name - Element name as written
|
|
2940
|
+
* @returns {string} Markup terminating the start tag
|
|
2941
|
+
*/
|
|
2942
|
+
emptyElementMarkup(element, _name) {
|
|
2943
|
+
return this.xhtml && this.isVoidElement(element) ? " />" : "/>";
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Test whether an element is an HTML void element.
|
|
2947
|
+
*
|
|
2948
|
+
* @param {Element} element - Element to test
|
|
2949
|
+
* @returns {boolean} True for void elements such as `br`
|
|
2950
|
+
*/
|
|
2951
|
+
isVoidElement(element) {
|
|
2952
|
+
return VOID_ELEMENTS.has(String(element.localName).toLowerCase());
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Escape character data.
|
|
2956
|
+
*
|
|
2957
|
+
* @param {string} value - Text content
|
|
2958
|
+
* @returns {string} Escaped text
|
|
2959
|
+
*/
|
|
2960
|
+
escapeText(value) {
|
|
2961
|
+
return escapeXmlText(value);
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Escape an attribute value.
|
|
2965
|
+
*
|
|
2966
|
+
* @param {string} value - Attribute value
|
|
2967
|
+
* @returns {string} Escaped value
|
|
2968
|
+
*/
|
|
2969
|
+
escapeAttribute(value) {
|
|
2970
|
+
return escapeXmlAttribute(value);
|
|
2971
|
+
}
|
|
2972
|
+
};
|
|
2973
|
+
|
|
2974
|
+
// src/xslt/serializer/htmlSerializer.js
|
|
2975
|
+
var HtmlWriter = class extends XmlWriter {
|
|
2976
|
+
/**
|
|
2977
|
+
* The html output method never writes an XML declaration.
|
|
2978
|
+
* @returns {boolean} Always false
|
|
2979
|
+
*/
|
|
2980
|
+
get emitsXmlDeclaration() {
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* The html output method never writes namespace declarations.
|
|
2985
|
+
* @returns {boolean} Always false
|
|
2986
|
+
*/
|
|
2987
|
+
get emitsNamespaces() {
|
|
2988
|
+
return false;
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* HTML processing instructions are terminated by `>` alone.
|
|
2992
|
+
* @returns {string} The HTML processing instruction terminator
|
|
2993
|
+
*/
|
|
2994
|
+
get piTerminator() {
|
|
2995
|
+
return ">";
|
|
2996
|
+
}
|
|
2997
|
+
/**
|
|
2998
|
+
* HTML has no CDATA sections, so such nodes are escaped as text.
|
|
2999
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
3000
|
+
*/
|
|
3001
|
+
get cdataNodeMode() {
|
|
3002
|
+
return TEXT_MODE.ESCAPE;
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Build the document type declaration for the html output method.
|
|
3006
|
+
*
|
|
3007
|
+
* @param {Element|null} rootElement - Result document element
|
|
3008
|
+
* @returns {string} Doctype markup, or an empty string when not applicable
|
|
3009
|
+
*/
|
|
3010
|
+
doctypeMarkup(rootElement) {
|
|
3011
|
+
const { doctypePublic, doctypeSystem } = this.settings;
|
|
3012
|
+
if (!doctypePublic && !doctypeSystem) {
|
|
3013
|
+
return "";
|
|
3014
|
+
}
|
|
3015
|
+
const name = rootElement ? rootElement.nodeName : "html";
|
|
3016
|
+
if (doctypePublic && doctypeSystem) {
|
|
3017
|
+
return `<!DOCTYPE ${name} PUBLIC "${doctypePublic}" "${doctypeSystem}">`;
|
|
3018
|
+
}
|
|
3019
|
+
if (doctypePublic) {
|
|
3020
|
+
return `<!DOCTYPE ${name} PUBLIC "${doctypePublic}">`;
|
|
3021
|
+
}
|
|
3022
|
+
return `<!DOCTYPE ${name} SYSTEM "${doctypeSystem}">`;
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Script and style content is written verbatim.
|
|
3026
|
+
*
|
|
3027
|
+
* @param {Element} element - Parent element
|
|
3028
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
3029
|
+
*/
|
|
3030
|
+
childTextMode(element) {
|
|
3031
|
+
return RAW_TEXT_ELEMENTS.has(String(element.localName).toLowerCase()) ? TEXT_MODE.RAW : TEXT_MODE.ESCAPE;
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Content of `pre`, `script`, `style` and `textarea` is never re-indented.
|
|
3035
|
+
*
|
|
3036
|
+
* @param {Element} element - Element being inspected
|
|
3037
|
+
* @returns {boolean} True when the content may be indented
|
|
3038
|
+
*/
|
|
3039
|
+
allowsIndentInside(element) {
|
|
3040
|
+
return !PRESERVE_SPACE_ELEMENTS.has(
|
|
3041
|
+
String(element.localName).toLowerCase()
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Void elements have no end tag; every other element gets one.
|
|
3046
|
+
*
|
|
3047
|
+
* @param {Element} element - Empty element
|
|
3048
|
+
* @param {string} name - Element name as written
|
|
3049
|
+
* @returns {string} Markup terminating the start tag
|
|
3050
|
+
*/
|
|
3051
|
+
emptyElementMarkup(element, name) {
|
|
3052
|
+
return this.isVoidElement(element) ? ">" : `></${name}>`;
|
|
3053
|
+
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Boolean attributes are minimized to their name alone.
|
|
3056
|
+
*
|
|
3057
|
+
* @param {Attr} attribute - Attribute to write
|
|
3058
|
+
* @returns {string} Attribute markup, starting with a space
|
|
3059
|
+
*/
|
|
3060
|
+
attributeMarkup(attribute) {
|
|
3061
|
+
const { name, value } = attribute;
|
|
3062
|
+
if (String(value).toLowerCase() === name.toLowerCase()) {
|
|
3063
|
+
return ` ${name}`;
|
|
3064
|
+
}
|
|
3065
|
+
return ` ${name}="${this.escapeAttribute(value)}"`;
|
|
3066
|
+
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Escape character data for HTML.
|
|
3069
|
+
*
|
|
3070
|
+
* @param {string} value - Text content
|
|
3071
|
+
* @returns {string} Escaped text
|
|
3072
|
+
*/
|
|
3073
|
+
escapeText(value) {
|
|
3074
|
+
return escapeHtmlText(value);
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Escape an attribute value for HTML.
|
|
3078
|
+
*
|
|
3079
|
+
* @param {string} value - Attribute value
|
|
3080
|
+
* @returns {string} Escaped value
|
|
3081
|
+
*/
|
|
3082
|
+
escapeAttribute(value) {
|
|
3083
|
+
return escapeHtmlAttribute(value);
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
|
|
3087
|
+
// src/xslt/serializer/textSerializer.js
|
|
3088
|
+
function serializeText(node) {
|
|
3089
|
+
if (node.nodeType === NODE_TYPE.TEXT || node.nodeType === NODE_TYPE.CDATA_SECTION) {
|
|
3090
|
+
return node.nodeValue || "";
|
|
3091
|
+
}
|
|
3092
|
+
let text = "";
|
|
3093
|
+
for (const child of node.childNodes || []) {
|
|
3094
|
+
text += serializeText(child);
|
|
3095
|
+
}
|
|
3096
|
+
return text;
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
// src/xslt/serializer.js
|
|
3100
|
+
function serializeResult(node, outputSettings = {}) {
|
|
3101
|
+
if (!node) {
|
|
3102
|
+
return "";
|
|
3103
|
+
}
|
|
3104
|
+
const settings = resolveOutputSettings(outputSettings, node);
|
|
3105
|
+
if (settings.method === "text") {
|
|
3106
|
+
return serializeText(node);
|
|
3107
|
+
}
|
|
3108
|
+
if (settings.method === "html") {
|
|
3109
|
+
return new HtmlWriter(settings).serialize(node);
|
|
3110
|
+
}
|
|
3111
|
+
return new XmlWriter(settings, {
|
|
3112
|
+
xhtml: settings.method === "xhtml"
|
|
3113
|
+
}).serialize(node);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
1602
3116
|
// src/xslt/engine.js
|
|
1603
|
-
var XSLT_NS =
|
|
3117
|
+
var XSLT_NS = XSLT_NAMESPACE;
|
|
1604
3118
|
var XsltContext = class _XsltContext {
|
|
1605
3119
|
constructor(options = {}) {
|
|
1606
3120
|
this.currentNode = options.currentNode;
|
|
@@ -1616,6 +3130,8 @@ var XsltProcessorLib = (() => {
|
|
|
1616
3130
|
this.decimalFormats = options.decimalFormats || {};
|
|
1617
3131
|
this.outputMethod = options.outputMethod || "xml";
|
|
1618
3132
|
this.xpathEvaluator = options.xpathEvaluator || new XPathEvaluator();
|
|
3133
|
+
this.currentTemplate = options.currentTemplate || null;
|
|
3134
|
+
this.currentMode = options.currentMode ?? null;
|
|
1619
3135
|
}
|
|
1620
3136
|
clone(overrides = {}) {
|
|
1621
3137
|
return new _XsltContext({
|
|
@@ -1631,7 +3147,9 @@ var XsltProcessorLib = (() => {
|
|
|
1631
3147
|
keys: this.keys,
|
|
1632
3148
|
decimalFormats: this.decimalFormats,
|
|
1633
3149
|
outputMethod: this.outputMethod,
|
|
1634
|
-
xpathEvaluator: this.xpathEvaluator
|
|
3150
|
+
xpathEvaluator: this.xpathEvaluator,
|
|
3151
|
+
currentTemplate: overrides.currentTemplate ?? this.currentTemplate,
|
|
3152
|
+
currentMode: overrides.currentMode ?? this.currentMode
|
|
1635
3153
|
});
|
|
1636
3154
|
}
|
|
1637
3155
|
getVariable(name) {
|
|
@@ -1656,7 +3174,9 @@ var XsltProcessorLib = (() => {
|
|
|
1656
3174
|
this.globalParameters = {};
|
|
1657
3175
|
this.outputSettings = {
|
|
1658
3176
|
method: "xml",
|
|
3177
|
+
version: "1.0",
|
|
1659
3178
|
encoding: "UTF-8",
|
|
3179
|
+
standalone: null,
|
|
1660
3180
|
indent: "no",
|
|
1661
3181
|
omitXmlDeclaration: "no",
|
|
1662
3182
|
doctypePublic: null,
|
|
@@ -1668,13 +3188,105 @@ var XsltProcessorLib = (() => {
|
|
|
1668
3188
|
this.decimalFormats = {};
|
|
1669
3189
|
this.stylesheetDoc = null;
|
|
1670
3190
|
this.attributeSets = {};
|
|
1671
|
-
this.namespaceAliases =
|
|
3191
|
+
this.namespaceAliases = new NamespaceAliasMap();
|
|
1672
3192
|
this.stripSpace = [];
|
|
1673
3193
|
this.preserveSpace = [];
|
|
1674
3194
|
this.stylesheetLoader = options.stylesheetLoader || null;
|
|
1675
3195
|
this.currentImportPrecedence = 0;
|
|
1676
3196
|
this.processedStylesheets = /* @__PURE__ */ new Set();
|
|
1677
3197
|
this.baseUri = options.baseUri || "";
|
|
3198
|
+
this.documentLoader = options.documentLoader || null;
|
|
3199
|
+
this.loadedDocuments = /* @__PURE__ */ new Map();
|
|
3200
|
+
this.generatedIds = /* @__PURE__ */ new WeakMap();
|
|
3201
|
+
this.generatedIdCount = 0;
|
|
3202
|
+
this.rootContext = null;
|
|
3203
|
+
this.keyRegistry = new KeyIndexRegistry({
|
|
3204
|
+
keys: this.keys,
|
|
3205
|
+
matchesPattern: (node, pattern) => this.matchesPattern(node, pattern, this.rootContext),
|
|
3206
|
+
evaluateUse: (node, expression) => this.evaluateKeyValues(node, expression)
|
|
3207
|
+
});
|
|
3208
|
+
this.xpathEvaluator.registerFunctions(createXsltFunctions(this));
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Set the loader used by the XSLT `document()` function.
|
|
3212
|
+
*
|
|
3213
|
+
* The loader is synchronous and must return a `Document`, an XML string or
|
|
3214
|
+
* null. Returning null (or configuring no loader at all) makes `document()`
|
|
3215
|
+
* evaluate to an empty node-set instead of failing the transformation.
|
|
3216
|
+
*
|
|
3217
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader - The loader, or null to remove it
|
|
3218
|
+
* @returns {XsltEngine} This engine, to allow chaining
|
|
3219
|
+
*
|
|
3220
|
+
* @example
|
|
3221
|
+
* engine.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
3222
|
+
*/
|
|
3223
|
+
setDocumentLoader(loader) {
|
|
3224
|
+
this.documentLoader = loader ?? null;
|
|
3225
|
+
this.loadedDocuments.clear();
|
|
3226
|
+
return this;
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Load an external document for the `document()` function.
|
|
3230
|
+
*
|
|
3231
|
+
* Results are cached per resolved URI for the life of the engine, so the same
|
|
3232
|
+
* URI always yields the identical node-set.
|
|
3233
|
+
*
|
|
3234
|
+
* @param {string} uri - The requested URI, fragment identifiers are ignored
|
|
3235
|
+
* @param {string} [baseUri] - Base URI used to resolve relative references
|
|
3236
|
+
* @returns {Document|null} The loaded document, or null when unavailable
|
|
3237
|
+
*
|
|
3238
|
+
* @example
|
|
3239
|
+
* engine.loadDocument('data.xml', '/styles/main.xsl');
|
|
3240
|
+
*/
|
|
3241
|
+
loadDocument(uri, baseUri) {
|
|
3242
|
+
const target = stripFragment(uri);
|
|
3243
|
+
if (target === "") return this.stylesheetDoc;
|
|
3244
|
+
if (!this.documentLoader) return null;
|
|
3245
|
+
const resolved = resolveUri(target, baseUri);
|
|
3246
|
+
if (this.loadedDocuments.has(resolved)) {
|
|
3247
|
+
return this.loadedDocuments.get(resolved);
|
|
3248
|
+
}
|
|
3249
|
+
const loaded = this.documentLoader(resolved, baseUri);
|
|
3250
|
+
const doc = typeof loaded === "string" ? this.parseXmlString(loaded) : loaded || null;
|
|
3251
|
+
this.loadedDocuments.set(resolved, doc);
|
|
3252
|
+
return doc;
|
|
3253
|
+
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Return the stable identifier of a node for `generate-id()`.
|
|
3256
|
+
*
|
|
3257
|
+
* @param {Node} node - The node to identify
|
|
3258
|
+
* @returns {string} An identifier starting with a letter
|
|
3259
|
+
*
|
|
3260
|
+
* @example
|
|
3261
|
+
* engine.generateId(element); // 'N1'
|
|
3262
|
+
*/
|
|
3263
|
+
generateId(node) {
|
|
3264
|
+
let id = this.generatedIds.get(node);
|
|
3265
|
+
if (!id) {
|
|
3266
|
+
this.generatedIdCount++;
|
|
3267
|
+
id = `N${this.generatedIdCount}`;
|
|
3268
|
+
this.generatedIds.set(node, id);
|
|
3269
|
+
}
|
|
3270
|
+
return id;
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Evaluate the `use` expression of an `xsl:key` for one node.
|
|
3274
|
+
*
|
|
3275
|
+
* @param {Node} node - The node being indexed
|
|
3276
|
+
* @param {string} expression - The `use` expression
|
|
3277
|
+
* @returns {string[]} The key values contributed by the node
|
|
3278
|
+
*/
|
|
3279
|
+
evaluateKeyValues(node, expression) {
|
|
3280
|
+
const context = this.rootContext.clone({
|
|
3281
|
+
currentNode: node,
|
|
3282
|
+
currentNodeList: [node],
|
|
3283
|
+
position: 1
|
|
3284
|
+
});
|
|
3285
|
+
const value = this.evaluateXPath(expression, context);
|
|
3286
|
+
if (Array.isArray(value)) {
|
|
3287
|
+
return value.map((item) => this.xpathEvaluator.getStringValue(item));
|
|
3288
|
+
}
|
|
3289
|
+
return [this.xpathEvaluator.toString(value)];
|
|
1678
3290
|
}
|
|
1679
3291
|
/**
|
|
1680
3292
|
* Set the stylesheet loader function for xsl:import and xsl:include
|
|
@@ -1685,14 +3297,13 @@ var XsltProcessorLib = (() => {
|
|
|
1685
3297
|
}
|
|
1686
3298
|
/**
|
|
1687
3299
|
* Resolve a relative URI against a base URI
|
|
3300
|
+
*
|
|
3301
|
+
* @param {string} href - The URI to resolve
|
|
3302
|
+
* @param {string} [baseUri] - The base URI
|
|
3303
|
+
* @returns {string} The resolved URI
|
|
1688
3304
|
*/
|
|
1689
3305
|
resolveUri(href, baseUri) {
|
|
1690
|
-
|
|
1691
|
-
return href;
|
|
1692
|
-
}
|
|
1693
|
-
const lastSlash = baseUri.lastIndexOf("/");
|
|
1694
|
-
const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
|
|
1695
|
-
return baseDir + href;
|
|
3306
|
+
return resolveUri(href, baseUri);
|
|
1696
3307
|
}
|
|
1697
3308
|
/**
|
|
1698
3309
|
* Load an external stylesheet document
|
|
@@ -1814,7 +3425,8 @@ var XsltProcessorLib = (() => {
|
|
|
1814
3425
|
this.currentImportPrecedence = savedPrecedence;
|
|
1815
3426
|
} catch (error) {
|
|
1816
3427
|
throw new Error(
|
|
1817
|
-
`Failed to include stylesheet "${href}": ${error.message}
|
|
3428
|
+
`Failed to include stylesheet "${href}": ${error.message}`,
|
|
3429
|
+
{ cause: error }
|
|
1818
3430
|
);
|
|
1819
3431
|
}
|
|
1820
3432
|
}
|
|
@@ -1842,7 +3454,8 @@ var XsltProcessorLib = (() => {
|
|
|
1842
3454
|
this.currentImportPrecedence++;
|
|
1843
3455
|
} catch (error) {
|
|
1844
3456
|
throw new Error(
|
|
1845
|
-
`Failed to import stylesheet "${href}": ${error.message}
|
|
3457
|
+
`Failed to import stylesheet "${href}": ${error.message}`,
|
|
3458
|
+
{ cause: error }
|
|
1846
3459
|
);
|
|
1847
3460
|
}
|
|
1848
3461
|
}
|
|
@@ -1918,39 +3531,50 @@ var XsltProcessorLib = (() => {
|
|
|
1918
3531
|
}
|
|
1919
3532
|
}
|
|
1920
3533
|
}
|
|
3534
|
+
/**
|
|
3535
|
+
* Register a template rule.
|
|
3536
|
+
*
|
|
3537
|
+
* A union match pattern is equivalent to a set of template rules, one per
|
|
3538
|
+
* alternative (XSLT 1.0 section 5.5), so each alternative is registered
|
|
3539
|
+
* separately with its own default priority.
|
|
3540
|
+
*
|
|
3541
|
+
* @param {Element} node - The xsl:template element
|
|
3542
|
+
*/
|
|
1921
3543
|
registerTemplate(node) {
|
|
1922
3544
|
const match = node.getAttribute("match");
|
|
1923
3545
|
const name = node.getAttribute("name");
|
|
1924
3546
|
const mode = node.getAttribute("mode") || null;
|
|
1925
3547
|
const priorityAttr = node.getAttribute("priority");
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
3548
|
+
const alternatives = match ? this.splitUnionPattern(match).map((p) => p.trim()) : [null];
|
|
3549
|
+
for (const alternative of alternatives) {
|
|
3550
|
+
this.templates.push({
|
|
3551
|
+
match: alternative,
|
|
3552
|
+
name,
|
|
3553
|
+
mode,
|
|
3554
|
+
priority: priorityAttr ? parseFloat(priorityAttr) : this.calculatePriority(alternative),
|
|
3555
|
+
importPrecedence: this.currentImportPrecedence,
|
|
3556
|
+
node
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
1935
3559
|
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Default priority of a single match pattern (see templatePriority.js).
|
|
3562
|
+
*
|
|
3563
|
+
* @param {string|null} matchPattern - The match pattern
|
|
3564
|
+
* @returns {number} The default priority
|
|
3565
|
+
*/
|
|
1936
3566
|
calculatePriority(matchPattern) {
|
|
1937
|
-
|
|
1938
|
-
if (matchPattern === "*" || matchPattern === "node()" || matchPattern === "text()" || matchPattern === "comment()" || matchPattern === "processing-instruction()") {
|
|
1939
|
-
return -0.5;
|
|
1940
|
-
}
|
|
1941
|
-
if (matchPattern.includes(":*")) {
|
|
1942
|
-
return -0.25;
|
|
1943
|
-
}
|
|
1944
|
-
if (/^[a-zA-Z_][\w.-]*$/.test(matchPattern)) {
|
|
1945
|
-
return 0;
|
|
1946
|
-
}
|
|
1947
|
-
return 0.5;
|
|
3567
|
+
return calculatePriority(matchPattern ? matchPattern.trim() : matchPattern);
|
|
1948
3568
|
}
|
|
1949
3569
|
processOutput(node) {
|
|
1950
3570
|
const method = node.getAttribute("method");
|
|
1951
3571
|
if (method) this.outputSettings.method = method;
|
|
3572
|
+
const version = node.getAttribute("version");
|
|
3573
|
+
if (version) this.outputSettings.version = version;
|
|
1952
3574
|
const encoding = node.getAttribute("encoding");
|
|
1953
3575
|
if (encoding) this.outputSettings.encoding = encoding;
|
|
3576
|
+
const standalone = node.getAttribute("standalone");
|
|
3577
|
+
if (standalone) this.outputSettings.standalone = standalone;
|
|
1954
3578
|
const indent = node.getAttribute("indent");
|
|
1955
3579
|
if (indent) this.outputSettings.indent = indent;
|
|
1956
3580
|
const omit = node.getAttribute("omit-xml-declaration");
|
|
@@ -1971,16 +3595,80 @@ var XsltProcessorLib = (() => {
|
|
|
1971
3595
|
const select2 = node.getAttribute("select");
|
|
1972
3596
|
this.globalVariables[name] = { node, select: select2 };
|
|
1973
3597
|
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Register an `xsl:param` top level declaration.
|
|
3600
|
+
*
|
|
3601
|
+
* A value supplied from outside (through `setParameter`) has precedence over
|
|
3602
|
+
* the declared default, so it survives compilation of the stylesheet.
|
|
3603
|
+
*
|
|
3604
|
+
* @param {Element} node - The `xsl:param` element
|
|
3605
|
+
* @returns {void}
|
|
3606
|
+
*/
|
|
1974
3607
|
processGlobalParam(node) {
|
|
1975
3608
|
const name = node.getAttribute("name");
|
|
1976
3609
|
const select2 = node.getAttribute("select");
|
|
1977
|
-
this.globalParameters[name]
|
|
3610
|
+
const existing = this.globalParameters[name];
|
|
3611
|
+
const definition = { node, select: select2 };
|
|
3612
|
+
if (existing && "value" in existing) {
|
|
3613
|
+
definition.value = existing.value;
|
|
3614
|
+
}
|
|
3615
|
+
this.globalParameters[name] = definition;
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Supply the value of a global parameter from outside the stylesheet.
|
|
3619
|
+
*
|
|
3620
|
+
* The value is merged into the `xsl:param` declaration when there is one, so
|
|
3621
|
+
* removing the value later restores the declared default.
|
|
3622
|
+
*
|
|
3623
|
+
* @param {string} name - The parameter name, `{uri}local` when namespaced
|
|
3624
|
+
* @param {*} value - The value to use
|
|
3625
|
+
* @returns {void}
|
|
3626
|
+
*
|
|
3627
|
+
* @example
|
|
3628
|
+
* engine.setParameterValue('sortOrder', 'ascending');
|
|
3629
|
+
*/
|
|
3630
|
+
setParameterValue(name, value) {
|
|
3631
|
+
const definition = this.globalParameters[name];
|
|
3632
|
+
if (definition) definition.value = value;
|
|
3633
|
+
else this.globalParameters[name] = { value };
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Remove an externally supplied parameter value.
|
|
3637
|
+
*
|
|
3638
|
+
* The `xsl:param` declaration of the stylesheet is kept, so the parameter
|
|
3639
|
+
* falls back to its declared default instead of becoming undefined.
|
|
3640
|
+
*
|
|
3641
|
+
* @param {string} name - The parameter name, `{uri}local` when namespaced
|
|
3642
|
+
* @returns {void}
|
|
3643
|
+
*
|
|
3644
|
+
* @example
|
|
3645
|
+
* engine.clearParameterValue('sortOrder');
|
|
3646
|
+
*/
|
|
3647
|
+
clearParameterValue(name) {
|
|
3648
|
+
const definition = this.globalParameters[name];
|
|
3649
|
+
if (!definition) return;
|
|
3650
|
+
if (definition.node) delete definition.value;
|
|
3651
|
+
else delete this.globalParameters[name];
|
|
3652
|
+
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Remove every externally supplied parameter value.
|
|
3655
|
+
*
|
|
3656
|
+
* @returns {void}
|
|
3657
|
+
*
|
|
3658
|
+
* @example
|
|
3659
|
+
* engine.clearParameterValues();
|
|
3660
|
+
*/
|
|
3661
|
+
clearParameterValues() {
|
|
3662
|
+
for (const name of Object.keys(this.globalParameters)) {
|
|
3663
|
+
this.clearParameterValue(name);
|
|
3664
|
+
}
|
|
1978
3665
|
}
|
|
1979
3666
|
processKey(node) {
|
|
1980
3667
|
const name = node.getAttribute("name");
|
|
1981
3668
|
const match = node.getAttribute("match");
|
|
1982
3669
|
const use = node.getAttribute("use");
|
|
1983
3670
|
this.keys[name] = { match, use };
|
|
3671
|
+
this.keyRegistry.clear();
|
|
1984
3672
|
}
|
|
1985
3673
|
processDecimalFormat(node) {
|
|
1986
3674
|
const name = node.getAttribute("name") || "";
|
|
@@ -1998,9 +3686,7 @@ var XsltProcessorLib = (() => {
|
|
|
1998
3686
|
};
|
|
1999
3687
|
}
|
|
2000
3688
|
processNamespaceAlias(node) {
|
|
2001
|
-
|
|
2002
|
-
const result = node.getAttribute("result-prefix");
|
|
2003
|
-
this.namespaceAliases[stylesheet] = result;
|
|
3689
|
+
this.namespaceAliases.add(node);
|
|
2004
3690
|
}
|
|
2005
3691
|
processAttributeSet(node) {
|
|
2006
3692
|
const name = node.getAttribute("name");
|
|
@@ -2030,11 +3716,13 @@ var XsltProcessorLib = (() => {
|
|
|
2030
3716
|
if (!doc) {
|
|
2031
3717
|
throw new Error("No output document available");
|
|
2032
3718
|
}
|
|
3719
|
+
const resultDocument = createResultDocument(doc);
|
|
3720
|
+
const source = this.prepareSource(sourceNode, doc);
|
|
2033
3721
|
const context = new XsltContext({
|
|
2034
|
-
currentNode:
|
|
2035
|
-
currentNodeList: [
|
|
3722
|
+
currentNode: source,
|
|
3723
|
+
currentNodeList: [source],
|
|
2036
3724
|
position: 1,
|
|
2037
|
-
outputDocument:
|
|
3725
|
+
outputDocument: resultDocument,
|
|
2038
3726
|
stylesheet: this.stylesheetDoc,
|
|
2039
3727
|
namespaces: { ...this.namespaces },
|
|
2040
3728
|
templates: this.templates,
|
|
@@ -2043,6 +3731,7 @@ var XsltProcessorLib = (() => {
|
|
|
2043
3731
|
outputMethod: this.outputSettings.method,
|
|
2044
3732
|
xpathEvaluator: this.xpathEvaluator
|
|
2045
3733
|
});
|
|
3734
|
+
this.rootContext = context;
|
|
2046
3735
|
for (const [name, def] of Object.entries(this.globalParameters)) {
|
|
2047
3736
|
if (!(name in context.parameters)) {
|
|
2048
3737
|
context.parameters[name] = this.evaluateVariable(def, context);
|
|
@@ -2051,28 +3740,91 @@ var XsltProcessorLib = (() => {
|
|
|
2051
3740
|
for (const [name, def] of Object.entries(this.globalVariables)) {
|
|
2052
3741
|
context.variables[name] = this.evaluateVariable(def, context);
|
|
2053
3742
|
}
|
|
2054
|
-
const fragment =
|
|
2055
|
-
this.applyTemplates([
|
|
2056
|
-
return fragment;
|
|
3743
|
+
const fragment = resultDocument.createDocumentFragment();
|
|
3744
|
+
this.applyTemplates([source], null, context, fragment);
|
|
3745
|
+
return importResultFragment(fragment, doc);
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Apply `xsl:strip-space` to the source tree.
|
|
3749
|
+
*
|
|
3750
|
+
* Stripping produces a copy so the caller's document is never modified; when
|
|
3751
|
+
* no `xsl:strip-space` is declared the original node is used unchanged.
|
|
3752
|
+
*
|
|
3753
|
+
* @param {Node} sourceNode - The source document or element
|
|
3754
|
+
* @param {Document} ownerDocument - Document providing the DOM implementation
|
|
3755
|
+
* @returns {Node} The source to transform
|
|
3756
|
+
*/
|
|
3757
|
+
prepareSource(sourceNode, ownerDocument) {
|
|
3758
|
+
const filter = new WhitespaceFilter(this.stripSpace, this.preserveSpace);
|
|
3759
|
+
if (!filter.isActive()) return sourceNode;
|
|
3760
|
+
return stripWhitespaceNodes(
|
|
3761
|
+
sourceNode,
|
|
3762
|
+
filter,
|
|
3763
|
+
createResultDocument(ownerDocument)
|
|
3764
|
+
);
|
|
2057
3765
|
}
|
|
2058
3766
|
/**
|
|
2059
3767
|
* Transform to a complete document
|
|
2060
3768
|
*/
|
|
2061
3769
|
transformToDocument(sourceNode) {
|
|
2062
|
-
const doc = this.createDocument();
|
|
3770
|
+
const doc = this.createDocument(sourceNode);
|
|
2063
3771
|
const fragment = this.transform(sourceNode, doc);
|
|
2064
3772
|
while (fragment.firstChild) {
|
|
2065
3773
|
doc.appendChild(fragment.firstChild);
|
|
2066
3774
|
}
|
|
2067
3775
|
return doc;
|
|
2068
3776
|
}
|
|
2069
|
-
|
|
3777
|
+
/**
|
|
3778
|
+
* Transform a source document and serialize the result to a string.
|
|
3779
|
+
*
|
|
3780
|
+
* Non-W3C convenience method: the result tree is serialized honoring the
|
|
3781
|
+
* `xsl:output` settings of the stylesheet (XSLT 1.0 section 16).
|
|
3782
|
+
*
|
|
3783
|
+
* @param {Node} sourceNode - Source document or element to transform
|
|
3784
|
+
* @returns {string} The serialized transformation result
|
|
3785
|
+
*/
|
|
3786
|
+
transformToString(sourceNode) {
|
|
3787
|
+
const fragment = this.transform(
|
|
3788
|
+
sourceNode,
|
|
3789
|
+
this.createDocument(sourceNode)
|
|
3790
|
+
);
|
|
3791
|
+
return serializeResult(fragment, this.outputSettings);
|
|
3792
|
+
}
|
|
3793
|
+
/**
|
|
3794
|
+
* Create an empty XML document to hold a transformation result.
|
|
3795
|
+
*
|
|
3796
|
+
* Uses the global `document` when running in a browser and otherwise falls
|
|
3797
|
+
* back to the DOM implementation owning `referenceNode` (e.g. a jsdom or
|
|
3798
|
+
* xmldom document in Node.js).
|
|
3799
|
+
*
|
|
3800
|
+
* @param {Node} [referenceNode] - Any node whose DOM implementation can be reused
|
|
3801
|
+
* @returns {Document} A new empty document
|
|
3802
|
+
* @throws {Error} When no DOM implementation is available
|
|
3803
|
+
*/
|
|
3804
|
+
createDocument(referenceNode) {
|
|
2070
3805
|
if (typeof document !== "undefined") {
|
|
2071
3806
|
return document.implementation.createDocument(null, null, null);
|
|
2072
3807
|
}
|
|
3808
|
+
const ownerDocument = referenceNode && (referenceNode.nodeType === 9 ? referenceNode : referenceNode.ownerDocument);
|
|
3809
|
+
if (ownerDocument?.implementation) {
|
|
3810
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
3811
|
+
}
|
|
2073
3812
|
throw new Error("Document creation not available in this environment");
|
|
2074
3813
|
}
|
|
3814
|
+
/**
|
|
3815
|
+
* Compute the value of a variable or parameter definition.
|
|
3816
|
+
*
|
|
3817
|
+
* A value supplied from outside (`setParameter`) wins over the `select`
|
|
3818
|
+
* expression and over the instantiated content of the declaration.
|
|
3819
|
+
*
|
|
3820
|
+
* @param {{value?: *, select?: string, node?: Element}} def - The definition
|
|
3821
|
+
* @param {XsltContext} context - The context used for evaluation
|
|
3822
|
+
* @returns {*} The variable value
|
|
3823
|
+
*/
|
|
2075
3824
|
evaluateVariable(def, context) {
|
|
3825
|
+
if ("value" in def) {
|
|
3826
|
+
return def.value;
|
|
3827
|
+
}
|
|
2076
3828
|
if (def.select) {
|
|
2077
3829
|
return this.evaluateXPath(def.select, context);
|
|
2078
3830
|
}
|
|
@@ -2092,7 +3844,9 @@ var XsltProcessorLib = (() => {
|
|
|
2092
3844
|
const newContext = context.clone({
|
|
2093
3845
|
currentNode: node,
|
|
2094
3846
|
currentNodeList: nodeList,
|
|
2095
|
-
position: i + 1
|
|
3847
|
+
position: i + 1,
|
|
3848
|
+
currentTemplate: template,
|
|
3849
|
+
currentMode: mode
|
|
2096
3850
|
});
|
|
2097
3851
|
this.processTemplate(template.node, newContext, output);
|
|
2098
3852
|
} else {
|
|
@@ -2103,13 +3857,14 @@ var XsltProcessorLib = (() => {
|
|
|
2103
3857
|
/**
|
|
2104
3858
|
* Find the best matching template for a node
|
|
2105
3859
|
*/
|
|
2106
|
-
findMatchingTemplate(node, mode, context) {
|
|
3860
|
+
findMatchingTemplate(node, mode, context, maxImportPrecedence = Infinity) {
|
|
2107
3861
|
let bestMatch = null;
|
|
2108
3862
|
let bestPriority = -Infinity;
|
|
2109
3863
|
let bestImportPrecedence = -Infinity;
|
|
2110
3864
|
for (const template of this.templates) {
|
|
2111
3865
|
if (template.mode !== mode) continue;
|
|
2112
3866
|
if (!template.match) continue;
|
|
3867
|
+
if ((template.importPrecedence || 0) >= maxImportPrecedence) continue;
|
|
2113
3868
|
if (this.matchesPattern(node, template.match, context)) {
|
|
2114
3869
|
const priority = template.priority;
|
|
2115
3870
|
const importPrecedence = template.importPrecedence || 0;
|
|
@@ -2170,10 +3925,9 @@ var XsltProcessorLib = (() => {
|
|
|
2170
3925
|
return parts;
|
|
2171
3926
|
}
|
|
2172
3927
|
matchesSinglePattern(node, pattern, context) {
|
|
2173
|
-
var _a;
|
|
2174
3928
|
try {
|
|
2175
3929
|
if (pattern === "/") {
|
|
2176
|
-
return node.nodeType === 9 || node ===
|
|
3930
|
+
return node.nodeType === 9 || node === node.ownerDocument?.documentElement;
|
|
2177
3931
|
}
|
|
2178
3932
|
const ast = parse(pattern);
|
|
2179
3933
|
if (pattern.startsWith("/")) {
|
|
@@ -2183,19 +3937,22 @@ var XsltProcessorLib = (() => {
|
|
|
2183
3937
|
1,
|
|
2184
3938
|
1,
|
|
2185
3939
|
{ ...context.variables, ...context.parameters },
|
|
2186
|
-
context.namespaces
|
|
3940
|
+
context.namespaces,
|
|
3941
|
+
context
|
|
2187
3942
|
);
|
|
2188
3943
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2189
3944
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
2190
3945
|
return nodes2.includes(node);
|
|
2191
3946
|
}
|
|
2192
|
-
|
|
3947
|
+
const parent = node.nodeType === 2 ? node.ownerElement : node.parentNode;
|
|
3948
|
+
if (parent) {
|
|
2193
3949
|
const xpathContext2 = new XPathContext(
|
|
2194
|
-
|
|
3950
|
+
parent,
|
|
2195
3951
|
1,
|
|
2196
3952
|
1,
|
|
2197
3953
|
{ ...context.variables, ...context.parameters },
|
|
2198
|
-
context.namespaces
|
|
3954
|
+
context.namespaces,
|
|
3955
|
+
context
|
|
2199
3956
|
);
|
|
2200
3957
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2201
3958
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
@@ -2206,7 +3963,8 @@ var XsltProcessorLib = (() => {
|
|
|
2206
3963
|
1,
|
|
2207
3964
|
1,
|
|
2208
3965
|
{ ...context.variables, ...context.parameters },
|
|
2209
|
-
context.namespaces
|
|
3966
|
+
context.namespaces,
|
|
3967
|
+
context
|
|
2210
3968
|
);
|
|
2211
3969
|
const result = this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2212
3970
|
const nodes = Array.isArray(result) ? result : [result];
|
|
@@ -2320,6 +4078,9 @@ var XsltProcessorLib = (() => {
|
|
|
2320
4078
|
case "apply-templates":
|
|
2321
4079
|
this.xslApplyTemplates(node, context, output);
|
|
2322
4080
|
break;
|
|
4081
|
+
case "apply-imports":
|
|
4082
|
+
this.xslApplyImports(node, context, output);
|
|
4083
|
+
break;
|
|
2323
4084
|
case "call-template":
|
|
2324
4085
|
this.xslCallTemplate(node, context, output);
|
|
2325
4086
|
break;
|
|
@@ -2379,34 +4140,47 @@ var XsltProcessorLib = (() => {
|
|
|
2379
4140
|
}
|
|
2380
4141
|
/**
|
|
2381
4142
|
* Process a literal result element (non-XSLT)
|
|
4143
|
+
*
|
|
4144
|
+
* Applies `xsl:namespace-alias` to the element and its attributes, honours
|
|
4145
|
+
* `xsl:use-attribute-sets` and keeps XSLT-only attributes and namespace
|
|
4146
|
+
* declarations out of the result tree.
|
|
4147
|
+
*
|
|
4148
|
+
* @param {Element} node - The literal result element in the stylesheet
|
|
4149
|
+
* @param {XsltContext} context - The current XSLT context
|
|
4150
|
+
* @param {Node} output - The result tree node receiving the element
|
|
4151
|
+
* @returns {void}
|
|
2382
4152
|
*/
|
|
2383
4153
|
processLiteralResultElement(node, context, output) {
|
|
2384
|
-
|
|
2385
|
-
const
|
|
2386
|
-
const
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
if (resolvedNS && context.outputDocument.createElementNS) {
|
|
2397
|
-
outputElement = context.outputDocument.createElementNS(
|
|
2398
|
-
resolvedNS,
|
|
2399
|
-
nodeName
|
|
2400
|
-
);
|
|
2401
|
-
} else {
|
|
2402
|
-
outputElement = context.outputDocument.createElement(nodeName);
|
|
4154
|
+
const localName = node.localName || node.nodeName;
|
|
4155
|
+
const alias = this.namespaceAliases.resolve(node.namespaceURI, localName);
|
|
4156
|
+
const namespaceUri = alias ? alias.namespaceUri : node.namespaceURI;
|
|
4157
|
+
const qname = alias ? alias.qname : node.nodeName;
|
|
4158
|
+
const outputElement = namespaceUri && context.outputDocument.createElementNS ? context.outputDocument.createElementNS(namespaceUri, qname) : context.outputDocument.createElement(qname);
|
|
4159
|
+
const useAttributeSets = getXsltAttribute(
|
|
4160
|
+
node,
|
|
4161
|
+
"use-attribute-sets",
|
|
4162
|
+
XSLT_NS
|
|
4163
|
+
);
|
|
4164
|
+
if (useAttributeSets) {
|
|
4165
|
+
this.applyAttributeSets(useAttributeSets, context, outputElement);
|
|
2403
4166
|
}
|
|
2404
4167
|
if (node.attributes) {
|
|
2405
4168
|
for (const attr of node.attributes) {
|
|
2406
|
-
if (attr
|
|
2407
|
-
if (attr.name.startsWith("xmlns")) continue;
|
|
4169
|
+
if (!shouldCopyAttribute(attr, XSLT_NS)) continue;
|
|
2408
4170
|
const value = this.processAttributeValueTemplate(attr.value, context);
|
|
2409
|
-
|
|
4171
|
+
const attrAlias = this.namespaceAliases.resolve(
|
|
4172
|
+
attr.namespaceURI,
|
|
4173
|
+
attr.localName || attr.name
|
|
4174
|
+
);
|
|
4175
|
+
if (attrAlias) {
|
|
4176
|
+
outputElement.setAttributeNS(
|
|
4177
|
+
attrAlias.namespaceUri,
|
|
4178
|
+
attrAlias.qname,
|
|
4179
|
+
value
|
|
4180
|
+
);
|
|
4181
|
+
} else {
|
|
4182
|
+
outputElement.setAttribute(attr.name, value);
|
|
4183
|
+
}
|
|
2410
4184
|
}
|
|
2411
4185
|
}
|
|
2412
4186
|
this.processChildren(node, context, outputElement);
|
|
@@ -2493,6 +4267,38 @@ var XsltProcessorLib = (() => {
|
|
|
2493
4267
|
});
|
|
2494
4268
|
this.applyTemplates(nodes, mode, newContext, output);
|
|
2495
4269
|
}
|
|
4270
|
+
/**
|
|
4271
|
+
* Instantiate `xsl:apply-imports`.
|
|
4272
|
+
*
|
|
4273
|
+
* Only templates with a lower import precedence than the template being
|
|
4274
|
+
* instantiated are considered; when none matches, the built-in template rules
|
|
4275
|
+
* apply, exactly as for `xsl:apply-templates`.
|
|
4276
|
+
*
|
|
4277
|
+
* @param {Element} node - The `xsl:apply-imports` element
|
|
4278
|
+
* @param {XsltContext} context - The current XSLT context
|
|
4279
|
+
* @param {Node} output - The result tree node receiving the output
|
|
4280
|
+
* @returns {void}
|
|
4281
|
+
*/
|
|
4282
|
+
xslApplyImports(node, context, output) {
|
|
4283
|
+
const currentNode = context.currentNode;
|
|
4284
|
+
const mode = context.currentMode ?? null;
|
|
4285
|
+
const precedence = context.currentTemplate ? context.currentTemplate.importPrecedence || 0 : 0;
|
|
4286
|
+
const template = this.findMatchingTemplate(
|
|
4287
|
+
currentNode,
|
|
4288
|
+
mode,
|
|
4289
|
+
context,
|
|
4290
|
+
precedence
|
|
4291
|
+
);
|
|
4292
|
+
if (!template) {
|
|
4293
|
+
this.applyBuiltinTemplate(currentNode, mode, context, output);
|
|
4294
|
+
return;
|
|
4295
|
+
}
|
|
4296
|
+
this.processTemplate(
|
|
4297
|
+
template.node,
|
|
4298
|
+
context.clone({ currentTemplate: template }),
|
|
4299
|
+
output
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
2496
4302
|
xslCallTemplate(node, context, output) {
|
|
2497
4303
|
const name = node.getAttribute("name");
|
|
2498
4304
|
const template = this.templates.find((t) => t.name === name);
|
|
@@ -2817,79 +4623,47 @@ var XsltProcessorLib = (() => {
|
|
|
2817
4623
|
xslNumber(node, context, output) {
|
|
2818
4624
|
const value = node.getAttribute("value");
|
|
2819
4625
|
const format = node.getAttribute("format") || "1";
|
|
2820
|
-
|
|
2821
|
-
let number;
|
|
4626
|
+
let numbers;
|
|
2822
4627
|
if (value) {
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
4628
|
+
numbers = [
|
|
4629
|
+
Math.round(
|
|
4630
|
+
this.xpathEvaluator.toNumber(this.evaluateXPath(value, context))
|
|
4631
|
+
)
|
|
4632
|
+
];
|
|
2826
4633
|
} else {
|
|
2827
|
-
|
|
4634
|
+
numbers = countXsltNumber(
|
|
4635
|
+
context.currentNode,
|
|
4636
|
+
{
|
|
4637
|
+
level: node.getAttribute("level") || "single",
|
|
4638
|
+
count: node.getAttribute("count"),
|
|
4639
|
+
from: node.getAttribute("from")
|
|
4640
|
+
},
|
|
4641
|
+
(candidate, pattern) => this.matchesPattern(candidate, pattern, context)
|
|
4642
|
+
);
|
|
2828
4643
|
}
|
|
2829
|
-
const
|
|
2830
|
-
|
|
4644
|
+
const text = context.outputDocument.createTextNode(
|
|
4645
|
+
formatXsltNumber(numbers, format)
|
|
4646
|
+
);
|
|
2831
4647
|
output.appendChild(text);
|
|
2832
4648
|
}
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
if (sibling.nodeType === 1) {
|
|
2841
|
-
if (!count || this.matchesPattern(sibling, count, context)) {
|
|
2842
|
-
n++;
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
sibling = sibling.previousSibling;
|
|
2846
|
-
}
|
|
2847
|
-
return n;
|
|
2848
|
-
}
|
|
2849
|
-
return 1;
|
|
2850
|
-
}
|
|
4649
|
+
/**
|
|
4650
|
+
* Format a single number with an `xsl:number` format token.
|
|
4651
|
+
*
|
|
4652
|
+
* @param {number} number - The number to format
|
|
4653
|
+
* @param {string} format - The format token, e.g. `1`, `01`, `a`, `I`
|
|
4654
|
+
* @returns {string} The formatted number
|
|
4655
|
+
*/
|
|
2851
4656
|
formatNumber(number, format) {
|
|
2852
|
-
|
|
2853
|
-
return String(number).padStart(format.length, "0");
|
|
2854
|
-
}
|
|
2855
|
-
if (format === "a") {
|
|
2856
|
-
return String.fromCharCode(96 + (number - 1) % 26 + 1);
|
|
2857
|
-
}
|
|
2858
|
-
if (format === "A") {
|
|
2859
|
-
return String.fromCharCode(64 + (number - 1) % 26 + 1);
|
|
2860
|
-
}
|
|
2861
|
-
if (format === "i") {
|
|
2862
|
-
return this.toRoman(number).toLowerCase();
|
|
2863
|
-
}
|
|
2864
|
-
if (format === "I") {
|
|
2865
|
-
return this.toRoman(number);
|
|
2866
|
-
}
|
|
2867
|
-
return String(number);
|
|
4657
|
+
return formatXsltNumber([number], format);
|
|
2868
4658
|
}
|
|
4659
|
+
/**
|
|
4660
|
+
* Convert a number to an upper case Roman numeral.
|
|
4661
|
+
*
|
|
4662
|
+
* @param {number} num - The number to convert
|
|
4663
|
+
* @returns {string} The Roman numeral
|
|
4664
|
+
*/
|
|
2869
4665
|
toRoman(num) {
|
|
2870
|
-
|
|
2871
|
-
["M", 1e3],
|
|
2872
|
-
["CM", 900],
|
|
2873
|
-
["D", 500],
|
|
2874
|
-
["CD", 400],
|
|
2875
|
-
["C", 100],
|
|
2876
|
-
["XC", 90],
|
|
2877
|
-
["L", 50],
|
|
2878
|
-
["XL", 40],
|
|
2879
|
-
["X", 10],
|
|
2880
|
-
["IX", 9],
|
|
2881
|
-
["V", 5],
|
|
2882
|
-
["IV", 4],
|
|
2883
|
-
["I", 1]
|
|
2884
|
-
];
|
|
2885
|
-
let result = "";
|
|
2886
|
-
for (const [numeral, value] of romanNumerals) {
|
|
2887
|
-
while (num >= value) {
|
|
2888
|
-
result += numeral;
|
|
2889
|
-
num -= value;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
return result;
|
|
4666
|
+
return toRoman(num);
|
|
2893
4667
|
}
|
|
2894
4668
|
xslMessage(node, context, _output) {
|
|
2895
4669
|
const terminate = node.getAttribute("terminate") === "yes";
|
|
@@ -2973,7 +4747,8 @@ var XsltProcessorLib = (() => {
|
|
|
2973
4747
|
context.position,
|
|
2974
4748
|
context.currentNodeList.length,
|
|
2975
4749
|
{ ...context.variables, ...context.parameters },
|
|
2976
|
-
context.namespaces
|
|
4750
|
+
context.namespaces,
|
|
4751
|
+
context
|
|
2977
4752
|
);
|
|
2978
4753
|
return this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2979
4754
|
}
|
|
@@ -2993,6 +4768,94 @@ var XsltProcessorLib = (() => {
|
|
|
2993
4768
|
this._engine = null;
|
|
2994
4769
|
this._stylesheet = null;
|
|
2995
4770
|
this._parameters = /* @__PURE__ */ new Map();
|
|
4771
|
+
this._stylesheetLoader = null;
|
|
4772
|
+
this._documentLoader = null;
|
|
4773
|
+
}
|
|
4774
|
+
/**
|
|
4775
|
+
* The underlying XSLT engine (advanced usage).
|
|
4776
|
+
*
|
|
4777
|
+
* The engine is created lazily by {@link XSLTProcessor#importStylesheet},
|
|
4778
|
+
* so this getter returns `null` until a stylesheet has been imported.
|
|
4779
|
+
* Prefer the public {@link XSLTProcessor#setStylesheetLoader} over reaching
|
|
4780
|
+
* into the engine directly.
|
|
4781
|
+
*
|
|
4782
|
+
* @returns {import('./xslt/engine.js').XsltEngine|null} The engine, or null before import
|
|
4783
|
+
*
|
|
4784
|
+
* @example
|
|
4785
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
4786
|
+
* console.log(processor.engine.outputSettings.method);
|
|
4787
|
+
*/
|
|
4788
|
+
get engine() {
|
|
4789
|
+
return this._engine;
|
|
4790
|
+
}
|
|
4791
|
+
/**
|
|
4792
|
+
* Sets the loader used to resolve `xsl:import` and `xsl:include` references.
|
|
4793
|
+
*
|
|
4794
|
+
* The loader is synchronous: it MUST return the external stylesheet as a
|
|
4795
|
+
* `Document` or as an XML string (which is parsed automatically). Promises
|
|
4796
|
+
* are not awaited by the engine, so pre-load remote stylesheets before
|
|
4797
|
+
* calling `importStylesheet`.
|
|
4798
|
+
*
|
|
4799
|
+
* The loader may be set before or after `importStylesheet`. When set before,
|
|
4800
|
+
* it is passed to the engine on creation, which is required for the loader to
|
|
4801
|
+
* be used while the stylesheet is being compiled. When set after, the live
|
|
4802
|
+
* engine is updated as well.
|
|
4803
|
+
*
|
|
4804
|
+
* @param {((href: string, baseUri?: string) => (Document|string))|null} loader
|
|
4805
|
+
* The loader function, or null to remove a previously configured loader
|
|
4806
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
4807
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
4808
|
+
*
|
|
4809
|
+
* @example
|
|
4810
|
+
* processor.setStylesheetLoader((href) => readFileSync(href, 'utf8'));
|
|
4811
|
+
* processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
4812
|
+
*/
|
|
4813
|
+
setStylesheetLoader(loader) {
|
|
4814
|
+
if (loader !== null && loader !== void 0 && typeof loader !== "function") {
|
|
4815
|
+
throw new TypeError(
|
|
4816
|
+
"Failed to execute 'setStylesheetLoader' on 'XSLTProcessor': The loader argument must be a function or null."
|
|
4817
|
+
);
|
|
4818
|
+
}
|
|
4819
|
+
this._stylesheetLoader = loader ?? null;
|
|
4820
|
+
if (this._engine) {
|
|
4821
|
+
this._engine.setStylesheetLoader(this._stylesheetLoader);
|
|
4822
|
+
}
|
|
4823
|
+
return this;
|
|
4824
|
+
}
|
|
4825
|
+
/**
|
|
4826
|
+
* Sets the loader used to resolve the XSLT `document()` function.
|
|
4827
|
+
*
|
|
4828
|
+
* The loader is synchronous: it MUST return the referenced document as a
|
|
4829
|
+
* `Document`, as an XML string (which is parsed automatically) or as `null`
|
|
4830
|
+
* when the document cannot be provided. Returning `null`, like configuring no
|
|
4831
|
+
* loader at all, makes `document()` evaluate to an empty node-set rather than
|
|
4832
|
+
* failing the transformation.
|
|
4833
|
+
*
|
|
4834
|
+
* The loader may be set before or after `importStylesheet`; a live engine is
|
|
4835
|
+
* kept in sync.
|
|
4836
|
+
*
|
|
4837
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader
|
|
4838
|
+
* The loader function, or null to remove a previously configured loader
|
|
4839
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
4840
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
4841
|
+
*
|
|
4842
|
+
* @example
|
|
4843
|
+
* // Node.js: resolve document() against the file system
|
|
4844
|
+
* import { readFileSync } from 'node:fs';
|
|
4845
|
+
* processor.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
4846
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
4847
|
+
*/
|
|
4848
|
+
setDocumentLoader(loader) {
|
|
4849
|
+
if (loader !== null && loader !== void 0 && typeof loader !== "function") {
|
|
4850
|
+
throw new TypeError(
|
|
4851
|
+
"Failed to execute 'setDocumentLoader' on 'XSLTProcessor': The loader argument must be a function or null."
|
|
4852
|
+
);
|
|
4853
|
+
}
|
|
4854
|
+
this._documentLoader = loader ?? null;
|
|
4855
|
+
if (this._engine) {
|
|
4856
|
+
this._engine.setDocumentLoader(this._documentLoader);
|
|
4857
|
+
}
|
|
4858
|
+
return this;
|
|
2996
4859
|
}
|
|
2997
4860
|
/**
|
|
2998
4861
|
* Imports the XSLT stylesheet.
|
|
@@ -3002,14 +4865,17 @@ var XsltProcessorLib = (() => {
|
|
|
3002
4865
|
* <xsl:stylesheet> or <xsl:transform> element.
|
|
3003
4866
|
*
|
|
3004
4867
|
* @param {Node} style - The XSLT stylesheet to import (Document or Element)
|
|
4868
|
+
* @param {string} [stylesheetUri] - Optional URI of the stylesheet, used as the
|
|
4869
|
+
* base URI when resolving relative `xsl:import`/`xsl:include` hrefs. When
|
|
4870
|
+
* omitted, hrefs are passed to the loader unresolved.
|
|
3005
4871
|
* @returns {void}
|
|
3006
4872
|
*
|
|
3007
4873
|
* @example
|
|
3008
4874
|
* const parser = new DOMParser();
|
|
3009
4875
|
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
3010
|
-
* processor.importStylesheet(xslDoc);
|
|
4876
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
3011
4877
|
*/
|
|
3012
|
-
importStylesheet(style) {
|
|
4878
|
+
importStylesheet(style, stylesheetUri) {
|
|
3013
4879
|
if (!style) {
|
|
3014
4880
|
throw new TypeError(
|
|
3015
4881
|
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present."
|
|
@@ -3025,11 +4891,14 @@ var XsltProcessorLib = (() => {
|
|
|
3025
4891
|
throw new Error("XSLT stylesheet contains parse errors");
|
|
3026
4892
|
}
|
|
3027
4893
|
this._stylesheet = style;
|
|
3028
|
-
this._engine = new XsltEngine(
|
|
4894
|
+
this._engine = new XsltEngine({
|
|
4895
|
+
stylesheetLoader: this._stylesheetLoader,
|
|
4896
|
+
documentLoader: this._documentLoader
|
|
4897
|
+
});
|
|
3029
4898
|
for (const [key, value] of this._parameters) {
|
|
3030
|
-
this._engine.
|
|
4899
|
+
this._engine.setParameterValue(key, value);
|
|
3031
4900
|
}
|
|
3032
|
-
this._engine.importStylesheet(style);
|
|
4901
|
+
this._engine.importStylesheet(style, stylesheetUri);
|
|
3033
4902
|
}
|
|
3034
4903
|
/**
|
|
3035
4904
|
* Transforms the node source by applying the XSLT stylesheet.
|
|
@@ -3110,6 +4979,45 @@ var XsltProcessorLib = (() => {
|
|
|
3110
4979
|
return null;
|
|
3111
4980
|
}
|
|
3112
4981
|
}
|
|
4982
|
+
/**
|
|
4983
|
+
* Transforms the node source by applying the XSLT stylesheet and serializes
|
|
4984
|
+
* the result to a string honoring the stylesheet `xsl:output` settings.
|
|
4985
|
+
*
|
|
4986
|
+
* Non-W3C convenience method: the native XSLTProcessor has no equivalent.
|
|
4987
|
+
* Output method, indentation, XML declaration, document type declaration,
|
|
4988
|
+
* CDATA sections and `disable-output-escaping` are all honored
|
|
4989
|
+
* (XSLT 1.0 section 16).
|
|
4990
|
+
*
|
|
4991
|
+
* @param {Node} source - The XML document to transform
|
|
4992
|
+
* @returns {string|null} The serialized result, or null on a transformation error
|
|
4993
|
+
*
|
|
4994
|
+
* @example
|
|
4995
|
+
* const xml = processor.transformToString(xmlDoc);
|
|
4996
|
+
* // '<?xml version="1.0" encoding="UTF-8"?>\n<BAR>\n <QUX/>\n</BAR>'
|
|
4997
|
+
*/
|
|
4998
|
+
transformToString(source) {
|
|
4999
|
+
if (!source) {
|
|
5000
|
+
throw new TypeError(
|
|
5001
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': 1 argument required, but only 0 present."
|
|
5002
|
+
);
|
|
5003
|
+
}
|
|
5004
|
+
if (!this._engine || !this._stylesheet) {
|
|
5005
|
+
throw new Error(
|
|
5006
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': No stylesheet has been imported."
|
|
5007
|
+
);
|
|
5008
|
+
}
|
|
5009
|
+
if (source.nodeType !== 1 && source.nodeType !== 9 && source.nodeType !== 11) {
|
|
5010
|
+
throw new TypeError(
|
|
5011
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': The source is not a valid node type."
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
try {
|
|
5015
|
+
return this._engine.transformToString(source);
|
|
5016
|
+
} catch (error) {
|
|
5017
|
+
console.error("XSLT transformation error:", error);
|
|
5018
|
+
return null;
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
3113
5021
|
/**
|
|
3114
5022
|
* Sets a parameter in the XSLT stylesheet.
|
|
3115
5023
|
*
|
|
@@ -3136,7 +5044,7 @@ var XsltProcessorLib = (() => {
|
|
|
3136
5044
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3137
5045
|
this._parameters.set(key, value);
|
|
3138
5046
|
if (this._engine) {
|
|
3139
|
-
this._engine.
|
|
5047
|
+
this._engine.setParameterValue(key, value);
|
|
3140
5048
|
}
|
|
3141
5049
|
}
|
|
3142
5050
|
/**
|
|
@@ -3193,7 +5101,7 @@ var XsltProcessorLib = (() => {
|
|
|
3193
5101
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3194
5102
|
this._parameters.delete(key);
|
|
3195
5103
|
if (this._engine) {
|
|
3196
|
-
|
|
5104
|
+
this._engine.clearParameterValue(key);
|
|
3197
5105
|
}
|
|
3198
5106
|
}
|
|
3199
5107
|
/**
|
|
@@ -3209,12 +5117,19 @@ var XsltProcessorLib = (() => {
|
|
|
3209
5117
|
clearParameters() {
|
|
3210
5118
|
this._parameters.clear();
|
|
3211
5119
|
if (this._engine) {
|
|
3212
|
-
this._engine.
|
|
5120
|
+
this._engine.clearParameterValues();
|
|
3213
5121
|
}
|
|
3214
5122
|
}
|
|
3215
5123
|
/**
|
|
3216
5124
|
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
3217
5125
|
*
|
|
5126
|
+
* Per the W3C `XSLTProcessor` semantics, `reset()` clears stylesheet state and
|
|
5127
|
+
* parameters only. The stylesheet and document loaders are processor
|
|
5128
|
+
* configuration rather than stylesheet state, so they are deliberately
|
|
5129
|
+
* preserved and stay effective for the next `importStylesheet()` call. Pass
|
|
5130
|
+
* `null` to {@link XSLTProcessor#setStylesheetLoader} or
|
|
5131
|
+
* {@link XSLTProcessor#setDocumentLoader} to remove them explicitly.
|
|
5132
|
+
*
|
|
3218
5133
|
* @returns {void}
|
|
3219
5134
|
*
|
|
3220
5135
|
* @example
|
|
@@ -3283,9 +5198,9 @@ var XsltProcessorLib = (() => {
|
|
|
3283
5198
|
}
|
|
3284
5199
|
|
|
3285
5200
|
// src/index.js
|
|
3286
|
-
var VERSION = "1.
|
|
5201
|
+
var VERSION = "1.1.1";
|
|
3287
5202
|
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
3288
|
-
var isNode = typeof process !== "undefined" && process.versions
|
|
5203
|
+
var isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
3289
5204
|
return __toCommonJS(index_exports);
|
|
3290
5205
|
})();
|
|
3291
5206
|
|