@tradik/xslt-processor 1.0.2 → 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 +292 -47
- 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 +2073 -163
- 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 +2077 -162
- 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 +2072 -161
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +27 -16
- 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 +474 -185
- 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
package/dist/xslt-processor.js
CHANGED
|
@@ -781,12 +781,22 @@ var FORBIDDEN_VARIABLE_NAMES = Object.freeze([
|
|
|
781
781
|
"__lookupSetter__"
|
|
782
782
|
]);
|
|
783
783
|
var XPathContext = class _XPathContext {
|
|
784
|
-
|
|
784
|
+
/**
|
|
785
|
+
* @param {Node} node - The context node
|
|
786
|
+
* @param {number} [position] - The context position (1-based)
|
|
787
|
+
* @param {number} [size] - The context size
|
|
788
|
+
* @param {Object} [variables] - Variable bindings by name
|
|
789
|
+
* @param {Object} [namespaces] - Namespace bindings by prefix
|
|
790
|
+
* @param {*} [hostContext] - Opaque context of the host language (XSLT),
|
|
791
|
+
* carried through unchanged so host defined functions can reach it
|
|
792
|
+
*/
|
|
793
|
+
constructor(node, position = 1, size = 1, variables = {}, namespaces = {}, hostContext = null) {
|
|
785
794
|
this.node = node;
|
|
786
795
|
this.position = position;
|
|
787
796
|
this.size = size;
|
|
788
797
|
this.variables = variables;
|
|
789
798
|
this.namespaces = namespaces;
|
|
799
|
+
this.hostContext = hostContext;
|
|
790
800
|
}
|
|
791
801
|
clone(overrides = {}) {
|
|
792
802
|
return new _XPathContext(
|
|
@@ -794,13 +804,17 @@ var XPathContext = class _XPathContext {
|
|
|
794
804
|
overrides.position ?? this.position,
|
|
795
805
|
overrides.size ?? this.size,
|
|
796
806
|
overrides.variables ?? this.variables,
|
|
797
|
-
overrides.namespaces ?? this.namespaces
|
|
807
|
+
overrides.namespaces ?? this.namespaces,
|
|
808
|
+
overrides.hostContext ?? this.hostContext
|
|
798
809
|
);
|
|
799
810
|
}
|
|
800
811
|
};
|
|
801
812
|
var XPathEvaluator = class {
|
|
802
813
|
constructor(options = {}) {
|
|
803
|
-
this.functions =
|
|
814
|
+
this.functions = Object.assign(
|
|
815
|
+
/* @__PURE__ */ Object.create(null),
|
|
816
|
+
this.initCoreFunctions()
|
|
817
|
+
);
|
|
804
818
|
this.maxRecursionDepth = options.maxRecursionDepth ?? XPathLimits.MAX_RECURSION_DEPTH;
|
|
805
819
|
this.maxResultSize = options.maxResultSize ?? XPathLimits.MAX_RESULT_SIZE;
|
|
806
820
|
this.maxStringLength = options.maxStringLength ?? XPathLimits.MAX_STRING_LENGTH;
|
|
@@ -1150,7 +1164,7 @@ var XPathEvaluator = class {
|
|
|
1150
1164
|
case "node":
|
|
1151
1165
|
return true;
|
|
1152
1166
|
case "text":
|
|
1153
|
-
return node.nodeType === 3;
|
|
1167
|
+
return node.nodeType === 3 || node.nodeType === 4;
|
|
1154
1168
|
case "comment":
|
|
1155
1169
|
return node.nodeType === 8;
|
|
1156
1170
|
case "processing-instruction":
|
|
@@ -1191,13 +1205,31 @@ var XPathEvaluator = class {
|
|
|
1191
1205
|
}
|
|
1192
1206
|
return context.variables[name];
|
|
1193
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Register additional functions, for example the XSLT function library.
|
|
1210
|
+
*
|
|
1211
|
+
* Existing names are overwritten, so a host language can also specialise a
|
|
1212
|
+
* core function. Each function is called as `fn(args, context)` with the
|
|
1213
|
+
* evaluator as `this`.
|
|
1214
|
+
*
|
|
1215
|
+
* @param {Object<string, Function>} functions - Functions by name
|
|
1216
|
+
* @returns {XPathEvaluator} This evaluator, to allow chaining
|
|
1217
|
+
*
|
|
1218
|
+
* @example
|
|
1219
|
+
* evaluator.registerFunctions({ 'my:double': (args, ctx) => 2 });
|
|
1220
|
+
*/
|
|
1221
|
+
registerFunctions(functions) {
|
|
1222
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
1223
|
+
this.functions[name] = fn;
|
|
1224
|
+
}
|
|
1225
|
+
return this;
|
|
1226
|
+
}
|
|
1194
1227
|
evalFunctionCall(ast, context) {
|
|
1195
1228
|
const name = ast.prefix ? `${ast.prefix}:${ast.name}` : ast.name;
|
|
1196
|
-
|
|
1197
|
-
if (!fn) {
|
|
1229
|
+
if (!Object.hasOwn(this.functions, name)) {
|
|
1198
1230
|
throw new Error(`Unknown function: ${name}`);
|
|
1199
1231
|
}
|
|
1200
|
-
return
|
|
1232
|
+
return this.functions[name].call(this, ast.args, context);
|
|
1201
1233
|
}
|
|
1202
1234
|
// Type conversion functions
|
|
1203
1235
|
toBoolean(value) {
|
|
@@ -1254,7 +1286,7 @@ var XPathEvaluator = class {
|
|
|
1254
1286
|
case 11: {
|
|
1255
1287
|
let text = "";
|
|
1256
1288
|
const walker = (n) => {
|
|
1257
|
-
if (n.nodeType === 3) {
|
|
1289
|
+
if (n.nodeType === 3 || n.nodeType === 4) {
|
|
1258
1290
|
text += n.nodeValue || "";
|
|
1259
1291
|
} else if (n.childNodes) {
|
|
1260
1292
|
for (const child of n.childNodes) {
|
|
@@ -1559,8 +1591,1486 @@ var XPathEvaluator = class {
|
|
|
1559
1591
|
}
|
|
1560
1592
|
};
|
|
1561
1593
|
|
|
1594
|
+
// src/xslt/elements.js
|
|
1595
|
+
var XSLT_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
|
|
1596
|
+
var XSLT_ELEMENTS = Object.freeze([
|
|
1597
|
+
"apply-imports",
|
|
1598
|
+
"apply-templates",
|
|
1599
|
+
"attribute",
|
|
1600
|
+
"call-template",
|
|
1601
|
+
"choose",
|
|
1602
|
+
"comment",
|
|
1603
|
+
"copy",
|
|
1604
|
+
"copy-of",
|
|
1605
|
+
"element",
|
|
1606
|
+
"fallback",
|
|
1607
|
+
"for-each",
|
|
1608
|
+
"if",
|
|
1609
|
+
"message",
|
|
1610
|
+
"number",
|
|
1611
|
+
"otherwise",
|
|
1612
|
+
"param",
|
|
1613
|
+
"processing-instruction",
|
|
1614
|
+
"sort",
|
|
1615
|
+
"text",
|
|
1616
|
+
"value-of",
|
|
1617
|
+
"variable",
|
|
1618
|
+
"when",
|
|
1619
|
+
"with-param"
|
|
1620
|
+
]);
|
|
1621
|
+
var ELEMENT_SET = new Set(XSLT_ELEMENTS);
|
|
1622
|
+
function isXsltElementAvailable(localName) {
|
|
1623
|
+
return ELEMENT_SET.has(localName);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/xslt/formatNumber.js
|
|
1627
|
+
var DEFAULT_DECIMAL_FORMAT = Object.freeze({
|
|
1628
|
+
decimalSeparator: ".",
|
|
1629
|
+
groupingSeparator: ",",
|
|
1630
|
+
percent: "%",
|
|
1631
|
+
perMille: "\u2030",
|
|
1632
|
+
zeroDigit: "0",
|
|
1633
|
+
digit: "#",
|
|
1634
|
+
patternSeparator: ";",
|
|
1635
|
+
infinity: "Infinity",
|
|
1636
|
+
nan: "NaN",
|
|
1637
|
+
minusSign: "-"
|
|
1638
|
+
});
|
|
1639
|
+
function splitSubPatterns(pattern, format) {
|
|
1640
|
+
const index = pattern.indexOf(format.patternSeparator);
|
|
1641
|
+
if (index === -1) return { positive: pattern, negative: null };
|
|
1642
|
+
return {
|
|
1643
|
+
positive: pattern.substring(0, index),
|
|
1644
|
+
negative: pattern.substring(index + format.patternSeparator.length)
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function parseSubPattern(subPattern, format) {
|
|
1648
|
+
const special = /* @__PURE__ */ new Set([
|
|
1649
|
+
format.digit,
|
|
1650
|
+
format.zeroDigit,
|
|
1651
|
+
format.groupingSeparator,
|
|
1652
|
+
format.decimalSeparator
|
|
1653
|
+
]);
|
|
1654
|
+
let start = 0;
|
|
1655
|
+
while (start < subPattern.length && !special.has(subPattern[start])) start++;
|
|
1656
|
+
let end = start;
|
|
1657
|
+
while (end < subPattern.length && special.has(subPattern[end])) end++;
|
|
1658
|
+
const prefix = subPattern.substring(0, start);
|
|
1659
|
+
const numeric = subPattern.substring(start, end);
|
|
1660
|
+
const suffix = subPattern.substring(end);
|
|
1661
|
+
const decimalIndex = numeric.indexOf(format.decimalSeparator);
|
|
1662
|
+
const integerPart = decimalIndex === -1 ? numeric : numeric.substring(0, decimalIndex);
|
|
1663
|
+
const fractionPart = decimalIndex === -1 ? "" : numeric.substring(decimalIndex + 1);
|
|
1664
|
+
const groupingIndex = integerPart.lastIndexOf(format.groupingSeparator);
|
|
1665
|
+
const affixes = prefix + suffix;
|
|
1666
|
+
let multiplier = 1;
|
|
1667
|
+
if (affixes.includes(format.percent)) multiplier = 100;
|
|
1668
|
+
else if (affixes.includes(format.perMille)) multiplier = 1e3;
|
|
1669
|
+
return {
|
|
1670
|
+
prefix,
|
|
1671
|
+
suffix,
|
|
1672
|
+
multiplier,
|
|
1673
|
+
minInteger: countOccurrences(integerPart, format.zeroDigit),
|
|
1674
|
+
minFraction: countOccurrences(fractionPart, format.zeroDigit),
|
|
1675
|
+
maxFraction: Math.min(fractionPart.length, 100),
|
|
1676
|
+
groupingSize: groupingIndex === -1 ? 0 : integerPart.length - groupingIndex - 1
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function countOccurrences(text, char) {
|
|
1680
|
+
let total = 0;
|
|
1681
|
+
for (const current of text) {
|
|
1682
|
+
if (current === char) total++;
|
|
1683
|
+
}
|
|
1684
|
+
return total;
|
|
1685
|
+
}
|
|
1686
|
+
function applyGrouping(digits, size, separator) {
|
|
1687
|
+
if (size <= 0 || digits.length <= size) return digits;
|
|
1688
|
+
let result = "";
|
|
1689
|
+
for (let i = 0; i < digits.length; i++) {
|
|
1690
|
+
const fromEnd = digits.length - i;
|
|
1691
|
+
if (i > 0 && fromEnd % size === 0) result += separator;
|
|
1692
|
+
result += digits[i];
|
|
1693
|
+
}
|
|
1694
|
+
return result;
|
|
1695
|
+
}
|
|
1696
|
+
function translateDigits(text, zeroDigit) {
|
|
1697
|
+
const offset = zeroDigit.codePointAt(0) - 48;
|
|
1698
|
+
if (offset === 0) return text;
|
|
1699
|
+
return text.replaceAll(
|
|
1700
|
+
/\d/g,
|
|
1701
|
+
(digit) => String.fromCodePoint(digit.codePointAt(0) + offset)
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
function formatMagnitude(magnitude, spec, format) {
|
|
1705
|
+
const fixed = magnitude.toFixed(spec.maxFraction);
|
|
1706
|
+
const [rawInteger, rawFraction = ""] = fixed.split(".");
|
|
1707
|
+
let fraction = rawFraction;
|
|
1708
|
+
while (fraction.length > spec.minFraction && fraction.endsWith("0")) {
|
|
1709
|
+
fraction = fraction.slice(0, -1);
|
|
1710
|
+
}
|
|
1711
|
+
let integer = rawInteger.padStart(spec.minInteger, "0");
|
|
1712
|
+
if (spec.minInteger === 0 && integer === "0" && fraction.length > 0) {
|
|
1713
|
+
integer = "";
|
|
1714
|
+
}
|
|
1715
|
+
integer = applyGrouping(integer, spec.groupingSize, format.groupingSeparator);
|
|
1716
|
+
const body = fraction.length > 0 ? integer + format.decimalSeparator + fraction : integer;
|
|
1717
|
+
return translateDigits(body, format.zeroDigit);
|
|
1718
|
+
}
|
|
1719
|
+
function formatNumber(value, pattern, decimalFormat = DEFAULT_DECIMAL_FORMAT) {
|
|
1720
|
+
const format = { ...DEFAULT_DECIMAL_FORMAT, ...decimalFormat };
|
|
1721
|
+
if (typeof value !== "number" || Number.isNaN(value)) return format.nan;
|
|
1722
|
+
const subPatterns = splitSubPatterns(pattern, format);
|
|
1723
|
+
const positive = parseSubPattern(subPatterns.positive, format);
|
|
1724
|
+
const isNegative = value < 0;
|
|
1725
|
+
let spec = positive;
|
|
1726
|
+
let prefix = positive.prefix;
|
|
1727
|
+
let suffix = positive.suffix;
|
|
1728
|
+
if (isNegative) {
|
|
1729
|
+
if (subPatterns.negative !== null) {
|
|
1730
|
+
spec = parseSubPattern(subPatterns.negative, format);
|
|
1731
|
+
prefix = spec.prefix;
|
|
1732
|
+
suffix = spec.suffix;
|
|
1733
|
+
} else {
|
|
1734
|
+
prefix = format.minusSign + positive.prefix;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
const magnitude = Math.abs(value) * spec.multiplier;
|
|
1738
|
+
const body = Number.isFinite(magnitude) ? formatMagnitude(magnitude, spec, format) : format.infinity;
|
|
1739
|
+
return prefix + body + suffix;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/xslt/functions.js
|
|
1743
|
+
var VENDOR = "@tradik/xslt-processor";
|
|
1744
|
+
var VENDOR_URL = "https://github.com/spagu/XSLT-Processor";
|
|
1745
|
+
var SYSTEM_PROPERTIES = Object.freeze({
|
|
1746
|
+
"xsl:version": "1",
|
|
1747
|
+
"xsl:vendor": VENDOR,
|
|
1748
|
+
"xsl:vendor-url": VENDOR_URL
|
|
1749
|
+
});
|
|
1750
|
+
function ownerDocumentOf(node) {
|
|
1751
|
+
return node.ownerDocument || node;
|
|
1752
|
+
}
|
|
1753
|
+
function toStringList(evaluator, stringify, value) {
|
|
1754
|
+
if (Array.isArray(value)) {
|
|
1755
|
+
return value.map((node) => evaluator.getStringValue(node));
|
|
1756
|
+
}
|
|
1757
|
+
return [stringify(value)];
|
|
1758
|
+
}
|
|
1759
|
+
function splitQName(qname) {
|
|
1760
|
+
const colon = qname.indexOf(":");
|
|
1761
|
+
if (colon === -1) return { prefix: null, localName: qname };
|
|
1762
|
+
return {
|
|
1763
|
+
prefix: qname.substring(0, colon),
|
|
1764
|
+
localName: qname.substring(colon + 1)
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
function createXsltFunctions(engine) {
|
|
1768
|
+
const evaluator = engine.xpathEvaluator;
|
|
1769
|
+
const stringify = evaluator.toString.bind(evaluator);
|
|
1770
|
+
const evaluate2 = (arg, ctx) => evaluator.evaluate(arg, ctx);
|
|
1771
|
+
const asString = (arg, ctx) => stringify(evaluate2(arg, ctx));
|
|
1772
|
+
return {
|
|
1773
|
+
/**
|
|
1774
|
+
* `document(object, base?)` - load external XML documents.
|
|
1775
|
+
*
|
|
1776
|
+
* An empty URI denotes the stylesheet itself. Without a document loader, or
|
|
1777
|
+
* when the loader returns null, the result is an empty node-set. The
|
|
1778
|
+
* optional second argument is read as a base URI string.
|
|
1779
|
+
*/
|
|
1780
|
+
document: (args, ctx) => {
|
|
1781
|
+
const baseUri = args.length > 1 ? asString(args[1], ctx) : engine.baseUri;
|
|
1782
|
+
const uris = toStringList(evaluator, stringify, evaluate2(args[0], ctx));
|
|
1783
|
+
const result = [];
|
|
1784
|
+
for (const uri of uris) {
|
|
1785
|
+
const doc = engine.loadDocument(uri, baseUri || engine.baseUri);
|
|
1786
|
+
if (doc && !result.includes(doc)) result.push(doc);
|
|
1787
|
+
}
|
|
1788
|
+
return result;
|
|
1789
|
+
},
|
|
1790
|
+
/** `key(name, value)` - look up nodes through an `xsl:key` index. */
|
|
1791
|
+
key: (args, ctx) => {
|
|
1792
|
+
const name = asString(args[0], ctx);
|
|
1793
|
+
const values = toStringList(evaluator, stringify, evaluate2(args[1], ctx));
|
|
1794
|
+
return engine.keyRegistry.lookup(name, values, ownerDocumentOf(ctx.node));
|
|
1795
|
+
},
|
|
1796
|
+
/** `format-number(number, pattern, decimalFormat?)`. */
|
|
1797
|
+
"format-number": (args, ctx) => {
|
|
1798
|
+
const value = evaluator.toNumber(evaluate2(args[0], ctx));
|
|
1799
|
+
const pattern = asString(args[1], ctx);
|
|
1800
|
+
const formatName = args.length > 2 ? asString(args[2], ctx) : "";
|
|
1801
|
+
const format = engine.decimalFormats[formatName] || DEFAULT_DECIMAL_FORMAT;
|
|
1802
|
+
return formatNumber(value, pattern, format);
|
|
1803
|
+
},
|
|
1804
|
+
/** `current()` - the XSLT current node, not the XPath context node. */
|
|
1805
|
+
current: (args, ctx) => {
|
|
1806
|
+
const currentNode = ctx.hostContext?.currentNode;
|
|
1807
|
+
return currentNode ? [currentNode] : [ctx.node];
|
|
1808
|
+
},
|
|
1809
|
+
/** `generate-id(node-set?)` - a stable id for the life of the transform. */
|
|
1810
|
+
"generate-id": (args, ctx) => {
|
|
1811
|
+
let node = ctx.node;
|
|
1812
|
+
if (args.length > 0) {
|
|
1813
|
+
const nodeSet = evaluate2(args[0], ctx);
|
|
1814
|
+
node = Array.isArray(nodeSet) ? nodeSet[0] : nodeSet;
|
|
1815
|
+
}
|
|
1816
|
+
return node ? engine.generateId(node) : "";
|
|
1817
|
+
},
|
|
1818
|
+
/** `system-property(name)` - XSLT version and vendor information. */
|
|
1819
|
+
"system-property": (args, ctx) => {
|
|
1820
|
+
const name = asString(args[0], ctx);
|
|
1821
|
+
return Object.hasOwn(SYSTEM_PROPERTIES, name) ? SYSTEM_PROPERTIES[name] : "";
|
|
1822
|
+
},
|
|
1823
|
+
/** `function-available(name)` - reflects the evaluator function table. */
|
|
1824
|
+
"function-available": (args, ctx) => {
|
|
1825
|
+
const name = asString(args[0], ctx);
|
|
1826
|
+
return Object.hasOwn(evaluator.functions, name);
|
|
1827
|
+
},
|
|
1828
|
+
/** `element-available(name)` - reflects the XSLT elements the engine runs. */
|
|
1829
|
+
"element-available": (args, ctx) => {
|
|
1830
|
+
const { prefix, localName } = splitQName(asString(args[0], ctx));
|
|
1831
|
+
if (!prefix) return false;
|
|
1832
|
+
const namespaceUri = ctx.namespaces[prefix] ?? (prefix === "xsl" ? XSLT_NAMESPACE : null);
|
|
1833
|
+
return namespaceUri === XSLT_NAMESPACE && isXsltElementAvailable(localName);
|
|
1834
|
+
},
|
|
1835
|
+
/**
|
|
1836
|
+
* `unparsed-entity-uri(name)` - always empty.
|
|
1837
|
+
*
|
|
1838
|
+
* Unparsed entity declarations are not exposed by the DOM, so this
|
|
1839
|
+
* processor cannot resolve them; returning the empty string keeps
|
|
1840
|
+
* stylesheets that call the function working.
|
|
1841
|
+
*/
|
|
1842
|
+
"unparsed-entity-uri": () => ""
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/xslt/keys.js
|
|
1847
|
+
var KeyIndexRegistry = class {
|
|
1848
|
+
/**
|
|
1849
|
+
* @param {Object} options - Registry configuration
|
|
1850
|
+
* @param {Object<string, {match: string, use: string}>} options.keys - Declared keys by name
|
|
1851
|
+
* @param {(node: Node, pattern: string) => boolean} options.matchesPattern - XSLT pattern matcher
|
|
1852
|
+
* @param {(node: Node, expression: string) => string[]} options.evaluateUse - `use` evaluator returning key values
|
|
1853
|
+
*/
|
|
1854
|
+
constructor({ keys, matchesPattern, evaluateUse }) {
|
|
1855
|
+
this.keys = keys;
|
|
1856
|
+
this.matchesPattern = matchesPattern;
|
|
1857
|
+
this.evaluateUse = evaluateUse;
|
|
1858
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Drop every cached index, for example after the key declarations changed.
|
|
1862
|
+
*
|
|
1863
|
+
* @returns {void}
|
|
1864
|
+
*
|
|
1865
|
+
* @example
|
|
1866
|
+
* registry.clear();
|
|
1867
|
+
*/
|
|
1868
|
+
clear() {
|
|
1869
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Look up the nodes indexed under one or more key values.
|
|
1873
|
+
*
|
|
1874
|
+
* @param {string} name - The key name
|
|
1875
|
+
* @param {string|string[]} values - One key value, or several to union
|
|
1876
|
+
* @param {Document} doc - The document to search
|
|
1877
|
+
* @returns {Node[]} Matching nodes in document order, without duplicates
|
|
1878
|
+
* @throws {Error} When the key name was never declared
|
|
1879
|
+
*
|
|
1880
|
+
* @example
|
|
1881
|
+
* registry.lookup('byId', 'a1', xmlDoc);
|
|
1882
|
+
*/
|
|
1883
|
+
lookup(name, values, doc) {
|
|
1884
|
+
if (!Object.hasOwn(this.keys, name)) {
|
|
1885
|
+
throw new Error(`Undefined key: ${name}`);
|
|
1886
|
+
}
|
|
1887
|
+
const index = this.getIndex(name, doc);
|
|
1888
|
+
const wanted = Array.isArray(values) ? values : [values];
|
|
1889
|
+
const result = [];
|
|
1890
|
+
for (const value of wanted) {
|
|
1891
|
+
for (const node of index.get(value) || []) {
|
|
1892
|
+
if (!result.includes(node)) result.push(node);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return result;
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Get (building if needed) the index of one key for one document.
|
|
1899
|
+
*
|
|
1900
|
+
* @param {string} name - The key name
|
|
1901
|
+
* @param {Document} doc - The document being indexed
|
|
1902
|
+
* @returns {Map<string, Node[]>} Key value to nodes
|
|
1903
|
+
*/
|
|
1904
|
+
getIndex(name, doc) {
|
|
1905
|
+
let byName = this.cache.get(doc);
|
|
1906
|
+
if (!byName) {
|
|
1907
|
+
byName = /* @__PURE__ */ new Map();
|
|
1908
|
+
this.cache.set(doc, byName);
|
|
1909
|
+
}
|
|
1910
|
+
let index = byName.get(name);
|
|
1911
|
+
if (!index) {
|
|
1912
|
+
index = this.buildIndex(name, doc);
|
|
1913
|
+
byName.set(name, index);
|
|
1914
|
+
}
|
|
1915
|
+
return index;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Build the index of one key for one document.
|
|
1919
|
+
*
|
|
1920
|
+
* @param {string} name - The key name
|
|
1921
|
+
* @param {Document} doc - The document being indexed
|
|
1922
|
+
* @returns {Map<string, Node[]>} Key value to nodes
|
|
1923
|
+
*/
|
|
1924
|
+
buildIndex(name, doc) {
|
|
1925
|
+
const { match, use } = this.keys[name];
|
|
1926
|
+
const index = /* @__PURE__ */ new Map();
|
|
1927
|
+
for (const node of documentOrderNodes(doc)) {
|
|
1928
|
+
if (!this.matchesPattern(node, match)) continue;
|
|
1929
|
+
for (const value of this.evaluateUse(node, use)) {
|
|
1930
|
+
const bucket = index.get(value);
|
|
1931
|
+
if (bucket) bucket.push(node);
|
|
1932
|
+
else index.set(value, [node]);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return index;
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
function* documentOrderNodes(root) {
|
|
1939
|
+
const stack = [root];
|
|
1940
|
+
while (stack.length > 0) {
|
|
1941
|
+
const current = stack.pop();
|
|
1942
|
+
yield current;
|
|
1943
|
+
if (current.nodeType === 1 && current.attributes) {
|
|
1944
|
+
for (const attribute of current.attributes) yield attribute;
|
|
1945
|
+
}
|
|
1946
|
+
const children = current.childNodes;
|
|
1947
|
+
if (children) {
|
|
1948
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// src/xslt/number.js
|
|
1954
|
+
var COUNTABLE_NODE_TYPES = /* @__PURE__ */ new Set([1, 3, 4, 7, 8]);
|
|
1955
|
+
function matchesDefaultCount(candidate, node) {
|
|
1956
|
+
if (candidate.nodeType !== node.nodeType) return false;
|
|
1957
|
+
if (candidate.nodeType === 1) return candidate.nodeName === node.nodeName;
|
|
1958
|
+
return true;
|
|
1959
|
+
}
|
|
1960
|
+
function createCountPredicate(node, count, matcher) {
|
|
1961
|
+
if (count) return (candidate) => matcher(candidate, count);
|
|
1962
|
+
return (candidate) => matchesDefaultCount(candidate, node);
|
|
1963
|
+
}
|
|
1964
|
+
function createFromPredicate(from, matcher) {
|
|
1965
|
+
if (!from) return () => false;
|
|
1966
|
+
return (candidate) => matcher(candidate, from);
|
|
1967
|
+
}
|
|
1968
|
+
function siblingPosition(node, isCounted) {
|
|
1969
|
+
let position = 1;
|
|
1970
|
+
let sibling = node.previousSibling;
|
|
1971
|
+
while (sibling) {
|
|
1972
|
+
if (COUNTABLE_NODE_TYPES.has(sibling.nodeType) && isCounted(sibling)) {
|
|
1973
|
+
position++;
|
|
1974
|
+
}
|
|
1975
|
+
sibling = sibling.previousSibling;
|
|
1976
|
+
}
|
|
1977
|
+
return position;
|
|
1978
|
+
}
|
|
1979
|
+
function nodesUpToTarget(target) {
|
|
1980
|
+
const root = target.ownerDocument || target;
|
|
1981
|
+
const result = [];
|
|
1982
|
+
const stack = [root];
|
|
1983
|
+
while (stack.length > 0) {
|
|
1984
|
+
const current = stack.pop();
|
|
1985
|
+
result.push(current);
|
|
1986
|
+
if (current === target) break;
|
|
1987
|
+
const children = current.childNodes;
|
|
1988
|
+
if (children) {
|
|
1989
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return result;
|
|
1993
|
+
}
|
|
1994
|
+
function countSingle(node, isCounted, isFrom) {
|
|
1995
|
+
let current = node;
|
|
1996
|
+
while (current && current.nodeType !== 9) {
|
|
1997
|
+
if (isFrom(current)) return [];
|
|
1998
|
+
if (isCounted(current)) return [siblingPosition(current, isCounted)];
|
|
1999
|
+
current = current.parentNode;
|
|
2000
|
+
}
|
|
2001
|
+
return [];
|
|
2002
|
+
}
|
|
2003
|
+
function countMultiple(node, isCounted, isFrom) {
|
|
2004
|
+
const numbers = [];
|
|
2005
|
+
let current = node;
|
|
2006
|
+
while (current && current.nodeType !== 9) {
|
|
2007
|
+
if (isFrom(current)) break;
|
|
2008
|
+
if (isCounted(current)) {
|
|
2009
|
+
numbers.unshift(siblingPosition(current, isCounted));
|
|
2010
|
+
}
|
|
2011
|
+
current = current.parentNode;
|
|
2012
|
+
}
|
|
2013
|
+
return numbers;
|
|
2014
|
+
}
|
|
2015
|
+
function countAny(node, isCounted, isFrom) {
|
|
2016
|
+
let total = 0;
|
|
2017
|
+
for (const candidate of nodesUpToTarget(node)) {
|
|
2018
|
+
if (!COUNTABLE_NODE_TYPES.has(candidate.nodeType)) continue;
|
|
2019
|
+
if (isFrom(candidate)) {
|
|
2020
|
+
total = 0;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
if (isCounted(candidate)) total++;
|
|
2024
|
+
}
|
|
2025
|
+
return total > 0 ? [total] : [];
|
|
2026
|
+
}
|
|
2027
|
+
function countXsltNumber(node, options, matcher) {
|
|
2028
|
+
const { level = "single", count = null, from = null } = options;
|
|
2029
|
+
const isCounted = createCountPredicate(node, count, matcher);
|
|
2030
|
+
const isFrom = createFromPredicate(from, matcher);
|
|
2031
|
+
if (level === "any") return countAny(node, isCounted, isFrom);
|
|
2032
|
+
if (level === "multiple") return countMultiple(node, isCounted, isFrom);
|
|
2033
|
+
return countSingle(node, isCounted, isFrom);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
// src/xslt/numberFormat.js
|
|
2037
|
+
function toAlphabetic(value, upperCase) {
|
|
2038
|
+
let remaining = value;
|
|
2039
|
+
let result = "";
|
|
2040
|
+
while (remaining > 0) {
|
|
2041
|
+
const index = (remaining - 1) % 26;
|
|
2042
|
+
result = String.fromCodePoint((upperCase ? 65 : 97) + index) + result;
|
|
2043
|
+
remaining = Math.floor((remaining - 1) / 26);
|
|
2044
|
+
}
|
|
2045
|
+
return result;
|
|
2046
|
+
}
|
|
2047
|
+
var ROMAN_NUMERALS = Object.freeze([
|
|
2048
|
+
["M", 1e3],
|
|
2049
|
+
["CM", 900],
|
|
2050
|
+
["D", 500],
|
|
2051
|
+
["CD", 400],
|
|
2052
|
+
["C", 100],
|
|
2053
|
+
["XC", 90],
|
|
2054
|
+
["L", 50],
|
|
2055
|
+
["XL", 40],
|
|
2056
|
+
["X", 10],
|
|
2057
|
+
["IX", 9],
|
|
2058
|
+
["V", 5],
|
|
2059
|
+
["IV", 4],
|
|
2060
|
+
["I", 1]
|
|
2061
|
+
]);
|
|
2062
|
+
function toRoman(value) {
|
|
2063
|
+
let remaining = value;
|
|
2064
|
+
let result = "";
|
|
2065
|
+
for (const [numeral, amount] of ROMAN_NUMERALS) {
|
|
2066
|
+
while (remaining >= amount) {
|
|
2067
|
+
result += numeral;
|
|
2068
|
+
remaining -= amount;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return result;
|
|
2072
|
+
}
|
|
2073
|
+
function formatToken(value, token) {
|
|
2074
|
+
if (/^\d+$/.test(token)) {
|
|
2075
|
+
return String(value).padStart(token.length, "0");
|
|
2076
|
+
}
|
|
2077
|
+
if (value <= 0) return String(value);
|
|
2078
|
+
switch (token) {
|
|
2079
|
+
case "a":
|
|
2080
|
+
return toAlphabetic(value, false);
|
|
2081
|
+
case "A":
|
|
2082
|
+
return toAlphabetic(value, true);
|
|
2083
|
+
case "i":
|
|
2084
|
+
return toRoman(value).toLowerCase();
|
|
2085
|
+
case "I":
|
|
2086
|
+
return toRoman(value);
|
|
2087
|
+
default:
|
|
2088
|
+
return String(value);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
function parseFormat(format) {
|
|
2092
|
+
const parts = format.match(/[a-zA-Z0-9]+|[^a-zA-Z0-9]+/g) || [];
|
|
2093
|
+
const isToken = (part) => /^[a-zA-Z0-9]+$/.test(part);
|
|
2094
|
+
const tokens = [];
|
|
2095
|
+
const separators = [];
|
|
2096
|
+
let prefix = "";
|
|
2097
|
+
let suffix = "";
|
|
2098
|
+
for (const part of parts) {
|
|
2099
|
+
if (isToken(part)) tokens.push(part);
|
|
2100
|
+
else if (tokens.length === 0) prefix = part;
|
|
2101
|
+
else separators.push(part);
|
|
2102
|
+
}
|
|
2103
|
+
if (parts.length > 0 && tokens.length > 0 && !isToken(parts.at(-1))) {
|
|
2104
|
+
suffix = separators.pop();
|
|
2105
|
+
}
|
|
2106
|
+
if (tokens.length === 0) tokens.push("1");
|
|
2107
|
+
return { prefix, suffix, tokens, separators };
|
|
2108
|
+
}
|
|
2109
|
+
function formatXsltNumber(numbers, format = "1") {
|
|
2110
|
+
if (numbers.length === 0) return "";
|
|
2111
|
+
const { prefix, suffix, tokens, separators } = parseFormat(format);
|
|
2112
|
+
let result = prefix;
|
|
2113
|
+
numbers.forEach((value, index) => {
|
|
2114
|
+
if (index > 0) {
|
|
2115
|
+
const separator = separators[index - 1] ?? separators.at(-1) ?? ".";
|
|
2116
|
+
result += separator;
|
|
2117
|
+
}
|
|
2118
|
+
result += formatToken(value, tokens[index] ?? tokens.at(-1));
|
|
2119
|
+
});
|
|
2120
|
+
return result + suffix;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// src/xslt/uri.js
|
|
2124
|
+
var ABSOLUTE_URI_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
2125
|
+
function resolveUri(href, baseUri) {
|
|
2126
|
+
if (!href) return href;
|
|
2127
|
+
if (!baseUri || isAbsoluteUri(href) || href.startsWith("/")) {
|
|
2128
|
+
return href;
|
|
2129
|
+
}
|
|
2130
|
+
const lastSlash = baseUri.lastIndexOf("/");
|
|
2131
|
+
const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
|
|
2132
|
+
return baseDir + href;
|
|
2133
|
+
}
|
|
2134
|
+
function isAbsoluteUri(uri) {
|
|
2135
|
+
return ABSOLUTE_URI_PATTERN.test(uri);
|
|
2136
|
+
}
|
|
2137
|
+
function stripFragment(uri) {
|
|
2138
|
+
if (typeof uri !== "string") return "";
|
|
2139
|
+
const hash = uri.indexOf("#");
|
|
2140
|
+
return hash === -1 ? uri : uri.substring(0, hash);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// src/xslt/whitespace.js
|
|
2144
|
+
var DOCUMENT_TYPE_NODE = 10;
|
|
2145
|
+
function nameTestPriority(nameTest) {
|
|
2146
|
+
if (nameTest === "*") return -0.5;
|
|
2147
|
+
if (nameTest.endsWith(":*")) return -0.25;
|
|
2148
|
+
return 0;
|
|
2149
|
+
}
|
|
2150
|
+
function matchesNameTest(element, nameTest) {
|
|
2151
|
+
if (nameTest === "*") return true;
|
|
2152
|
+
if (nameTest.endsWith(":*")) {
|
|
2153
|
+
const prefix = nameTest.slice(0, -2);
|
|
2154
|
+
return element.nodeName.startsWith(`${prefix}:`);
|
|
2155
|
+
}
|
|
2156
|
+
return element.nodeName === nameTest || element.localName === nameTest;
|
|
2157
|
+
}
|
|
2158
|
+
var WhitespaceFilter = class {
|
|
2159
|
+
/**
|
|
2160
|
+
* @param {string[]} [stripSpace] - Name tests from `xsl:strip-space`
|
|
2161
|
+
* @param {string[]} [preserveSpace] - Name tests from `xsl:preserve-space`
|
|
2162
|
+
*/
|
|
2163
|
+
constructor(stripSpace = [], preserveSpace = []) {
|
|
2164
|
+
this.stripSpace = stripSpace;
|
|
2165
|
+
this.preserveSpace = preserveSpace;
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Whether the filter can remove anything at all.
|
|
2169
|
+
*
|
|
2170
|
+
* @returns {boolean} True when at least one `xsl:strip-space` was declared
|
|
2171
|
+
*
|
|
2172
|
+
* @example
|
|
2173
|
+
* new WhitespaceFilter(['*']).isActive(); // true
|
|
2174
|
+
*/
|
|
2175
|
+
isActive() {
|
|
2176
|
+
return this.stripSpace.length > 0;
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Whether whitespace-only children of an element are stripped.
|
|
2180
|
+
*
|
|
2181
|
+
* The most specific name test wins; `xsl:preserve-space` wins ties.
|
|
2182
|
+
*
|
|
2183
|
+
* @param {Element} element - The parent element
|
|
2184
|
+
* @returns {boolean} True when whitespace-only text children are removed
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* new WhitespaceFilter(['*'], ['pre']).isStripped(preElement); // false
|
|
2188
|
+
*/
|
|
2189
|
+
isStripped(element) {
|
|
2190
|
+
let stripPriority = -Infinity;
|
|
2191
|
+
let preservePriority = -Infinity;
|
|
2192
|
+
for (const nameTest of this.stripSpace) {
|
|
2193
|
+
if (matchesNameTest(element, nameTest)) {
|
|
2194
|
+
stripPriority = Math.max(stripPriority, nameTestPriority(nameTest));
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
for (const nameTest of this.preserveSpace) {
|
|
2198
|
+
if (matchesNameTest(element, nameTest)) {
|
|
2199
|
+
preservePriority = Math.max(
|
|
2200
|
+
preservePriority,
|
|
2201
|
+
nameTestPriority(nameTest)
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
return stripPriority > -Infinity && stripPriority > preservePriority;
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
function hasXmlSpacePreserve(node) {
|
|
2209
|
+
let current = node;
|
|
2210
|
+
while (current?.nodeType === 1) {
|
|
2211
|
+
const value = current.getAttribute("xml:space");
|
|
2212
|
+
if (value === "preserve") return true;
|
|
2213
|
+
if (value === "default") return false;
|
|
2214
|
+
current = current.parentNode;
|
|
2215
|
+
}
|
|
2216
|
+
return false;
|
|
2217
|
+
}
|
|
2218
|
+
function pruneWhitespace(root, filter) {
|
|
2219
|
+
const doomed = [];
|
|
2220
|
+
const stack = [root];
|
|
2221
|
+
while (stack.length > 0) {
|
|
2222
|
+
const current = stack.pop();
|
|
2223
|
+
if ((current.nodeType === 3 || current.nodeType === 4) && current.nodeValue !== null && current.nodeValue.trim() === "" && current.parentNode?.nodeType === 1 && filter.isStripped(current.parentNode) && !hasXmlSpacePreserve(current.parentNode)) {
|
|
2224
|
+
doomed.push(current);
|
|
2225
|
+
}
|
|
2226
|
+
const children = current.childNodes;
|
|
2227
|
+
if (children) {
|
|
2228
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
for (const node of doomed) node.remove();
|
|
2232
|
+
}
|
|
2233
|
+
function stripWhitespaceNodes(sourceNode, filter, targetDoc) {
|
|
2234
|
+
let root;
|
|
2235
|
+
if (sourceNode.nodeType === 9) {
|
|
2236
|
+
root = targetDoc;
|
|
2237
|
+
for (const child of Array.from(sourceNode.childNodes)) {
|
|
2238
|
+
if (child.nodeType === DOCUMENT_TYPE_NODE) continue;
|
|
2239
|
+
targetDoc.appendChild(targetDoc.importNode(child, true));
|
|
2240
|
+
}
|
|
2241
|
+
} else {
|
|
2242
|
+
root = targetDoc.importNode(sourceNode, true);
|
|
2243
|
+
targetDoc.appendChild(root);
|
|
2244
|
+
}
|
|
2245
|
+
pruneWhitespace(root, filter);
|
|
2246
|
+
return root;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// src/xslt/literalResult.js
|
|
2250
|
+
function lookupNamespaceUri(node, prefix) {
|
|
2251
|
+
if (typeof node.lookupNamespaceURI === "function") {
|
|
2252
|
+
const found = node.lookupNamespaceURI(prefix);
|
|
2253
|
+
if (found) return found;
|
|
2254
|
+
}
|
|
2255
|
+
const attributeName = prefix ? `xmlns:${prefix}` : "xmlns";
|
|
2256
|
+
let current = node;
|
|
2257
|
+
while (current?.nodeType === 1) {
|
|
2258
|
+
const value = current.getAttribute(attributeName);
|
|
2259
|
+
if (value) return value;
|
|
2260
|
+
current = current.parentNode;
|
|
2261
|
+
}
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
var NamespaceAliasMap = class {
|
|
2265
|
+
constructor() {
|
|
2266
|
+
this.byUri = /* @__PURE__ */ new Map();
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Record one `xsl:namespace-alias` declaration.
|
|
2270
|
+
*
|
|
2271
|
+
* @param {Element} node - The `xsl:namespace-alias` element
|
|
2272
|
+
* @returns {void}
|
|
2273
|
+
*
|
|
2274
|
+
* @example
|
|
2275
|
+
* aliases.add(namespaceAliasElement);
|
|
2276
|
+
*/
|
|
2277
|
+
add(node) {
|
|
2278
|
+
const stylesheetPrefix = node.getAttribute("stylesheet-prefix");
|
|
2279
|
+
const resultPrefix = node.getAttribute("result-prefix");
|
|
2280
|
+
if (!stylesheetPrefix || !resultPrefix) return;
|
|
2281
|
+
const fromUri = lookupNamespaceUri(
|
|
2282
|
+
node,
|
|
2283
|
+
stylesheetPrefix === "#default" ? null : stylesheetPrefix
|
|
2284
|
+
);
|
|
2285
|
+
if (!fromUri) return;
|
|
2286
|
+
const isDefaultResult = resultPrefix === "#default";
|
|
2287
|
+
const toUri = lookupNamespaceUri(
|
|
2288
|
+
node,
|
|
2289
|
+
isDefaultResult ? null : resultPrefix
|
|
2290
|
+
);
|
|
2291
|
+
this.byUri.set(fromUri, {
|
|
2292
|
+
uri: toUri,
|
|
2293
|
+
prefix: isDefaultResult ? null : resultPrefix
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Whether any alias was declared.
|
|
2298
|
+
*
|
|
2299
|
+
* @returns {boolean} True when at least one alias is known
|
|
2300
|
+
*
|
|
2301
|
+
* @example
|
|
2302
|
+
* aliases.isEmpty();
|
|
2303
|
+
*/
|
|
2304
|
+
isEmpty() {
|
|
2305
|
+
return this.byUri.size === 0;
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Apply aliasing to a literal result name.
|
|
2309
|
+
*
|
|
2310
|
+
* @param {string|null} namespaceUri - The namespace of the stylesheet node
|
|
2311
|
+
* @param {string} localName - The local name of the stylesheet node
|
|
2312
|
+
* @returns {{namespaceUri: (string|null), qname: string}|null} The aliased name, or null when no alias applies
|
|
2313
|
+
*
|
|
2314
|
+
* @example
|
|
2315
|
+
* aliases.resolve('http://www.w3.org/1999/XSL/TransformAlias', 'stylesheet');
|
|
2316
|
+
* // { namespaceUri: 'http://www.w3.org/1999/XSL/Transform', qname: 'xsl:stylesheet' }
|
|
2317
|
+
*/
|
|
2318
|
+
resolve(namespaceUri, localName) {
|
|
2319
|
+
const alias = namespaceUri ? this.byUri.get(namespaceUri) : void 0;
|
|
2320
|
+
if (!alias) return null;
|
|
2321
|
+
return {
|
|
2322
|
+
namespaceUri: alias.uri,
|
|
2323
|
+
qname: alias.prefix ? `${alias.prefix}:${localName}` : localName
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
function shouldCopyAttribute(attribute, xsltNamespace) {
|
|
2328
|
+
if (attribute.namespaceURI === xsltNamespace) return false;
|
|
2329
|
+
if (attribute.name === "xmlns" || attribute.name.startsWith("xmlns:")) {
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
return !attribute.name.startsWith("xsl:");
|
|
2333
|
+
}
|
|
2334
|
+
function getXsltAttribute(node, localName, xsltNamespace) {
|
|
2335
|
+
if (!node.attributes) return null;
|
|
2336
|
+
for (const attribute of node.attributes) {
|
|
2337
|
+
const matchesNamespace = attribute.namespaceURI === xsltNamespace && (attribute.localName || attribute.name) === localName;
|
|
2338
|
+
if (matchesNamespace || attribute.name === `xsl:${localName}`) {
|
|
2339
|
+
return attribute.value;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
return null;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// src/xslt/resultTree.js
|
|
2346
|
+
function createResultDocument(ownerDocument) {
|
|
2347
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
2348
|
+
}
|
|
2349
|
+
function importResultNode(node, targetDoc) {
|
|
2350
|
+
const copy = targetDoc.importNode(node, false);
|
|
2351
|
+
if (node._disableOutputEscaping) {
|
|
2352
|
+
copy._disableOutputEscaping = true;
|
|
2353
|
+
}
|
|
2354
|
+
if (node.childNodes) {
|
|
2355
|
+
for (const child of node.childNodes) {
|
|
2356
|
+
copy.appendChild(importResultNode(child, targetDoc));
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return copy;
|
|
2360
|
+
}
|
|
2361
|
+
function importResultFragment(fragment, targetDoc) {
|
|
2362
|
+
if (fragment.ownerDocument === targetDoc) return fragment;
|
|
2363
|
+
const imported = targetDoc.createDocumentFragment();
|
|
2364
|
+
for (const child of fragment.childNodes) {
|
|
2365
|
+
imported.appendChild(importResultNode(child, targetDoc));
|
|
2366
|
+
}
|
|
2367
|
+
return imported;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// src/xslt/templatePriority.js
|
|
2371
|
+
var NAME = String.raw`[A-Za-z_][\w.-]*`;
|
|
2372
|
+
var QNAME = `(?:${NAME}:)?${NAME}`;
|
|
2373
|
+
var QNAME_PATTERN = new RegExp(`^(?:child::|attribute::|@)?${QNAME}$`);
|
|
2374
|
+
var PREFIX_WILDCARD_PATTERN = new RegExp(
|
|
2375
|
+
String.raw`^(?:child::|attribute::|@)?${NAME}:\*$`
|
|
2376
|
+
);
|
|
2377
|
+
var NODE_TEST_PATTERN = /^(?:child::|attribute::|@)?(?:\*|node\(\)|text\(\)|comment\(\)|processing-instruction\(\))$/;
|
|
2378
|
+
var PI_LITERAL_PATTERN = /^(?:child::)?processing-instruction\(\s*(?:"[^"]*"|'[^']*')\s*\)$/;
|
|
2379
|
+
function calculatePriority(pattern) {
|
|
2380
|
+
if (!pattern) return 0.5;
|
|
2381
|
+
if (NODE_TEST_PATTERN.test(pattern)) return -0.5;
|
|
2382
|
+
if (PREFIX_WILDCARD_PATTERN.test(pattern)) return -0.25;
|
|
2383
|
+
if (QNAME_PATTERN.test(pattern) || PI_LITERAL_PATTERN.test(pattern)) return 0;
|
|
2384
|
+
return 0.5;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/xslt/serializer/constants.js
|
|
2388
|
+
var NODE_TYPE = {
|
|
2389
|
+
ELEMENT: 1,
|
|
2390
|
+
TEXT: 3,
|
|
2391
|
+
CDATA_SECTION: 4,
|
|
2392
|
+
PROCESSING_INSTRUCTION: 7,
|
|
2393
|
+
COMMENT: 8,
|
|
2394
|
+
DOCUMENT: 9,
|
|
2395
|
+
DOCUMENT_FRAGMENT: 11
|
|
2396
|
+
};
|
|
2397
|
+
var XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
|
|
2398
|
+
var XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
|
|
2399
|
+
var TEXT_MODE = {
|
|
2400
|
+
ESCAPE: "escape",
|
|
2401
|
+
CDATA: "cdata",
|
|
2402
|
+
RAW: "raw"
|
|
2403
|
+
};
|
|
2404
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2405
|
+
"area",
|
|
2406
|
+
"base",
|
|
2407
|
+
"br",
|
|
2408
|
+
"col",
|
|
2409
|
+
"embed",
|
|
2410
|
+
"hr",
|
|
2411
|
+
"img",
|
|
2412
|
+
"input",
|
|
2413
|
+
"link",
|
|
2414
|
+
"meta",
|
|
2415
|
+
"param",
|
|
2416
|
+
"source",
|
|
2417
|
+
"track",
|
|
2418
|
+
"wbr"
|
|
2419
|
+
]);
|
|
2420
|
+
var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
|
|
2421
|
+
var PRESERVE_SPACE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2422
|
+
"pre",
|
|
2423
|
+
"script",
|
|
2424
|
+
"style",
|
|
2425
|
+
"textarea"
|
|
2426
|
+
]);
|
|
2427
|
+
var INDENT_UNIT = " ";
|
|
2428
|
+
|
|
2429
|
+
// src/xslt/serializer/settings.js
|
|
2430
|
+
function isYes(value) {
|
|
2431
|
+
return value === true || String(value).toLowerCase() === "yes";
|
|
2432
|
+
}
|
|
2433
|
+
function toNameSet(value) {
|
|
2434
|
+
if (Array.isArray(value)) {
|
|
2435
|
+
return new Set(value);
|
|
2436
|
+
}
|
|
2437
|
+
if (typeof value === "string") {
|
|
2438
|
+
return new Set(value.split(/\s+/).filter(Boolean));
|
|
2439
|
+
}
|
|
2440
|
+
return /* @__PURE__ */ new Set();
|
|
2441
|
+
}
|
|
2442
|
+
function findRootElement(node) {
|
|
2443
|
+
if (!node) {
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
if (node.nodeType === NODE_TYPE.ELEMENT) {
|
|
2447
|
+
return node;
|
|
2448
|
+
}
|
|
2449
|
+
for (const child of node.childNodes || []) {
|
|
2450
|
+
if (child.nodeType === NODE_TYPE.ELEMENT) {
|
|
2451
|
+
return child;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return null;
|
|
2455
|
+
}
|
|
2456
|
+
function detectOutputMethod(node) {
|
|
2457
|
+
const root = findRootElement(node);
|
|
2458
|
+
const isHtmlRoot = root && !root.namespaceURI && root.localName.toLowerCase() === "html";
|
|
2459
|
+
return isHtmlRoot ? "html" : "xml";
|
|
2460
|
+
}
|
|
2461
|
+
function resolveOutputSettings(outputSettings, node) {
|
|
2462
|
+
const raw = outputSettings || {};
|
|
2463
|
+
const declared = typeof raw.method === "string" ? raw.method.trim() : "";
|
|
2464
|
+
const method = declared && declared !== "auto" ? declared.toLowerCase() : detectOutputMethod(node);
|
|
2465
|
+
return {
|
|
2466
|
+
method,
|
|
2467
|
+
version: raw.version || "1.0",
|
|
2468
|
+
encoding: raw.encoding || "UTF-8",
|
|
2469
|
+
standalone: raw.standalone || null,
|
|
2470
|
+
indent: isYes(raw.indent),
|
|
2471
|
+
omitXmlDeclaration: isYes(raw.omitXmlDeclaration),
|
|
2472
|
+
doctypePublic: raw.doctypePublic || null,
|
|
2473
|
+
doctypeSystem: raw.doctypeSystem || null,
|
|
2474
|
+
mediaType: raw.mediaType || null,
|
|
2475
|
+
cdataSectionElements: toNameSet(raw.cdataSectionElements)
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/xslt/serializer/escape.js
|
|
2480
|
+
var XML_TEXT_ESCAPES = { "&": "&", "<": "<" };
|
|
2481
|
+
var HTML_TEXT_ESCAPES = { "&": "&", "<": "<", ">": ">" };
|
|
2482
|
+
var XML_ATTRIBUTE_ESCAPES = {
|
|
2483
|
+
"&": "&",
|
|
2484
|
+
"<": "<",
|
|
2485
|
+
">": ">",
|
|
2486
|
+
'"': """,
|
|
2487
|
+
" ": "	",
|
|
2488
|
+
"\n": " ",
|
|
2489
|
+
"\r": " "
|
|
2490
|
+
};
|
|
2491
|
+
var HTML_ATTRIBUTE_ESCAPES = {
|
|
2492
|
+
"&": "&",
|
|
2493
|
+
"<": "<",
|
|
2494
|
+
">": ">",
|
|
2495
|
+
'"': """
|
|
2496
|
+
};
|
|
2497
|
+
function escapeWith(value, pattern, escapes) {
|
|
2498
|
+
return String(value).replaceAll(pattern, (character) => escapes[character]);
|
|
2499
|
+
}
|
|
2500
|
+
function escapeXmlText(value) {
|
|
2501
|
+
return escapeWith(value, /[&<]/g, XML_TEXT_ESCAPES).replaceAll(
|
|
2502
|
+
"]]>",
|
|
2503
|
+
"]]>"
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
function escapeXmlAttribute(value) {
|
|
2507
|
+
return escapeWith(value, /[&<>"\t\n\r]/g, XML_ATTRIBUTE_ESCAPES);
|
|
2508
|
+
}
|
|
2509
|
+
function escapeHtmlText(value) {
|
|
2510
|
+
return escapeWith(value, /[&<>]/g, HTML_TEXT_ESCAPES);
|
|
2511
|
+
}
|
|
2512
|
+
function escapeHtmlAttribute(value) {
|
|
2513
|
+
return escapeWith(value, /[&<>"]/g, HTML_ATTRIBUTE_ESCAPES);
|
|
2514
|
+
}
|
|
2515
|
+
function wrapCdata(value) {
|
|
2516
|
+
return `<![CDATA[${String(value).replaceAll("]]>", "]]]]><![CDATA[>")}]]>`;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/xslt/serializer/namespaces.js
|
|
2520
|
+
function createNamespaceScope() {
|
|
2521
|
+
return /* @__PURE__ */ new Map([
|
|
2522
|
+
["", ""],
|
|
2523
|
+
["xml", XML_NAMESPACE]
|
|
2524
|
+
]);
|
|
2525
|
+
}
|
|
2526
|
+
function collectNamespaceDeclarations(element, scope) {
|
|
2527
|
+
const declarations = [];
|
|
2528
|
+
let next = scope;
|
|
2529
|
+
const declare = (prefix, uri) => {
|
|
2530
|
+
if (next.get(prefix) === uri) {
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
if (next === scope) {
|
|
2534
|
+
next = new Map(scope);
|
|
2535
|
+
}
|
|
2536
|
+
next.set(prefix, uri);
|
|
2537
|
+
declarations.push({ prefix, uri });
|
|
2538
|
+
};
|
|
2539
|
+
declare(element.prefix || "", element.namespaceURI || "");
|
|
2540
|
+
const attributes = Array.from(element.attributes || []);
|
|
2541
|
+
for (const attribute of attributes) {
|
|
2542
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE && attribute.prefix) {
|
|
2543
|
+
declare(attribute.prefix, attribute.namespaceURI || "");
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
for (const attribute of attributes) {
|
|
2547
|
+
if (attribute.namespaceURI === XMLNS_NAMESPACE) {
|
|
2548
|
+
declare(attribute.prefix ? attribute.localName : "", attribute.value);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
return { declarations, scope: next };
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// src/xslt/serializer/indent.js
|
|
2555
|
+
function isWhitespaceOnlyText(node) {
|
|
2556
|
+
return !/\S/.test(node.nodeValue || "");
|
|
2557
|
+
}
|
|
2558
|
+
function getIndentableChildren(element) {
|
|
2559
|
+
const indentable = [];
|
|
2560
|
+
for (const child of element.childNodes) {
|
|
2561
|
+
if (child.nodeType === NODE_TYPE.TEXT) {
|
|
2562
|
+
if (isWhitespaceOnlyText(child)) {
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
if (child.nodeType !== NODE_TYPE.ELEMENT && child.nodeType !== NODE_TYPE.COMMENT && child.nodeType !== NODE_TYPE.PROCESSING_INSTRUCTION) {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
indentable.push(child);
|
|
2571
|
+
}
|
|
2572
|
+
return indentable.length > 0 ? indentable : null;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// src/xslt/serializer/rawText.js
|
|
2576
|
+
var rawTextNodes = /* @__PURE__ */ new WeakSet();
|
|
2577
|
+
function markRawText(node) {
|
|
2578
|
+
if (node) {
|
|
2579
|
+
rawTextNodes.add(node);
|
|
2580
|
+
}
|
|
2581
|
+
return node;
|
|
2582
|
+
}
|
|
2583
|
+
function isRawText(node) {
|
|
2584
|
+
if (!node) {
|
|
2585
|
+
return false;
|
|
2586
|
+
}
|
|
2587
|
+
return rawTextNodes.has(node) || node._disableOutputEscaping === true;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// src/xslt/serializer/baseWriter.js
|
|
2591
|
+
var BaseWriter = class {
|
|
2592
|
+
/**
|
|
2593
|
+
* @param {object} settings - Normalized output settings
|
|
2594
|
+
* @param {{xhtml?: boolean}} [options] - Dialect options
|
|
2595
|
+
*/
|
|
2596
|
+
constructor(settings, options = {}) {
|
|
2597
|
+
this.settings = settings;
|
|
2598
|
+
this.xhtml = options.xhtml === true;
|
|
2599
|
+
this.parts = [];
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Serialize a result tree node.
|
|
2603
|
+
*
|
|
2604
|
+
* @param {Node} node - Document, fragment or element to serialize
|
|
2605
|
+
* @returns {string} Serialized output
|
|
2606
|
+
*/
|
|
2607
|
+
serialize(node) {
|
|
2608
|
+
this.parts = [];
|
|
2609
|
+
this.writeProlog(node);
|
|
2610
|
+
this.writeNode(node, createNamespaceScope(), 0, TEXT_MODE.ESCAPE);
|
|
2611
|
+
return this.parts.join("");
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Write the XML declaration and the document type declaration.
|
|
2615
|
+
*
|
|
2616
|
+
* @param {Node} node - Result tree root
|
|
2617
|
+
* @returns {void}
|
|
2618
|
+
*/
|
|
2619
|
+
writeProlog(node) {
|
|
2620
|
+
if (this.emitsXmlDeclaration) {
|
|
2621
|
+
const { version, encoding, standalone } = this.settings;
|
|
2622
|
+
const standalonePart = standalone ? ` standalone="${standalone}"` : "";
|
|
2623
|
+
this.parts.push(
|
|
2624
|
+
`<?xml version="${version}" encoding="${encoding}"${standalonePart}?>
|
|
2625
|
+
`
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
const doctype = this.doctypeMarkup(findRootElement(node));
|
|
2629
|
+
if (doctype) {
|
|
2630
|
+
this.parts.push(`${doctype}
|
|
2631
|
+
`);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Write any result tree node.
|
|
2636
|
+
*
|
|
2637
|
+
* @param {Node} node - Node to write
|
|
2638
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2639
|
+
* @param {number} depth - Current indentation depth
|
|
2640
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2641
|
+
* @returns {void}
|
|
2642
|
+
*/
|
|
2643
|
+
writeNode(node, scope, depth, textMode) {
|
|
2644
|
+
switch (node.nodeType) {
|
|
2645
|
+
case NODE_TYPE.ELEMENT:
|
|
2646
|
+
this.writeElement(node, scope, depth);
|
|
2647
|
+
break;
|
|
2648
|
+
case NODE_TYPE.TEXT:
|
|
2649
|
+
case NODE_TYPE.CDATA_SECTION:
|
|
2650
|
+
this.writeText(node, textMode);
|
|
2651
|
+
break;
|
|
2652
|
+
case NODE_TYPE.COMMENT:
|
|
2653
|
+
this.parts.push(`<!--${node.nodeValue}-->`);
|
|
2654
|
+
break;
|
|
2655
|
+
case NODE_TYPE.PROCESSING_INSTRUCTION:
|
|
2656
|
+
this.writeProcessingInstruction(node);
|
|
2657
|
+
break;
|
|
2658
|
+
case NODE_TYPE.DOCUMENT:
|
|
2659
|
+
case NODE_TYPE.DOCUMENT_FRAGMENT:
|
|
2660
|
+
this.writeChildNodes(node, scope, depth, textMode);
|
|
2661
|
+
break;
|
|
2662
|
+
default:
|
|
2663
|
+
break;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Write every child of a node without adding whitespace.
|
|
2668
|
+
*
|
|
2669
|
+
* @param {Node} node - Parent node
|
|
2670
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2671
|
+
* @param {number} depth - Current indentation depth
|
|
2672
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2673
|
+
* @returns {void}
|
|
2674
|
+
*/
|
|
2675
|
+
writeChildNodes(node, scope, depth, textMode) {
|
|
2676
|
+
for (const child of node.childNodes) {
|
|
2677
|
+
this.writeNode(child, scope, depth, textMode);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Write an element with its namespaces, attributes and children.
|
|
2682
|
+
*
|
|
2683
|
+
* @param {Element} element - Element to write
|
|
2684
|
+
* @param {Map<string, string>} scope - Namespace scope inherited from the parent
|
|
2685
|
+
* @param {number} depth - Current indentation depth
|
|
2686
|
+
* @returns {void}
|
|
2687
|
+
*/
|
|
2688
|
+
writeElement(element, scope, depth) {
|
|
2689
|
+
const namespaces = this.emitsNamespaces ? collectNamespaceDeclarations(element, scope) : { declarations: [], scope };
|
|
2690
|
+
const name = element.nodeName;
|
|
2691
|
+
this.parts.push(
|
|
2692
|
+
`<${name}${this.namespaceMarkup(namespaces.declarations)}` + this.attributesMarkup(element)
|
|
2693
|
+
);
|
|
2694
|
+
if (!element.firstChild) {
|
|
2695
|
+
this.parts.push(this.emptyElementMarkup(element, name));
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
this.parts.push(">");
|
|
2699
|
+
this.writeElementChildren(element, namespaces.scope, depth);
|
|
2700
|
+
this.parts.push(`</${name}>`);
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Write the children of an element, indenting element-only content.
|
|
2704
|
+
*
|
|
2705
|
+
* @param {Element} element - Parent element
|
|
2706
|
+
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
2707
|
+
* @param {number} depth - Depth of the parent element
|
|
2708
|
+
* @returns {void}
|
|
2709
|
+
*/
|
|
2710
|
+
writeElementChildren(element, scope, depth) {
|
|
2711
|
+
const textMode = this.childTextMode(element);
|
|
2712
|
+
const indentable = this.indentableChildren(element, textMode);
|
|
2713
|
+
if (!indentable) {
|
|
2714
|
+
this.writeChildNodes(element, scope, depth, textMode);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
const childIndent = `
|
|
2718
|
+
${INDENT_UNIT.repeat(depth + 1)}`;
|
|
2719
|
+
for (const child of indentable) {
|
|
2720
|
+
this.parts.push(childIndent);
|
|
2721
|
+
this.writeNode(child, scope, depth + 1, textMode);
|
|
2722
|
+
}
|
|
2723
|
+
this.parts.push(`
|
|
2724
|
+
${INDENT_UNIT.repeat(depth)}`);
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Determine the children to indent inside an element.
|
|
2728
|
+
*
|
|
2729
|
+
* @param {Element} element - Parent element
|
|
2730
|
+
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
2731
|
+
* @returns {Node[]|null} Children to indent, or null when indenting is off
|
|
2732
|
+
*/
|
|
2733
|
+
indentableChildren(element, textMode) {
|
|
2734
|
+
if (!this.settings.indent || textMode !== TEXT_MODE.ESCAPE) {
|
|
2735
|
+
return null;
|
|
2736
|
+
}
|
|
2737
|
+
if (!this.allowsIndentInside(element)) {
|
|
2738
|
+
return null;
|
|
2739
|
+
}
|
|
2740
|
+
return getIndentableChildren(element);
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Build the namespace declaration markup of an element.
|
|
2744
|
+
*
|
|
2745
|
+
* @param {Array<{prefix: string, uri: string}>} declarations - Declarations
|
|
2746
|
+
* @returns {string} Attribute markup, starting with a space when non-empty
|
|
2747
|
+
*/
|
|
2748
|
+
namespaceMarkup(declarations) {
|
|
2749
|
+
return declarations.map(({ prefix, uri }) => {
|
|
2750
|
+
const name = prefix ? `xmlns:${prefix}` : "xmlns";
|
|
2751
|
+
return ` ${name}="${this.escapeAttribute(uri)}"`;
|
|
2752
|
+
}).join("");
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Build the attribute markup of an element, skipping namespace declarations.
|
|
2756
|
+
*
|
|
2757
|
+
* @param {Element} element - Element being written
|
|
2758
|
+
* @returns {string} Attribute markup, starting with a space when non-empty
|
|
2759
|
+
*/
|
|
2760
|
+
attributesMarkup(element) {
|
|
2761
|
+
let markup = "";
|
|
2762
|
+
for (const attribute of Array.from(element.attributes || [])) {
|
|
2763
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE) {
|
|
2764
|
+
markup += this.attributeMarkup(attribute);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return markup;
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Build the markup of a single attribute.
|
|
2771
|
+
*
|
|
2772
|
+
* @param {Attr} attribute - Attribute to write
|
|
2773
|
+
* @returns {string} Attribute markup, starting with a space
|
|
2774
|
+
*/
|
|
2775
|
+
attributeMarkup(attribute) {
|
|
2776
|
+
return ` ${attribute.name}="${this.escapeAttribute(attribute.value)}"`;
|
|
2777
|
+
}
|
|
2778
|
+
/**
|
|
2779
|
+
* Write a character data node.
|
|
2780
|
+
*
|
|
2781
|
+
* Nodes produced with `disable-output-escaping="yes"` are written verbatim.
|
|
2782
|
+
*
|
|
2783
|
+
* @param {Node} node - Text or CDATA section node
|
|
2784
|
+
* @param {string} textMode - {@link TEXT_MODE} requested by the parent
|
|
2785
|
+
* @returns {void}
|
|
2786
|
+
*/
|
|
2787
|
+
writeText(node, textMode) {
|
|
2788
|
+
const value = node.nodeValue || "";
|
|
2789
|
+
if (isRawText(node)) {
|
|
2790
|
+
this.parts.push(value);
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
const mode = this.resolveTextMode(node, textMode);
|
|
2794
|
+
if (mode === TEXT_MODE.CDATA) {
|
|
2795
|
+
this.parts.push(wrapCdata(value));
|
|
2796
|
+
} else if (mode === TEXT_MODE.RAW) {
|
|
2797
|
+
this.parts.push(value);
|
|
2798
|
+
} else {
|
|
2799
|
+
this.parts.push(this.escapeText(value));
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* Resolve the effective text mode of a character data node.
|
|
2804
|
+
*
|
|
2805
|
+
* @param {Node} node - Text or CDATA section node
|
|
2806
|
+
* @param {string} textMode - {@link TEXT_MODE} requested by the parent
|
|
2807
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2808
|
+
*/
|
|
2809
|
+
resolveTextMode(node, textMode) {
|
|
2810
|
+
if (textMode !== TEXT_MODE.ESCAPE) {
|
|
2811
|
+
return textMode;
|
|
2812
|
+
}
|
|
2813
|
+
return node.nodeType === NODE_TYPE.CDATA_SECTION ? this.cdataNodeMode : TEXT_MODE.ESCAPE;
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Write a processing instruction node.
|
|
2817
|
+
*
|
|
2818
|
+
* @param {ProcessingInstruction} node - Node to write
|
|
2819
|
+
* @returns {void}
|
|
2820
|
+
*/
|
|
2821
|
+
writeProcessingInstruction(node) {
|
|
2822
|
+
const data = node.nodeValue || "";
|
|
2823
|
+
const separator = data ? " " : "";
|
|
2824
|
+
this.parts.push(`<?${node.target}${separator}${data}${this.piTerminator}`);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
// src/xslt/serializer/xmlSerializer.js
|
|
2829
|
+
var XmlWriter = class extends BaseWriter {
|
|
2830
|
+
/**
|
|
2831
|
+
* Whether an XML declaration has to be written.
|
|
2832
|
+
* @returns {boolean} True when the declaration is not omitted
|
|
2833
|
+
*/
|
|
2834
|
+
get emitsXmlDeclaration() {
|
|
2835
|
+
return !this.settings.omitXmlDeclaration;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Whether namespace declarations have to be written.
|
|
2839
|
+
* @returns {boolean} Always true for XML output
|
|
2840
|
+
*/
|
|
2841
|
+
get emitsNamespaces() {
|
|
2842
|
+
return true;
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Terminator of a processing instruction.
|
|
2846
|
+
* @returns {string} The XML processing instruction terminator
|
|
2847
|
+
*/
|
|
2848
|
+
get piTerminator() {
|
|
2849
|
+
return "?>";
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* How a source CDATA section node has to be written.
|
|
2853
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2854
|
+
*/
|
|
2855
|
+
get cdataNodeMode() {
|
|
2856
|
+
return TEXT_MODE.CDATA;
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Build the document type declaration for the xml output method.
|
|
2860
|
+
*
|
|
2861
|
+
* @param {Element|null} rootElement - Result document element
|
|
2862
|
+
* @returns {string} Doctype markup, or an empty string when not applicable
|
|
2863
|
+
*/
|
|
2864
|
+
doctypeMarkup(rootElement) {
|
|
2865
|
+
const { doctypePublic, doctypeSystem } = this.settings;
|
|
2866
|
+
if (!rootElement || !doctypeSystem) {
|
|
2867
|
+
return "";
|
|
2868
|
+
}
|
|
2869
|
+
const name = rootElement.nodeName;
|
|
2870
|
+
return doctypePublic ? `<!DOCTYPE ${name} PUBLIC "${doctypePublic}" "${doctypeSystem}">` : `<!DOCTYPE ${name} SYSTEM "${doctypeSystem}">`;
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Determine how the character data children of an element are written.
|
|
2874
|
+
*
|
|
2875
|
+
* @param {Element} element - Parent element
|
|
2876
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2877
|
+
*/
|
|
2878
|
+
childTextMode(element) {
|
|
2879
|
+
const names = this.settings.cdataSectionElements;
|
|
2880
|
+
return names.has(element.nodeName) || names.has(element.localName) ? TEXT_MODE.CDATA : TEXT_MODE.ESCAPE;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Whether the content of an element may be re-indented.
|
|
2884
|
+
*
|
|
2885
|
+
* @param {Element} _element - Element being inspected
|
|
2886
|
+
* @returns {boolean} Always true for XML output
|
|
2887
|
+
*/
|
|
2888
|
+
allowsIndentInside(_element) {
|
|
2889
|
+
return true;
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Build the markup closing an element that has no children.
|
|
2893
|
+
*
|
|
2894
|
+
* @param {Element} element - Empty element
|
|
2895
|
+
* @param {string} _name - Element name as written
|
|
2896
|
+
* @returns {string} Markup terminating the start tag
|
|
2897
|
+
*/
|
|
2898
|
+
emptyElementMarkup(element, _name) {
|
|
2899
|
+
return this.xhtml && this.isVoidElement(element) ? " />" : "/>";
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Test whether an element is an HTML void element.
|
|
2903
|
+
*
|
|
2904
|
+
* @param {Element} element - Element to test
|
|
2905
|
+
* @returns {boolean} True for void elements such as `br`
|
|
2906
|
+
*/
|
|
2907
|
+
isVoidElement(element) {
|
|
2908
|
+
return VOID_ELEMENTS.has(String(element.localName).toLowerCase());
|
|
2909
|
+
}
|
|
2910
|
+
/**
|
|
2911
|
+
* Escape character data.
|
|
2912
|
+
*
|
|
2913
|
+
* @param {string} value - Text content
|
|
2914
|
+
* @returns {string} Escaped text
|
|
2915
|
+
*/
|
|
2916
|
+
escapeText(value) {
|
|
2917
|
+
return escapeXmlText(value);
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Escape an attribute value.
|
|
2921
|
+
*
|
|
2922
|
+
* @param {string} value - Attribute value
|
|
2923
|
+
* @returns {string} Escaped value
|
|
2924
|
+
*/
|
|
2925
|
+
escapeAttribute(value) {
|
|
2926
|
+
return escapeXmlAttribute(value);
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
|
|
2930
|
+
// src/xslt/serializer/htmlSerializer.js
|
|
2931
|
+
var HtmlWriter = class extends XmlWriter {
|
|
2932
|
+
/**
|
|
2933
|
+
* The html output method never writes an XML declaration.
|
|
2934
|
+
* @returns {boolean} Always false
|
|
2935
|
+
*/
|
|
2936
|
+
get emitsXmlDeclaration() {
|
|
2937
|
+
return false;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* The html output method never writes namespace declarations.
|
|
2941
|
+
* @returns {boolean} Always false
|
|
2942
|
+
*/
|
|
2943
|
+
get emitsNamespaces() {
|
|
2944
|
+
return false;
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* HTML processing instructions are terminated by `>` alone.
|
|
2948
|
+
* @returns {string} The HTML processing instruction terminator
|
|
2949
|
+
*/
|
|
2950
|
+
get piTerminator() {
|
|
2951
|
+
return ">";
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* HTML has no CDATA sections, so such nodes are escaped as text.
|
|
2955
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2956
|
+
*/
|
|
2957
|
+
get cdataNodeMode() {
|
|
2958
|
+
return TEXT_MODE.ESCAPE;
|
|
2959
|
+
}
|
|
2960
|
+
/**
|
|
2961
|
+
* Build the document type declaration for the html output method.
|
|
2962
|
+
*
|
|
2963
|
+
* @param {Element|null} rootElement - Result document element
|
|
2964
|
+
* @returns {string} Doctype markup, or an empty string when not applicable
|
|
2965
|
+
*/
|
|
2966
|
+
doctypeMarkup(rootElement) {
|
|
2967
|
+
const { doctypePublic, doctypeSystem } = this.settings;
|
|
2968
|
+
if (!doctypePublic && !doctypeSystem) {
|
|
2969
|
+
return "";
|
|
2970
|
+
}
|
|
2971
|
+
const name = rootElement ? rootElement.nodeName : "html";
|
|
2972
|
+
if (doctypePublic && doctypeSystem) {
|
|
2973
|
+
return `<!DOCTYPE ${name} PUBLIC "${doctypePublic}" "${doctypeSystem}">`;
|
|
2974
|
+
}
|
|
2975
|
+
if (doctypePublic) {
|
|
2976
|
+
return `<!DOCTYPE ${name} PUBLIC "${doctypePublic}">`;
|
|
2977
|
+
}
|
|
2978
|
+
return `<!DOCTYPE ${name} SYSTEM "${doctypeSystem}">`;
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Script and style content is written verbatim.
|
|
2982
|
+
*
|
|
2983
|
+
* @param {Element} element - Parent element
|
|
2984
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
2985
|
+
*/
|
|
2986
|
+
childTextMode(element) {
|
|
2987
|
+
return RAW_TEXT_ELEMENTS.has(String(element.localName).toLowerCase()) ? TEXT_MODE.RAW : TEXT_MODE.ESCAPE;
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* Content of `pre`, `script`, `style` and `textarea` is never re-indented.
|
|
2991
|
+
*
|
|
2992
|
+
* @param {Element} element - Element being inspected
|
|
2993
|
+
* @returns {boolean} True when the content may be indented
|
|
2994
|
+
*/
|
|
2995
|
+
allowsIndentInside(element) {
|
|
2996
|
+
return !PRESERVE_SPACE_ELEMENTS.has(
|
|
2997
|
+
String(element.localName).toLowerCase()
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Void elements have no end tag; every other element gets one.
|
|
3002
|
+
*
|
|
3003
|
+
* @param {Element} element - Empty element
|
|
3004
|
+
* @param {string} name - Element name as written
|
|
3005
|
+
* @returns {string} Markup terminating the start tag
|
|
3006
|
+
*/
|
|
3007
|
+
emptyElementMarkup(element, name) {
|
|
3008
|
+
return this.isVoidElement(element) ? ">" : `></${name}>`;
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* Boolean attributes are minimized to their name alone.
|
|
3012
|
+
*
|
|
3013
|
+
* @param {Attr} attribute - Attribute to write
|
|
3014
|
+
* @returns {string} Attribute markup, starting with a space
|
|
3015
|
+
*/
|
|
3016
|
+
attributeMarkup(attribute) {
|
|
3017
|
+
const { name, value } = attribute;
|
|
3018
|
+
if (String(value).toLowerCase() === name.toLowerCase()) {
|
|
3019
|
+
return ` ${name}`;
|
|
3020
|
+
}
|
|
3021
|
+
return ` ${name}="${this.escapeAttribute(value)}"`;
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Escape character data for HTML.
|
|
3025
|
+
*
|
|
3026
|
+
* @param {string} value - Text content
|
|
3027
|
+
* @returns {string} Escaped text
|
|
3028
|
+
*/
|
|
3029
|
+
escapeText(value) {
|
|
3030
|
+
return escapeHtmlText(value);
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* Escape an attribute value for HTML.
|
|
3034
|
+
*
|
|
3035
|
+
* @param {string} value - Attribute value
|
|
3036
|
+
* @returns {string} Escaped value
|
|
3037
|
+
*/
|
|
3038
|
+
escapeAttribute(value) {
|
|
3039
|
+
return escapeHtmlAttribute(value);
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// src/xslt/serializer/textSerializer.js
|
|
3044
|
+
function serializeText(node) {
|
|
3045
|
+
if (node.nodeType === NODE_TYPE.TEXT || node.nodeType === NODE_TYPE.CDATA_SECTION) {
|
|
3046
|
+
return node.nodeValue || "";
|
|
3047
|
+
}
|
|
3048
|
+
let text = "";
|
|
3049
|
+
for (const child of node.childNodes || []) {
|
|
3050
|
+
text += serializeText(child);
|
|
3051
|
+
}
|
|
3052
|
+
return text;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// src/xslt/serializer.js
|
|
3056
|
+
function serializeResult(node, outputSettings = {}) {
|
|
3057
|
+
if (!node) {
|
|
3058
|
+
return "";
|
|
3059
|
+
}
|
|
3060
|
+
const settings = resolveOutputSettings(outputSettings, node);
|
|
3061
|
+
if (settings.method === "text") {
|
|
3062
|
+
return serializeText(node);
|
|
3063
|
+
}
|
|
3064
|
+
if (settings.method === "html") {
|
|
3065
|
+
return new HtmlWriter(settings).serialize(node);
|
|
3066
|
+
}
|
|
3067
|
+
return new XmlWriter(settings, {
|
|
3068
|
+
xhtml: settings.method === "xhtml"
|
|
3069
|
+
}).serialize(node);
|
|
3070
|
+
}
|
|
3071
|
+
|
|
1562
3072
|
// src/xslt/engine.js
|
|
1563
|
-
var XSLT_NS =
|
|
3073
|
+
var XSLT_NS = XSLT_NAMESPACE;
|
|
1564
3074
|
var XsltContext = class _XsltContext {
|
|
1565
3075
|
constructor(options = {}) {
|
|
1566
3076
|
this.currentNode = options.currentNode;
|
|
@@ -1576,6 +3086,8 @@ var XsltContext = class _XsltContext {
|
|
|
1576
3086
|
this.decimalFormats = options.decimalFormats || {};
|
|
1577
3087
|
this.outputMethod = options.outputMethod || "xml";
|
|
1578
3088
|
this.xpathEvaluator = options.xpathEvaluator || new XPathEvaluator();
|
|
3089
|
+
this.currentTemplate = options.currentTemplate || null;
|
|
3090
|
+
this.currentMode = options.currentMode ?? null;
|
|
1579
3091
|
}
|
|
1580
3092
|
clone(overrides = {}) {
|
|
1581
3093
|
return new _XsltContext({
|
|
@@ -1591,7 +3103,9 @@ var XsltContext = class _XsltContext {
|
|
|
1591
3103
|
keys: this.keys,
|
|
1592
3104
|
decimalFormats: this.decimalFormats,
|
|
1593
3105
|
outputMethod: this.outputMethod,
|
|
1594
|
-
xpathEvaluator: this.xpathEvaluator
|
|
3106
|
+
xpathEvaluator: this.xpathEvaluator,
|
|
3107
|
+
currentTemplate: overrides.currentTemplate ?? this.currentTemplate,
|
|
3108
|
+
currentMode: overrides.currentMode ?? this.currentMode
|
|
1595
3109
|
});
|
|
1596
3110
|
}
|
|
1597
3111
|
getVariable(name) {
|
|
@@ -1616,7 +3130,9 @@ var XsltEngine = class {
|
|
|
1616
3130
|
this.globalParameters = {};
|
|
1617
3131
|
this.outputSettings = {
|
|
1618
3132
|
method: "xml",
|
|
3133
|
+
version: "1.0",
|
|
1619
3134
|
encoding: "UTF-8",
|
|
3135
|
+
standalone: null,
|
|
1620
3136
|
indent: "no",
|
|
1621
3137
|
omitXmlDeclaration: "no",
|
|
1622
3138
|
doctypePublic: null,
|
|
@@ -1628,13 +3144,105 @@ var XsltEngine = class {
|
|
|
1628
3144
|
this.decimalFormats = {};
|
|
1629
3145
|
this.stylesheetDoc = null;
|
|
1630
3146
|
this.attributeSets = {};
|
|
1631
|
-
this.namespaceAliases =
|
|
3147
|
+
this.namespaceAliases = new NamespaceAliasMap();
|
|
1632
3148
|
this.stripSpace = [];
|
|
1633
3149
|
this.preserveSpace = [];
|
|
1634
3150
|
this.stylesheetLoader = options.stylesheetLoader || null;
|
|
1635
3151
|
this.currentImportPrecedence = 0;
|
|
1636
3152
|
this.processedStylesheets = /* @__PURE__ */ new Set();
|
|
1637
3153
|
this.baseUri = options.baseUri || "";
|
|
3154
|
+
this.documentLoader = options.documentLoader || null;
|
|
3155
|
+
this.loadedDocuments = /* @__PURE__ */ new Map();
|
|
3156
|
+
this.generatedIds = /* @__PURE__ */ new WeakMap();
|
|
3157
|
+
this.generatedIdCount = 0;
|
|
3158
|
+
this.rootContext = null;
|
|
3159
|
+
this.keyRegistry = new KeyIndexRegistry({
|
|
3160
|
+
keys: this.keys,
|
|
3161
|
+
matchesPattern: (node, pattern) => this.matchesPattern(node, pattern, this.rootContext),
|
|
3162
|
+
evaluateUse: (node, expression) => this.evaluateKeyValues(node, expression)
|
|
3163
|
+
});
|
|
3164
|
+
this.xpathEvaluator.registerFunctions(createXsltFunctions(this));
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* Set the loader used by the XSLT `document()` function.
|
|
3168
|
+
*
|
|
3169
|
+
* The loader is synchronous and must return a `Document`, an XML string or
|
|
3170
|
+
* null. Returning null (or configuring no loader at all) makes `document()`
|
|
3171
|
+
* evaluate to an empty node-set instead of failing the transformation.
|
|
3172
|
+
*
|
|
3173
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader - The loader, or null to remove it
|
|
3174
|
+
* @returns {XsltEngine} This engine, to allow chaining
|
|
3175
|
+
*
|
|
3176
|
+
* @example
|
|
3177
|
+
* engine.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
3178
|
+
*/
|
|
3179
|
+
setDocumentLoader(loader) {
|
|
3180
|
+
this.documentLoader = loader ?? null;
|
|
3181
|
+
this.loadedDocuments.clear();
|
|
3182
|
+
return this;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Load an external document for the `document()` function.
|
|
3186
|
+
*
|
|
3187
|
+
* Results are cached per resolved URI for the life of the engine, so the same
|
|
3188
|
+
* URI always yields the identical node-set.
|
|
3189
|
+
*
|
|
3190
|
+
* @param {string} uri - The requested URI, fragment identifiers are ignored
|
|
3191
|
+
* @param {string} [baseUri] - Base URI used to resolve relative references
|
|
3192
|
+
* @returns {Document|null} The loaded document, or null when unavailable
|
|
3193
|
+
*
|
|
3194
|
+
* @example
|
|
3195
|
+
* engine.loadDocument('data.xml', '/styles/main.xsl');
|
|
3196
|
+
*/
|
|
3197
|
+
loadDocument(uri, baseUri) {
|
|
3198
|
+
const target = stripFragment(uri);
|
|
3199
|
+
if (target === "") return this.stylesheetDoc;
|
|
3200
|
+
if (!this.documentLoader) return null;
|
|
3201
|
+
const resolved = resolveUri(target, baseUri);
|
|
3202
|
+
if (this.loadedDocuments.has(resolved)) {
|
|
3203
|
+
return this.loadedDocuments.get(resolved);
|
|
3204
|
+
}
|
|
3205
|
+
const loaded = this.documentLoader(resolved, baseUri);
|
|
3206
|
+
const doc = typeof loaded === "string" ? this.parseXmlString(loaded) : loaded || null;
|
|
3207
|
+
this.loadedDocuments.set(resolved, doc);
|
|
3208
|
+
return doc;
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Return the stable identifier of a node for `generate-id()`.
|
|
3212
|
+
*
|
|
3213
|
+
* @param {Node} node - The node to identify
|
|
3214
|
+
* @returns {string} An identifier starting with a letter
|
|
3215
|
+
*
|
|
3216
|
+
* @example
|
|
3217
|
+
* engine.generateId(element); // 'N1'
|
|
3218
|
+
*/
|
|
3219
|
+
generateId(node) {
|
|
3220
|
+
let id = this.generatedIds.get(node);
|
|
3221
|
+
if (!id) {
|
|
3222
|
+
this.generatedIdCount++;
|
|
3223
|
+
id = `N${this.generatedIdCount}`;
|
|
3224
|
+
this.generatedIds.set(node, id);
|
|
3225
|
+
}
|
|
3226
|
+
return id;
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Evaluate the `use` expression of an `xsl:key` for one node.
|
|
3230
|
+
*
|
|
3231
|
+
* @param {Node} node - The node being indexed
|
|
3232
|
+
* @param {string} expression - The `use` expression
|
|
3233
|
+
* @returns {string[]} The key values contributed by the node
|
|
3234
|
+
*/
|
|
3235
|
+
evaluateKeyValues(node, expression) {
|
|
3236
|
+
const context = this.rootContext.clone({
|
|
3237
|
+
currentNode: node,
|
|
3238
|
+
currentNodeList: [node],
|
|
3239
|
+
position: 1
|
|
3240
|
+
});
|
|
3241
|
+
const value = this.evaluateXPath(expression, context);
|
|
3242
|
+
if (Array.isArray(value)) {
|
|
3243
|
+
return value.map((item) => this.xpathEvaluator.getStringValue(item));
|
|
3244
|
+
}
|
|
3245
|
+
return [this.xpathEvaluator.toString(value)];
|
|
1638
3246
|
}
|
|
1639
3247
|
/**
|
|
1640
3248
|
* Set the stylesheet loader function for xsl:import and xsl:include
|
|
@@ -1645,14 +3253,13 @@ var XsltEngine = class {
|
|
|
1645
3253
|
}
|
|
1646
3254
|
/**
|
|
1647
3255
|
* Resolve a relative URI against a base URI
|
|
3256
|
+
*
|
|
3257
|
+
* @param {string} href - The URI to resolve
|
|
3258
|
+
* @param {string} [baseUri] - The base URI
|
|
3259
|
+
* @returns {string} The resolved URI
|
|
1648
3260
|
*/
|
|
1649
3261
|
resolveUri(href, baseUri) {
|
|
1650
|
-
|
|
1651
|
-
return href;
|
|
1652
|
-
}
|
|
1653
|
-
const lastSlash = baseUri.lastIndexOf("/");
|
|
1654
|
-
const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
|
|
1655
|
-
return baseDir + href;
|
|
3262
|
+
return resolveUri(href, baseUri);
|
|
1656
3263
|
}
|
|
1657
3264
|
/**
|
|
1658
3265
|
* Load an external stylesheet document
|
|
@@ -1774,7 +3381,8 @@ var XsltEngine = class {
|
|
|
1774
3381
|
this.currentImportPrecedence = savedPrecedence;
|
|
1775
3382
|
} catch (error) {
|
|
1776
3383
|
throw new Error(
|
|
1777
|
-
`Failed to include stylesheet "${href}": ${error.message}
|
|
3384
|
+
`Failed to include stylesheet "${href}": ${error.message}`,
|
|
3385
|
+
{ cause: error }
|
|
1778
3386
|
);
|
|
1779
3387
|
}
|
|
1780
3388
|
}
|
|
@@ -1802,7 +3410,8 @@ var XsltEngine = class {
|
|
|
1802
3410
|
this.currentImportPrecedence++;
|
|
1803
3411
|
} catch (error) {
|
|
1804
3412
|
throw new Error(
|
|
1805
|
-
`Failed to import stylesheet "${href}": ${error.message}
|
|
3413
|
+
`Failed to import stylesheet "${href}": ${error.message}`,
|
|
3414
|
+
{ cause: error }
|
|
1806
3415
|
);
|
|
1807
3416
|
}
|
|
1808
3417
|
}
|
|
@@ -1878,39 +3487,50 @@ var XsltEngine = class {
|
|
|
1878
3487
|
}
|
|
1879
3488
|
}
|
|
1880
3489
|
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Register a template rule.
|
|
3492
|
+
*
|
|
3493
|
+
* A union match pattern is equivalent to a set of template rules, one per
|
|
3494
|
+
* alternative (XSLT 1.0 section 5.5), so each alternative is registered
|
|
3495
|
+
* separately with its own default priority.
|
|
3496
|
+
*
|
|
3497
|
+
* @param {Element} node - The xsl:template element
|
|
3498
|
+
*/
|
|
1881
3499
|
registerTemplate(node) {
|
|
1882
3500
|
const match = node.getAttribute("match");
|
|
1883
3501
|
const name = node.getAttribute("name");
|
|
1884
3502
|
const mode = node.getAttribute("mode") || null;
|
|
1885
3503
|
const priorityAttr = node.getAttribute("priority");
|
|
1886
|
-
const
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
3504
|
+
const alternatives = match ? this.splitUnionPattern(match).map((p) => p.trim()) : [null];
|
|
3505
|
+
for (const alternative of alternatives) {
|
|
3506
|
+
this.templates.push({
|
|
3507
|
+
match: alternative,
|
|
3508
|
+
name,
|
|
3509
|
+
mode,
|
|
3510
|
+
priority: priorityAttr ? parseFloat(priorityAttr) : this.calculatePriority(alternative),
|
|
3511
|
+
importPrecedence: this.currentImportPrecedence,
|
|
3512
|
+
node
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
1895
3515
|
}
|
|
3516
|
+
/**
|
|
3517
|
+
* Default priority of a single match pattern (see templatePriority.js).
|
|
3518
|
+
*
|
|
3519
|
+
* @param {string|null} matchPattern - The match pattern
|
|
3520
|
+
* @returns {number} The default priority
|
|
3521
|
+
*/
|
|
1896
3522
|
calculatePriority(matchPattern) {
|
|
1897
|
-
|
|
1898
|
-
if (matchPattern === "*" || matchPattern === "node()" || matchPattern === "text()" || matchPattern === "comment()" || matchPattern === "processing-instruction()") {
|
|
1899
|
-
return -0.5;
|
|
1900
|
-
}
|
|
1901
|
-
if (matchPattern.includes(":*")) {
|
|
1902
|
-
return -0.25;
|
|
1903
|
-
}
|
|
1904
|
-
if (/^[a-zA-Z_][\w.-]*$/.test(matchPattern)) {
|
|
1905
|
-
return 0;
|
|
1906
|
-
}
|
|
1907
|
-
return 0.5;
|
|
3523
|
+
return calculatePriority(matchPattern ? matchPattern.trim() : matchPattern);
|
|
1908
3524
|
}
|
|
1909
3525
|
processOutput(node) {
|
|
1910
3526
|
const method = node.getAttribute("method");
|
|
1911
3527
|
if (method) this.outputSettings.method = method;
|
|
3528
|
+
const version = node.getAttribute("version");
|
|
3529
|
+
if (version) this.outputSettings.version = version;
|
|
1912
3530
|
const encoding = node.getAttribute("encoding");
|
|
1913
3531
|
if (encoding) this.outputSettings.encoding = encoding;
|
|
3532
|
+
const standalone = node.getAttribute("standalone");
|
|
3533
|
+
if (standalone) this.outputSettings.standalone = standalone;
|
|
1914
3534
|
const indent = node.getAttribute("indent");
|
|
1915
3535
|
if (indent) this.outputSettings.indent = indent;
|
|
1916
3536
|
const omit = node.getAttribute("omit-xml-declaration");
|
|
@@ -1931,16 +3551,80 @@ var XsltEngine = class {
|
|
|
1931
3551
|
const select2 = node.getAttribute("select");
|
|
1932
3552
|
this.globalVariables[name] = { node, select: select2 };
|
|
1933
3553
|
}
|
|
3554
|
+
/**
|
|
3555
|
+
* Register an `xsl:param` top level declaration.
|
|
3556
|
+
*
|
|
3557
|
+
* A value supplied from outside (through `setParameter`) has precedence over
|
|
3558
|
+
* the declared default, so it survives compilation of the stylesheet.
|
|
3559
|
+
*
|
|
3560
|
+
* @param {Element} node - The `xsl:param` element
|
|
3561
|
+
* @returns {void}
|
|
3562
|
+
*/
|
|
1934
3563
|
processGlobalParam(node) {
|
|
1935
3564
|
const name = node.getAttribute("name");
|
|
1936
3565
|
const select2 = node.getAttribute("select");
|
|
1937
|
-
this.globalParameters[name]
|
|
3566
|
+
const existing = this.globalParameters[name];
|
|
3567
|
+
const definition = { node, select: select2 };
|
|
3568
|
+
if (existing && "value" in existing) {
|
|
3569
|
+
definition.value = existing.value;
|
|
3570
|
+
}
|
|
3571
|
+
this.globalParameters[name] = definition;
|
|
3572
|
+
}
|
|
3573
|
+
/**
|
|
3574
|
+
* Supply the value of a global parameter from outside the stylesheet.
|
|
3575
|
+
*
|
|
3576
|
+
* The value is merged into the `xsl:param` declaration when there is one, so
|
|
3577
|
+
* removing the value later restores the declared default.
|
|
3578
|
+
*
|
|
3579
|
+
* @param {string} name - The parameter name, `{uri}local` when namespaced
|
|
3580
|
+
* @param {*} value - The value to use
|
|
3581
|
+
* @returns {void}
|
|
3582
|
+
*
|
|
3583
|
+
* @example
|
|
3584
|
+
* engine.setParameterValue('sortOrder', 'ascending');
|
|
3585
|
+
*/
|
|
3586
|
+
setParameterValue(name, value) {
|
|
3587
|
+
const definition = this.globalParameters[name];
|
|
3588
|
+
if (definition) definition.value = value;
|
|
3589
|
+
else this.globalParameters[name] = { value };
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Remove an externally supplied parameter value.
|
|
3593
|
+
*
|
|
3594
|
+
* The `xsl:param` declaration of the stylesheet is kept, so the parameter
|
|
3595
|
+
* falls back to its declared default instead of becoming undefined.
|
|
3596
|
+
*
|
|
3597
|
+
* @param {string} name - The parameter name, `{uri}local` when namespaced
|
|
3598
|
+
* @returns {void}
|
|
3599
|
+
*
|
|
3600
|
+
* @example
|
|
3601
|
+
* engine.clearParameterValue('sortOrder');
|
|
3602
|
+
*/
|
|
3603
|
+
clearParameterValue(name) {
|
|
3604
|
+
const definition = this.globalParameters[name];
|
|
3605
|
+
if (!definition) return;
|
|
3606
|
+
if (definition.node) delete definition.value;
|
|
3607
|
+
else delete this.globalParameters[name];
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Remove every externally supplied parameter value.
|
|
3611
|
+
*
|
|
3612
|
+
* @returns {void}
|
|
3613
|
+
*
|
|
3614
|
+
* @example
|
|
3615
|
+
* engine.clearParameterValues();
|
|
3616
|
+
*/
|
|
3617
|
+
clearParameterValues() {
|
|
3618
|
+
for (const name of Object.keys(this.globalParameters)) {
|
|
3619
|
+
this.clearParameterValue(name);
|
|
3620
|
+
}
|
|
1938
3621
|
}
|
|
1939
3622
|
processKey(node) {
|
|
1940
3623
|
const name = node.getAttribute("name");
|
|
1941
3624
|
const match = node.getAttribute("match");
|
|
1942
3625
|
const use = node.getAttribute("use");
|
|
1943
3626
|
this.keys[name] = { match, use };
|
|
3627
|
+
this.keyRegistry.clear();
|
|
1944
3628
|
}
|
|
1945
3629
|
processDecimalFormat(node) {
|
|
1946
3630
|
const name = node.getAttribute("name") || "";
|
|
@@ -1958,9 +3642,7 @@ var XsltEngine = class {
|
|
|
1958
3642
|
};
|
|
1959
3643
|
}
|
|
1960
3644
|
processNamespaceAlias(node) {
|
|
1961
|
-
|
|
1962
|
-
const result = node.getAttribute("result-prefix");
|
|
1963
|
-
this.namespaceAliases[stylesheet] = result;
|
|
3645
|
+
this.namespaceAliases.add(node);
|
|
1964
3646
|
}
|
|
1965
3647
|
processAttributeSet(node) {
|
|
1966
3648
|
const name = node.getAttribute("name");
|
|
@@ -1990,11 +3672,13 @@ var XsltEngine = class {
|
|
|
1990
3672
|
if (!doc) {
|
|
1991
3673
|
throw new Error("No output document available");
|
|
1992
3674
|
}
|
|
3675
|
+
const resultDocument = createResultDocument(doc);
|
|
3676
|
+
const source = this.prepareSource(sourceNode, doc);
|
|
1993
3677
|
const context = new XsltContext({
|
|
1994
|
-
currentNode:
|
|
1995
|
-
currentNodeList: [
|
|
3678
|
+
currentNode: source,
|
|
3679
|
+
currentNodeList: [source],
|
|
1996
3680
|
position: 1,
|
|
1997
|
-
outputDocument:
|
|
3681
|
+
outputDocument: resultDocument,
|
|
1998
3682
|
stylesheet: this.stylesheetDoc,
|
|
1999
3683
|
namespaces: { ...this.namespaces },
|
|
2000
3684
|
templates: this.templates,
|
|
@@ -2003,6 +3687,7 @@ var XsltEngine = class {
|
|
|
2003
3687
|
outputMethod: this.outputSettings.method,
|
|
2004
3688
|
xpathEvaluator: this.xpathEvaluator
|
|
2005
3689
|
});
|
|
3690
|
+
this.rootContext = context;
|
|
2006
3691
|
for (const [name, def] of Object.entries(this.globalParameters)) {
|
|
2007
3692
|
if (!(name in context.parameters)) {
|
|
2008
3693
|
context.parameters[name] = this.evaluateVariable(def, context);
|
|
@@ -2011,33 +3696,91 @@ var XsltEngine = class {
|
|
|
2011
3696
|
for (const [name, def] of Object.entries(this.globalVariables)) {
|
|
2012
3697
|
context.variables[name] = this.evaluateVariable(def, context);
|
|
2013
3698
|
}
|
|
2014
|
-
const fragment =
|
|
2015
|
-
this.applyTemplates(
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
3699
|
+
const fragment = resultDocument.createDocumentFragment();
|
|
3700
|
+
this.applyTemplates([source], null, context, fragment);
|
|
3701
|
+
return importResultFragment(fragment, doc);
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Apply `xsl:strip-space` to the source tree.
|
|
3705
|
+
*
|
|
3706
|
+
* Stripping produces a copy so the caller's document is never modified; when
|
|
3707
|
+
* no `xsl:strip-space` is declared the original node is used unchanged.
|
|
3708
|
+
*
|
|
3709
|
+
* @param {Node} sourceNode - The source document or element
|
|
3710
|
+
* @param {Document} ownerDocument - Document providing the DOM implementation
|
|
3711
|
+
* @returns {Node} The source to transform
|
|
3712
|
+
*/
|
|
3713
|
+
prepareSource(sourceNode, ownerDocument) {
|
|
3714
|
+
const filter = new WhitespaceFilter(this.stripSpace, this.preserveSpace);
|
|
3715
|
+
if (!filter.isActive()) return sourceNode;
|
|
3716
|
+
return stripWhitespaceNodes(
|
|
3717
|
+
sourceNode,
|
|
3718
|
+
filter,
|
|
3719
|
+
createResultDocument(ownerDocument)
|
|
2020
3720
|
);
|
|
2021
|
-
return fragment;
|
|
2022
3721
|
}
|
|
2023
3722
|
/**
|
|
2024
3723
|
* Transform to a complete document
|
|
2025
3724
|
*/
|
|
2026
3725
|
transformToDocument(sourceNode) {
|
|
2027
|
-
const doc = this.createDocument();
|
|
3726
|
+
const doc = this.createDocument(sourceNode);
|
|
2028
3727
|
const fragment = this.transform(sourceNode, doc);
|
|
2029
3728
|
while (fragment.firstChild) {
|
|
2030
3729
|
doc.appendChild(fragment.firstChild);
|
|
2031
3730
|
}
|
|
2032
3731
|
return doc;
|
|
2033
3732
|
}
|
|
2034
|
-
|
|
3733
|
+
/**
|
|
3734
|
+
* Transform a source document and serialize the result to a string.
|
|
3735
|
+
*
|
|
3736
|
+
* Non-W3C convenience method: the result tree is serialized honoring the
|
|
3737
|
+
* `xsl:output` settings of the stylesheet (XSLT 1.0 section 16).
|
|
3738
|
+
*
|
|
3739
|
+
* @param {Node} sourceNode - Source document or element to transform
|
|
3740
|
+
* @returns {string} The serialized transformation result
|
|
3741
|
+
*/
|
|
3742
|
+
transformToString(sourceNode) {
|
|
3743
|
+
const fragment = this.transform(
|
|
3744
|
+
sourceNode,
|
|
3745
|
+
this.createDocument(sourceNode)
|
|
3746
|
+
);
|
|
3747
|
+
return serializeResult(fragment, this.outputSettings);
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* Create an empty XML document to hold a transformation result.
|
|
3751
|
+
*
|
|
3752
|
+
* Uses the global `document` when running in a browser and otherwise falls
|
|
3753
|
+
* back to the DOM implementation owning `referenceNode` (e.g. a jsdom or
|
|
3754
|
+
* xmldom document in Node.js).
|
|
3755
|
+
*
|
|
3756
|
+
* @param {Node} [referenceNode] - Any node whose DOM implementation can be reused
|
|
3757
|
+
* @returns {Document} A new empty document
|
|
3758
|
+
* @throws {Error} When no DOM implementation is available
|
|
3759
|
+
*/
|
|
3760
|
+
createDocument(referenceNode) {
|
|
2035
3761
|
if (typeof document !== "undefined") {
|
|
2036
3762
|
return document.implementation.createDocument(null, null, null);
|
|
2037
3763
|
}
|
|
3764
|
+
const ownerDocument = referenceNode && (referenceNode.nodeType === 9 ? referenceNode : referenceNode.ownerDocument);
|
|
3765
|
+
if (ownerDocument?.implementation) {
|
|
3766
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
3767
|
+
}
|
|
2038
3768
|
throw new Error("Document creation not available in this environment");
|
|
2039
3769
|
}
|
|
3770
|
+
/**
|
|
3771
|
+
* Compute the value of a variable or parameter definition.
|
|
3772
|
+
*
|
|
3773
|
+
* A value supplied from outside (`setParameter`) wins over the `select`
|
|
3774
|
+
* expression and over the instantiated content of the declaration.
|
|
3775
|
+
*
|
|
3776
|
+
* @param {{value?: *, select?: string, node?: Element}} def - The definition
|
|
3777
|
+
* @param {XsltContext} context - The context used for evaluation
|
|
3778
|
+
* @returns {*} The variable value
|
|
3779
|
+
*/
|
|
2040
3780
|
evaluateVariable(def, context) {
|
|
3781
|
+
if ("value" in def) {
|
|
3782
|
+
return def.value;
|
|
3783
|
+
}
|
|
2041
3784
|
if (def.select) {
|
|
2042
3785
|
return this.evaluateXPath(def.select, context);
|
|
2043
3786
|
}
|
|
@@ -2057,7 +3800,9 @@ var XsltEngine = class {
|
|
|
2057
3800
|
const newContext = context.clone({
|
|
2058
3801
|
currentNode: node,
|
|
2059
3802
|
currentNodeList: nodeList,
|
|
2060
|
-
position: i + 1
|
|
3803
|
+
position: i + 1,
|
|
3804
|
+
currentTemplate: template,
|
|
3805
|
+
currentMode: mode
|
|
2061
3806
|
});
|
|
2062
3807
|
this.processTemplate(template.node, newContext, output);
|
|
2063
3808
|
} else {
|
|
@@ -2068,13 +3813,14 @@ var XsltEngine = class {
|
|
|
2068
3813
|
/**
|
|
2069
3814
|
* Find the best matching template for a node
|
|
2070
3815
|
*/
|
|
2071
|
-
findMatchingTemplate(node, mode, context) {
|
|
3816
|
+
findMatchingTemplate(node, mode, context, maxImportPrecedence = Infinity) {
|
|
2072
3817
|
let bestMatch = null;
|
|
2073
3818
|
let bestPriority = -Infinity;
|
|
2074
3819
|
let bestImportPrecedence = -Infinity;
|
|
2075
3820
|
for (const template of this.templates) {
|
|
2076
3821
|
if (template.mode !== mode) continue;
|
|
2077
3822
|
if (!template.match) continue;
|
|
3823
|
+
if ((template.importPrecedence || 0) >= maxImportPrecedence) continue;
|
|
2078
3824
|
if (this.matchesPattern(node, template.match, context)) {
|
|
2079
3825
|
const priority = template.priority;
|
|
2080
3826
|
const importPrecedence = template.importPrecedence || 0;
|
|
@@ -2147,19 +3893,22 @@ var XsltEngine = class {
|
|
|
2147
3893
|
1,
|
|
2148
3894
|
1,
|
|
2149
3895
|
{ ...context.variables, ...context.parameters },
|
|
2150
|
-
context.namespaces
|
|
3896
|
+
context.namespaces,
|
|
3897
|
+
context
|
|
2151
3898
|
);
|
|
2152
3899
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2153
3900
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
2154
3901
|
return nodes2.includes(node);
|
|
2155
3902
|
}
|
|
2156
|
-
|
|
3903
|
+
const parent = node.nodeType === 2 ? node.ownerElement : node.parentNode;
|
|
3904
|
+
if (parent) {
|
|
2157
3905
|
const xpathContext2 = new XPathContext(
|
|
2158
|
-
|
|
3906
|
+
parent,
|
|
2159
3907
|
1,
|
|
2160
3908
|
1,
|
|
2161
3909
|
{ ...context.variables, ...context.parameters },
|
|
2162
|
-
context.namespaces
|
|
3910
|
+
context.namespaces,
|
|
3911
|
+
context
|
|
2163
3912
|
);
|
|
2164
3913
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2165
3914
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
@@ -2170,7 +3919,8 @@ var XsltEngine = class {
|
|
|
2170
3919
|
1,
|
|
2171
3920
|
1,
|
|
2172
3921
|
{ ...context.variables, ...context.parameters },
|
|
2173
|
-
context.namespaces
|
|
3922
|
+
context.namespaces,
|
|
3923
|
+
context
|
|
2174
3924
|
);
|
|
2175
3925
|
const result = this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2176
3926
|
const nodes = Array.isArray(result) ? result : [result];
|
|
@@ -2284,6 +4034,9 @@ var XsltEngine = class {
|
|
|
2284
4034
|
case "apply-templates":
|
|
2285
4035
|
this.xslApplyTemplates(node, context, output);
|
|
2286
4036
|
break;
|
|
4037
|
+
case "apply-imports":
|
|
4038
|
+
this.xslApplyImports(node, context, output);
|
|
4039
|
+
break;
|
|
2287
4040
|
case "call-template":
|
|
2288
4041
|
this.xslCallTemplate(node, context, output);
|
|
2289
4042
|
break;
|
|
@@ -2343,34 +4096,47 @@ var XsltEngine = class {
|
|
|
2343
4096
|
}
|
|
2344
4097
|
/**
|
|
2345
4098
|
* Process a literal result element (non-XSLT)
|
|
4099
|
+
*
|
|
4100
|
+
* Applies `xsl:namespace-alias` to the element and its attributes, honours
|
|
4101
|
+
* `xsl:use-attribute-sets` and keeps XSLT-only attributes and namespace
|
|
4102
|
+
* declarations out of the result tree.
|
|
4103
|
+
*
|
|
4104
|
+
* @param {Element} node - The literal result element in the stylesheet
|
|
4105
|
+
* @param {XsltContext} context - The current XSLT context
|
|
4106
|
+
* @param {Node} output - The result tree node receiving the element
|
|
4107
|
+
* @returns {void}
|
|
2346
4108
|
*/
|
|
2347
4109
|
processLiteralResultElement(node, context, output) {
|
|
2348
|
-
|
|
2349
|
-
const
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
if (resolvedNS && context.outputDocument.createElementNS) {
|
|
2361
|
-
outputElement = context.outputDocument.createElementNS(
|
|
2362
|
-
resolvedNS,
|
|
2363
|
-
nodeName
|
|
2364
|
-
);
|
|
2365
|
-
} else {
|
|
2366
|
-
outputElement = context.outputDocument.createElement(nodeName);
|
|
4110
|
+
const localName = node.localName || node.nodeName;
|
|
4111
|
+
const alias = this.namespaceAliases.resolve(node.namespaceURI, localName);
|
|
4112
|
+
const namespaceUri = alias ? alias.namespaceUri : node.namespaceURI;
|
|
4113
|
+
const qname = alias ? alias.qname : node.nodeName;
|
|
4114
|
+
const outputElement = namespaceUri && context.outputDocument.createElementNS ? context.outputDocument.createElementNS(namespaceUri, qname) : context.outputDocument.createElement(qname);
|
|
4115
|
+
const useAttributeSets = getXsltAttribute(
|
|
4116
|
+
node,
|
|
4117
|
+
"use-attribute-sets",
|
|
4118
|
+
XSLT_NS
|
|
4119
|
+
);
|
|
4120
|
+
if (useAttributeSets) {
|
|
4121
|
+
this.applyAttributeSets(useAttributeSets, context, outputElement);
|
|
2367
4122
|
}
|
|
2368
4123
|
if (node.attributes) {
|
|
2369
4124
|
for (const attr of node.attributes) {
|
|
2370
|
-
if (attr
|
|
2371
|
-
if (attr.name.startsWith("xmlns")) continue;
|
|
4125
|
+
if (!shouldCopyAttribute(attr, XSLT_NS)) continue;
|
|
2372
4126
|
const value = this.processAttributeValueTemplate(attr.value, context);
|
|
2373
|
-
|
|
4127
|
+
const attrAlias = this.namespaceAliases.resolve(
|
|
4128
|
+
attr.namespaceURI,
|
|
4129
|
+
attr.localName || attr.name
|
|
4130
|
+
);
|
|
4131
|
+
if (attrAlias) {
|
|
4132
|
+
outputElement.setAttributeNS(
|
|
4133
|
+
attrAlias.namespaceUri,
|
|
4134
|
+
attrAlias.qname,
|
|
4135
|
+
value
|
|
4136
|
+
);
|
|
4137
|
+
} else {
|
|
4138
|
+
outputElement.setAttribute(attr.name, value);
|
|
4139
|
+
}
|
|
2374
4140
|
}
|
|
2375
4141
|
}
|
|
2376
4142
|
this.processChildren(node, context, outputElement);
|
|
@@ -2457,6 +4223,38 @@ var XsltEngine = class {
|
|
|
2457
4223
|
});
|
|
2458
4224
|
this.applyTemplates(nodes, mode, newContext, output);
|
|
2459
4225
|
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Instantiate `xsl:apply-imports`.
|
|
4228
|
+
*
|
|
4229
|
+
* Only templates with a lower import precedence than the template being
|
|
4230
|
+
* instantiated are considered; when none matches, the built-in template rules
|
|
4231
|
+
* apply, exactly as for `xsl:apply-templates`.
|
|
4232
|
+
*
|
|
4233
|
+
* @param {Element} node - The `xsl:apply-imports` element
|
|
4234
|
+
* @param {XsltContext} context - The current XSLT context
|
|
4235
|
+
* @param {Node} output - The result tree node receiving the output
|
|
4236
|
+
* @returns {void}
|
|
4237
|
+
*/
|
|
4238
|
+
xslApplyImports(node, context, output) {
|
|
4239
|
+
const currentNode = context.currentNode;
|
|
4240
|
+
const mode = context.currentMode ?? null;
|
|
4241
|
+
const precedence = context.currentTemplate ? context.currentTemplate.importPrecedence || 0 : 0;
|
|
4242
|
+
const template = this.findMatchingTemplate(
|
|
4243
|
+
currentNode,
|
|
4244
|
+
mode,
|
|
4245
|
+
context,
|
|
4246
|
+
precedence
|
|
4247
|
+
);
|
|
4248
|
+
if (!template) {
|
|
4249
|
+
this.applyBuiltinTemplate(currentNode, mode, context, output);
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
this.processTemplate(
|
|
4253
|
+
template.node,
|
|
4254
|
+
context.clone({ currentTemplate: template }),
|
|
4255
|
+
output
|
|
4256
|
+
);
|
|
4257
|
+
}
|
|
2460
4258
|
xslCallTemplate(node, context, output) {
|
|
2461
4259
|
const name = node.getAttribute("name");
|
|
2462
4260
|
const template = this.templates.find((t) => t.name === name);
|
|
@@ -2781,79 +4579,47 @@ var XsltEngine = class {
|
|
|
2781
4579
|
xslNumber(node, context, output) {
|
|
2782
4580
|
const value = node.getAttribute("value");
|
|
2783
4581
|
const format = node.getAttribute("format") || "1";
|
|
2784
|
-
|
|
2785
|
-
let number;
|
|
4582
|
+
let numbers;
|
|
2786
4583
|
if (value) {
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
4584
|
+
numbers = [
|
|
4585
|
+
Math.round(
|
|
4586
|
+
this.xpathEvaluator.toNumber(this.evaluateXPath(value, context))
|
|
4587
|
+
)
|
|
4588
|
+
];
|
|
2790
4589
|
} else {
|
|
2791
|
-
|
|
4590
|
+
numbers = countXsltNumber(
|
|
4591
|
+
context.currentNode,
|
|
4592
|
+
{
|
|
4593
|
+
level: node.getAttribute("level") || "single",
|
|
4594
|
+
count: node.getAttribute("count"),
|
|
4595
|
+
from: node.getAttribute("from")
|
|
4596
|
+
},
|
|
4597
|
+
(candidate, pattern) => this.matchesPattern(candidate, pattern, context)
|
|
4598
|
+
);
|
|
2792
4599
|
}
|
|
2793
|
-
const
|
|
2794
|
-
|
|
4600
|
+
const text = context.outputDocument.createTextNode(
|
|
4601
|
+
formatXsltNumber(numbers, format)
|
|
4602
|
+
);
|
|
2795
4603
|
output.appendChild(text);
|
|
2796
4604
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
if (sibling.nodeType === 1) {
|
|
2805
|
-
if (!count || this.matchesPattern(sibling, count, context)) {
|
|
2806
|
-
n++;
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
sibling = sibling.previousSibling;
|
|
2810
|
-
}
|
|
2811
|
-
return n;
|
|
2812
|
-
}
|
|
2813
|
-
return 1;
|
|
2814
|
-
}
|
|
4605
|
+
/**
|
|
4606
|
+
* Format a single number with an `xsl:number` format token.
|
|
4607
|
+
*
|
|
4608
|
+
* @param {number} number - The number to format
|
|
4609
|
+
* @param {string} format - The format token, e.g. `1`, `01`, `a`, `I`
|
|
4610
|
+
* @returns {string} The formatted number
|
|
4611
|
+
*/
|
|
2815
4612
|
formatNumber(number, format) {
|
|
2816
|
-
|
|
2817
|
-
return String(number).padStart(format.length, "0");
|
|
2818
|
-
}
|
|
2819
|
-
if (format === "a") {
|
|
2820
|
-
return String.fromCharCode(96 + (number - 1) % 26 + 1);
|
|
2821
|
-
}
|
|
2822
|
-
if (format === "A") {
|
|
2823
|
-
return String.fromCharCode(64 + (number - 1) % 26 + 1);
|
|
2824
|
-
}
|
|
2825
|
-
if (format === "i") {
|
|
2826
|
-
return this.toRoman(number).toLowerCase();
|
|
2827
|
-
}
|
|
2828
|
-
if (format === "I") {
|
|
2829
|
-
return this.toRoman(number);
|
|
2830
|
-
}
|
|
2831
|
-
return String(number);
|
|
4613
|
+
return formatXsltNumber([number], format);
|
|
2832
4614
|
}
|
|
4615
|
+
/**
|
|
4616
|
+
* Convert a number to an upper case Roman numeral.
|
|
4617
|
+
*
|
|
4618
|
+
* @param {number} num - The number to convert
|
|
4619
|
+
* @returns {string} The Roman numeral
|
|
4620
|
+
*/
|
|
2833
4621
|
toRoman(num) {
|
|
2834
|
-
|
|
2835
|
-
["M", 1e3],
|
|
2836
|
-
["CM", 900],
|
|
2837
|
-
["D", 500],
|
|
2838
|
-
["CD", 400],
|
|
2839
|
-
["C", 100],
|
|
2840
|
-
["XC", 90],
|
|
2841
|
-
["L", 50],
|
|
2842
|
-
["XL", 40],
|
|
2843
|
-
["X", 10],
|
|
2844
|
-
["IX", 9],
|
|
2845
|
-
["V", 5],
|
|
2846
|
-
["IV", 4],
|
|
2847
|
-
["I", 1]
|
|
2848
|
-
];
|
|
2849
|
-
let result = "";
|
|
2850
|
-
for (const [numeral, value] of romanNumerals) {
|
|
2851
|
-
while (num >= value) {
|
|
2852
|
-
result += numeral;
|
|
2853
|
-
num -= value;
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
2856
|
-
return result;
|
|
4622
|
+
return toRoman(num);
|
|
2857
4623
|
}
|
|
2858
4624
|
xslMessage(node, context, _output) {
|
|
2859
4625
|
const terminate = node.getAttribute("terminate") === "yes";
|
|
@@ -2937,7 +4703,8 @@ var XsltEngine = class {
|
|
|
2937
4703
|
context.position,
|
|
2938
4704
|
context.currentNodeList.length,
|
|
2939
4705
|
{ ...context.variables, ...context.parameters },
|
|
2940
|
-
context.namespaces
|
|
4706
|
+
context.namespaces,
|
|
4707
|
+
context
|
|
2941
4708
|
);
|
|
2942
4709
|
return this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2943
4710
|
}
|
|
@@ -2957,6 +4724,94 @@ var XSLTProcessor = class {
|
|
|
2957
4724
|
this._engine = null;
|
|
2958
4725
|
this._stylesheet = null;
|
|
2959
4726
|
this._parameters = /* @__PURE__ */ new Map();
|
|
4727
|
+
this._stylesheetLoader = null;
|
|
4728
|
+
this._documentLoader = null;
|
|
4729
|
+
}
|
|
4730
|
+
/**
|
|
4731
|
+
* The underlying XSLT engine (advanced usage).
|
|
4732
|
+
*
|
|
4733
|
+
* The engine is created lazily by {@link XSLTProcessor#importStylesheet},
|
|
4734
|
+
* so this getter returns `null` until a stylesheet has been imported.
|
|
4735
|
+
* Prefer the public {@link XSLTProcessor#setStylesheetLoader} over reaching
|
|
4736
|
+
* into the engine directly.
|
|
4737
|
+
*
|
|
4738
|
+
* @returns {import('./xslt/engine.js').XsltEngine|null} The engine, or null before import
|
|
4739
|
+
*
|
|
4740
|
+
* @example
|
|
4741
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
4742
|
+
* console.log(processor.engine.outputSettings.method);
|
|
4743
|
+
*/
|
|
4744
|
+
get engine() {
|
|
4745
|
+
return this._engine;
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Sets the loader used to resolve `xsl:import` and `xsl:include` references.
|
|
4749
|
+
*
|
|
4750
|
+
* The loader is synchronous: it MUST return the external stylesheet as a
|
|
4751
|
+
* `Document` or as an XML string (which is parsed automatically). Promises
|
|
4752
|
+
* are not awaited by the engine, so pre-load remote stylesheets before
|
|
4753
|
+
* calling `importStylesheet`.
|
|
4754
|
+
*
|
|
4755
|
+
* The loader may be set before or after `importStylesheet`. When set before,
|
|
4756
|
+
* it is passed to the engine on creation, which is required for the loader to
|
|
4757
|
+
* be used while the stylesheet is being compiled. When set after, the live
|
|
4758
|
+
* engine is updated as well.
|
|
4759
|
+
*
|
|
4760
|
+
* @param {((href: string, baseUri?: string) => (Document|string))|null} loader
|
|
4761
|
+
* The loader function, or null to remove a previously configured loader
|
|
4762
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
4763
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
4764
|
+
*
|
|
4765
|
+
* @example
|
|
4766
|
+
* processor.setStylesheetLoader((href) => readFileSync(href, 'utf8'));
|
|
4767
|
+
* processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
4768
|
+
*/
|
|
4769
|
+
setStylesheetLoader(loader) {
|
|
4770
|
+
if (loader !== null && loader !== void 0 && typeof loader !== "function") {
|
|
4771
|
+
throw new TypeError(
|
|
4772
|
+
"Failed to execute 'setStylesheetLoader' on 'XSLTProcessor': The loader argument must be a function or null."
|
|
4773
|
+
);
|
|
4774
|
+
}
|
|
4775
|
+
this._stylesheetLoader = loader ?? null;
|
|
4776
|
+
if (this._engine) {
|
|
4777
|
+
this._engine.setStylesheetLoader(this._stylesheetLoader);
|
|
4778
|
+
}
|
|
4779
|
+
return this;
|
|
4780
|
+
}
|
|
4781
|
+
/**
|
|
4782
|
+
* Sets the loader used to resolve the XSLT `document()` function.
|
|
4783
|
+
*
|
|
4784
|
+
* The loader is synchronous: it MUST return the referenced document as a
|
|
4785
|
+
* `Document`, as an XML string (which is parsed automatically) or as `null`
|
|
4786
|
+
* when the document cannot be provided. Returning `null`, like configuring no
|
|
4787
|
+
* loader at all, makes `document()` evaluate to an empty node-set rather than
|
|
4788
|
+
* failing the transformation.
|
|
4789
|
+
*
|
|
4790
|
+
* The loader may be set before or after `importStylesheet`; a live engine is
|
|
4791
|
+
* kept in sync.
|
|
4792
|
+
*
|
|
4793
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader
|
|
4794
|
+
* The loader function, or null to remove a previously configured loader
|
|
4795
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
4796
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
4797
|
+
*
|
|
4798
|
+
* @example
|
|
4799
|
+
* // Node.js: resolve document() against the file system
|
|
4800
|
+
* import { readFileSync } from 'node:fs';
|
|
4801
|
+
* processor.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
4802
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
4803
|
+
*/
|
|
4804
|
+
setDocumentLoader(loader) {
|
|
4805
|
+
if (loader !== null && loader !== void 0 && typeof loader !== "function") {
|
|
4806
|
+
throw new TypeError(
|
|
4807
|
+
"Failed to execute 'setDocumentLoader' on 'XSLTProcessor': The loader argument must be a function or null."
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
this._documentLoader = loader ?? null;
|
|
4811
|
+
if (this._engine) {
|
|
4812
|
+
this._engine.setDocumentLoader(this._documentLoader);
|
|
4813
|
+
}
|
|
4814
|
+
return this;
|
|
2960
4815
|
}
|
|
2961
4816
|
/**
|
|
2962
4817
|
* Imports the XSLT stylesheet.
|
|
@@ -2966,14 +4821,17 @@ var XSLTProcessor = class {
|
|
|
2966
4821
|
* <xsl:stylesheet> or <xsl:transform> element.
|
|
2967
4822
|
*
|
|
2968
4823
|
* @param {Node} style - The XSLT stylesheet to import (Document or Element)
|
|
4824
|
+
* @param {string} [stylesheetUri] - Optional URI of the stylesheet, used as the
|
|
4825
|
+
* base URI when resolving relative `xsl:import`/`xsl:include` hrefs. When
|
|
4826
|
+
* omitted, hrefs are passed to the loader unresolved.
|
|
2969
4827
|
* @returns {void}
|
|
2970
4828
|
*
|
|
2971
4829
|
* @example
|
|
2972
4830
|
* const parser = new DOMParser();
|
|
2973
4831
|
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
2974
|
-
* processor.importStylesheet(xslDoc);
|
|
4832
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
2975
4833
|
*/
|
|
2976
|
-
importStylesheet(style) {
|
|
4834
|
+
importStylesheet(style, stylesheetUri) {
|
|
2977
4835
|
if (!style) {
|
|
2978
4836
|
throw new TypeError(
|
|
2979
4837
|
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present."
|
|
@@ -2989,11 +4847,14 @@ var XSLTProcessor = class {
|
|
|
2989
4847
|
throw new Error("XSLT stylesheet contains parse errors");
|
|
2990
4848
|
}
|
|
2991
4849
|
this._stylesheet = style;
|
|
2992
|
-
this._engine = new XsltEngine(
|
|
4850
|
+
this._engine = new XsltEngine({
|
|
4851
|
+
stylesheetLoader: this._stylesheetLoader,
|
|
4852
|
+
documentLoader: this._documentLoader
|
|
4853
|
+
});
|
|
2993
4854
|
for (const [key, value] of this._parameters) {
|
|
2994
|
-
this._engine.
|
|
4855
|
+
this._engine.setParameterValue(key, value);
|
|
2995
4856
|
}
|
|
2996
|
-
this._engine.importStylesheet(style);
|
|
4857
|
+
this._engine.importStylesheet(style, stylesheetUri);
|
|
2997
4858
|
}
|
|
2998
4859
|
/**
|
|
2999
4860
|
* Transforms the node source by applying the XSLT stylesheet.
|
|
@@ -3074,6 +4935,45 @@ var XSLTProcessor = class {
|
|
|
3074
4935
|
return null;
|
|
3075
4936
|
}
|
|
3076
4937
|
}
|
|
4938
|
+
/**
|
|
4939
|
+
* Transforms the node source by applying the XSLT stylesheet and serializes
|
|
4940
|
+
* the result to a string honoring the stylesheet `xsl:output` settings.
|
|
4941
|
+
*
|
|
4942
|
+
* Non-W3C convenience method: the native XSLTProcessor has no equivalent.
|
|
4943
|
+
* Output method, indentation, XML declaration, document type declaration,
|
|
4944
|
+
* CDATA sections and `disable-output-escaping` are all honored
|
|
4945
|
+
* (XSLT 1.0 section 16).
|
|
4946
|
+
*
|
|
4947
|
+
* @param {Node} source - The XML document to transform
|
|
4948
|
+
* @returns {string|null} The serialized result, or null on a transformation error
|
|
4949
|
+
*
|
|
4950
|
+
* @example
|
|
4951
|
+
* const xml = processor.transformToString(xmlDoc);
|
|
4952
|
+
* // '<?xml version="1.0" encoding="UTF-8"?>\n<BAR>\n <QUX/>\n</BAR>'
|
|
4953
|
+
*/
|
|
4954
|
+
transformToString(source) {
|
|
4955
|
+
if (!source) {
|
|
4956
|
+
throw new TypeError(
|
|
4957
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': 1 argument required, but only 0 present."
|
|
4958
|
+
);
|
|
4959
|
+
}
|
|
4960
|
+
if (!this._engine || !this._stylesheet) {
|
|
4961
|
+
throw new Error(
|
|
4962
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': No stylesheet has been imported."
|
|
4963
|
+
);
|
|
4964
|
+
}
|
|
4965
|
+
if (source.nodeType !== 1 && source.nodeType !== 9 && source.nodeType !== 11) {
|
|
4966
|
+
throw new TypeError(
|
|
4967
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': The source is not a valid node type."
|
|
4968
|
+
);
|
|
4969
|
+
}
|
|
4970
|
+
try {
|
|
4971
|
+
return this._engine.transformToString(source);
|
|
4972
|
+
} catch (error) {
|
|
4973
|
+
console.error("XSLT transformation error:", error);
|
|
4974
|
+
return null;
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
3077
4977
|
/**
|
|
3078
4978
|
* Sets a parameter in the XSLT stylesheet.
|
|
3079
4979
|
*
|
|
@@ -3100,7 +5000,7 @@ var XSLTProcessor = class {
|
|
|
3100
5000
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3101
5001
|
this._parameters.set(key, value);
|
|
3102
5002
|
if (this._engine) {
|
|
3103
|
-
this._engine.
|
|
5003
|
+
this._engine.setParameterValue(key, value);
|
|
3104
5004
|
}
|
|
3105
5005
|
}
|
|
3106
5006
|
/**
|
|
@@ -3157,7 +5057,7 @@ var XSLTProcessor = class {
|
|
|
3157
5057
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3158
5058
|
this._parameters.delete(key);
|
|
3159
5059
|
if (this._engine) {
|
|
3160
|
-
|
|
5060
|
+
this._engine.clearParameterValue(key);
|
|
3161
5061
|
}
|
|
3162
5062
|
}
|
|
3163
5063
|
/**
|
|
@@ -3173,12 +5073,19 @@ var XSLTProcessor = class {
|
|
|
3173
5073
|
clearParameters() {
|
|
3174
5074
|
this._parameters.clear();
|
|
3175
5075
|
if (this._engine) {
|
|
3176
|
-
this._engine.
|
|
5076
|
+
this._engine.clearParameterValues();
|
|
3177
5077
|
}
|
|
3178
5078
|
}
|
|
3179
5079
|
/**
|
|
3180
5080
|
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
3181
5081
|
*
|
|
5082
|
+
* Per the W3C `XSLTProcessor` semantics, `reset()` clears stylesheet state and
|
|
5083
|
+
* parameters only. The stylesheet and document loaders are processor
|
|
5084
|
+
* configuration rather than stylesheet state, so they are deliberately
|
|
5085
|
+
* preserved and stay effective for the next `importStylesheet()` call. Pass
|
|
5086
|
+
* `null` to {@link XSLTProcessor#setStylesheetLoader} or
|
|
5087
|
+
* {@link XSLTProcessor#setDocumentLoader} to remove them explicitly.
|
|
5088
|
+
*
|
|
3182
5089
|
* @returns {void}
|
|
3183
5090
|
*
|
|
3184
5091
|
* @example
|
|
@@ -3247,9 +5154,9 @@ function selectFirst(expression, contextNode, options = {}) {
|
|
|
3247
5154
|
}
|
|
3248
5155
|
|
|
3249
5156
|
// src/index.js
|
|
3250
|
-
var VERSION = "1.
|
|
5157
|
+
var VERSION = "1.1.1";
|
|
3251
5158
|
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
3252
|
-
var isNode = typeof process !== "undefined" && process.versions
|
|
5159
|
+
var isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
3253
5160
|
export {
|
|
3254
5161
|
VERSION,
|
|
3255
5162
|
XPathContext,
|
|
@@ -3264,8 +5171,12 @@ export {
|
|
|
3264
5171
|
isBrowser,
|
|
3265
5172
|
isNativeXSLTSupported,
|
|
3266
5173
|
isNode,
|
|
5174
|
+
isRawText,
|
|
5175
|
+
markRawText,
|
|
3267
5176
|
parse as parseXPath,
|
|
5177
|
+
resolveOutputSettings,
|
|
3268
5178
|
selectFirst as selectFirstXPath,
|
|
3269
|
-
select as selectXPath
|
|
5179
|
+
select as selectXPath,
|
|
5180
|
+
serializeResult
|
|
3270
5181
|
};
|
|
3271
5182
|
//# sourceMappingURL=xslt-processor.js.map
|