@tradik/xslt-processor 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +290 -45
- package/bin/lib/options.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +115 -0
- package/bin/xslt.js +68 -162
- package/dist/xslt-processor.browser.js +2074 -159
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +6 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +2078 -158
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +299 -0
- package/dist/xslt-processor.d.ts +92 -4
- package/dist/xslt-processor.js +2073 -157
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +26 -15
- package/src/XSLTProcessor.js +177 -8
- package/src/index.js +11 -5
- package/src/xpath/evaluator.js +48 -7
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine.js +471 -179
- package/src/xslt/formatNumber.js +220 -0
- package/src/xslt/functions.js +191 -0
- package/src/xslt/index.js +31 -0
- package/src/xslt/keys.js +141 -0
- package/src/xslt/literalResult.js +167 -0
- package/src/xslt/number.js +178 -0
- package/src/xslt/numberFormat.js +155 -0
- package/src/xslt/resultTree.js +74 -0
- package/src/xslt/serializer/baseWriter.js +283 -0
- package/src/xslt/serializer/constants.js +78 -0
- package/src/xslt/serializer/escape.js +98 -0
- package/src/xslt/serializer/htmlSerializer.js +141 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +103 -0
- package/src/xslt/serializer/textSerializer.js +29 -0
- package/src/xslt/serializer/xmlSerializer.js +127 -0
- package/src/xslt/serializer.js +57 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/whitespace.js +184 -0
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- package/src/xslt/engine.test.js +0 -3130
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,28 +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
|
-
return fragment;
|
|
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)
|
|
3720
|
+
);
|
|
2017
3721
|
}
|
|
2018
3722
|
/**
|
|
2019
3723
|
* Transform to a complete document
|
|
2020
3724
|
*/
|
|
2021
3725
|
transformToDocument(sourceNode) {
|
|
2022
|
-
const doc = this.createDocument();
|
|
3726
|
+
const doc = this.createDocument(sourceNode);
|
|
2023
3727
|
const fragment = this.transform(sourceNode, doc);
|
|
2024
3728
|
while (fragment.firstChild) {
|
|
2025
3729
|
doc.appendChild(fragment.firstChild);
|
|
2026
3730
|
}
|
|
2027
3731
|
return doc;
|
|
2028
3732
|
}
|
|
2029
|
-
|
|
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) {
|
|
2030
3761
|
if (typeof document !== "undefined") {
|
|
2031
3762
|
return document.implementation.createDocument(null, null, null);
|
|
2032
3763
|
}
|
|
3764
|
+
const ownerDocument = referenceNode && (referenceNode.nodeType === 9 ? referenceNode : referenceNode.ownerDocument);
|
|
3765
|
+
if (ownerDocument?.implementation) {
|
|
3766
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
3767
|
+
}
|
|
2033
3768
|
throw new Error("Document creation not available in this environment");
|
|
2034
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
|
+
*/
|
|
2035
3780
|
evaluateVariable(def, context) {
|
|
3781
|
+
if ("value" in def) {
|
|
3782
|
+
return def.value;
|
|
3783
|
+
}
|
|
2036
3784
|
if (def.select) {
|
|
2037
3785
|
return this.evaluateXPath(def.select, context);
|
|
2038
3786
|
}
|
|
@@ -2052,7 +3800,9 @@ var XsltEngine = class {
|
|
|
2052
3800
|
const newContext = context.clone({
|
|
2053
3801
|
currentNode: node,
|
|
2054
3802
|
currentNodeList: nodeList,
|
|
2055
|
-
position: i + 1
|
|
3803
|
+
position: i + 1,
|
|
3804
|
+
currentTemplate: template,
|
|
3805
|
+
currentMode: mode
|
|
2056
3806
|
});
|
|
2057
3807
|
this.processTemplate(template.node, newContext, output);
|
|
2058
3808
|
} else {
|
|
@@ -2063,13 +3813,14 @@ var XsltEngine = class {
|
|
|
2063
3813
|
/**
|
|
2064
3814
|
* Find the best matching template for a node
|
|
2065
3815
|
*/
|
|
2066
|
-
findMatchingTemplate(node, mode, context) {
|
|
3816
|
+
findMatchingTemplate(node, mode, context, maxImportPrecedence = Infinity) {
|
|
2067
3817
|
let bestMatch = null;
|
|
2068
3818
|
let bestPriority = -Infinity;
|
|
2069
3819
|
let bestImportPrecedence = -Infinity;
|
|
2070
3820
|
for (const template of this.templates) {
|
|
2071
3821
|
if (template.mode !== mode) continue;
|
|
2072
3822
|
if (!template.match) continue;
|
|
3823
|
+
if ((template.importPrecedence || 0) >= maxImportPrecedence) continue;
|
|
2073
3824
|
if (this.matchesPattern(node, template.match, context)) {
|
|
2074
3825
|
const priority = template.priority;
|
|
2075
3826
|
const importPrecedence = template.importPrecedence || 0;
|
|
@@ -2142,19 +3893,22 @@ var XsltEngine = class {
|
|
|
2142
3893
|
1,
|
|
2143
3894
|
1,
|
|
2144
3895
|
{ ...context.variables, ...context.parameters },
|
|
2145
|
-
context.namespaces
|
|
3896
|
+
context.namespaces,
|
|
3897
|
+
context
|
|
2146
3898
|
);
|
|
2147
3899
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2148
3900
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
2149
3901
|
return nodes2.includes(node);
|
|
2150
3902
|
}
|
|
2151
|
-
|
|
3903
|
+
const parent = node.nodeType === 2 ? node.ownerElement : node.parentNode;
|
|
3904
|
+
if (parent) {
|
|
2152
3905
|
const xpathContext2 = new XPathContext(
|
|
2153
|
-
|
|
3906
|
+
parent,
|
|
2154
3907
|
1,
|
|
2155
3908
|
1,
|
|
2156
3909
|
{ ...context.variables, ...context.parameters },
|
|
2157
|
-
context.namespaces
|
|
3910
|
+
context.namespaces,
|
|
3911
|
+
context
|
|
2158
3912
|
);
|
|
2159
3913
|
const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
|
|
2160
3914
|
const nodes2 = Array.isArray(result2) ? result2 : [result2];
|
|
@@ -2165,7 +3919,8 @@ var XsltEngine = class {
|
|
|
2165
3919
|
1,
|
|
2166
3920
|
1,
|
|
2167
3921
|
{ ...context.variables, ...context.parameters },
|
|
2168
|
-
context.namespaces
|
|
3922
|
+
context.namespaces,
|
|
3923
|
+
context
|
|
2169
3924
|
);
|
|
2170
3925
|
const result = this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2171
3926
|
const nodes = Array.isArray(result) ? result : [result];
|
|
@@ -2279,6 +4034,9 @@ var XsltEngine = class {
|
|
|
2279
4034
|
case "apply-templates":
|
|
2280
4035
|
this.xslApplyTemplates(node, context, output);
|
|
2281
4036
|
break;
|
|
4037
|
+
case "apply-imports":
|
|
4038
|
+
this.xslApplyImports(node, context, output);
|
|
4039
|
+
break;
|
|
2282
4040
|
case "call-template":
|
|
2283
4041
|
this.xslCallTemplate(node, context, output);
|
|
2284
4042
|
break;
|
|
@@ -2338,34 +4096,47 @@ var XsltEngine = class {
|
|
|
2338
4096
|
}
|
|
2339
4097
|
/**
|
|
2340
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}
|
|
2341
4108
|
*/
|
|
2342
4109
|
processLiteralResultElement(node, context, output) {
|
|
2343
|
-
|
|
2344
|
-
const
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
if (resolvedNS && context.outputDocument.createElementNS) {
|
|
2356
|
-
outputElement = context.outputDocument.createElementNS(
|
|
2357
|
-
resolvedNS,
|
|
2358
|
-
nodeName
|
|
2359
|
-
);
|
|
2360
|
-
} else {
|
|
2361
|
-
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);
|
|
2362
4122
|
}
|
|
2363
4123
|
if (node.attributes) {
|
|
2364
4124
|
for (const attr of node.attributes) {
|
|
2365
|
-
if (attr
|
|
2366
|
-
if (attr.name.startsWith("xmlns")) continue;
|
|
4125
|
+
if (!shouldCopyAttribute(attr, XSLT_NS)) continue;
|
|
2367
4126
|
const value = this.processAttributeValueTemplate(attr.value, context);
|
|
2368
|
-
|
|
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
|
+
}
|
|
2369
4140
|
}
|
|
2370
4141
|
}
|
|
2371
4142
|
this.processChildren(node, context, outputElement);
|
|
@@ -2452,6 +4223,38 @@ var XsltEngine = class {
|
|
|
2452
4223
|
});
|
|
2453
4224
|
this.applyTemplates(nodes, mode, newContext, output);
|
|
2454
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
|
+
}
|
|
2455
4258
|
xslCallTemplate(node, context, output) {
|
|
2456
4259
|
const name = node.getAttribute("name");
|
|
2457
4260
|
const template = this.templates.find((t) => t.name === name);
|
|
@@ -2776,79 +4579,47 @@ var XsltEngine = class {
|
|
|
2776
4579
|
xslNumber(node, context, output) {
|
|
2777
4580
|
const value = node.getAttribute("value");
|
|
2778
4581
|
const format = node.getAttribute("format") || "1";
|
|
2779
|
-
|
|
2780
|
-
let number;
|
|
4582
|
+
let numbers;
|
|
2781
4583
|
if (value) {
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
4584
|
+
numbers = [
|
|
4585
|
+
Math.round(
|
|
4586
|
+
this.xpathEvaluator.toNumber(this.evaluateXPath(value, context))
|
|
4587
|
+
)
|
|
4588
|
+
];
|
|
2785
4589
|
} else {
|
|
2786
|
-
|
|
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
|
+
);
|
|
2787
4599
|
}
|
|
2788
|
-
const
|
|
2789
|
-
|
|
4600
|
+
const text = context.outputDocument.createTextNode(
|
|
4601
|
+
formatXsltNumber(numbers, format)
|
|
4602
|
+
);
|
|
2790
4603
|
output.appendChild(text);
|
|
2791
4604
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
if (sibling.nodeType === 1) {
|
|
2800
|
-
if (!count || this.matchesPattern(sibling, count, context)) {
|
|
2801
|
-
n++;
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2804
|
-
sibling = sibling.previousSibling;
|
|
2805
|
-
}
|
|
2806
|
-
return n;
|
|
2807
|
-
}
|
|
2808
|
-
return 1;
|
|
2809
|
-
}
|
|
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
|
+
*/
|
|
2810
4612
|
formatNumber(number, format) {
|
|
2811
|
-
|
|
2812
|
-
return String(number).padStart(format.length, "0");
|
|
2813
|
-
}
|
|
2814
|
-
if (format === "a") {
|
|
2815
|
-
return String.fromCharCode(96 + (number - 1) % 26 + 1);
|
|
2816
|
-
}
|
|
2817
|
-
if (format === "A") {
|
|
2818
|
-
return String.fromCharCode(64 + (number - 1) % 26 + 1);
|
|
2819
|
-
}
|
|
2820
|
-
if (format === "i") {
|
|
2821
|
-
return this.toRoman(number).toLowerCase();
|
|
2822
|
-
}
|
|
2823
|
-
if (format === "I") {
|
|
2824
|
-
return this.toRoman(number);
|
|
2825
|
-
}
|
|
2826
|
-
return String(number);
|
|
4613
|
+
return formatXsltNumber([number], format);
|
|
2827
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
|
+
*/
|
|
2828
4621
|
toRoman(num) {
|
|
2829
|
-
|
|
2830
|
-
["M", 1e3],
|
|
2831
|
-
["CM", 900],
|
|
2832
|
-
["D", 500],
|
|
2833
|
-
["CD", 400],
|
|
2834
|
-
["C", 100],
|
|
2835
|
-
["XC", 90],
|
|
2836
|
-
["L", 50],
|
|
2837
|
-
["XL", 40],
|
|
2838
|
-
["X", 10],
|
|
2839
|
-
["IX", 9],
|
|
2840
|
-
["V", 5],
|
|
2841
|
-
["IV", 4],
|
|
2842
|
-
["I", 1]
|
|
2843
|
-
];
|
|
2844
|
-
let result = "";
|
|
2845
|
-
for (const [numeral, value] of romanNumerals) {
|
|
2846
|
-
while (num >= value) {
|
|
2847
|
-
result += numeral;
|
|
2848
|
-
num -= value;
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
return result;
|
|
4622
|
+
return toRoman(num);
|
|
2852
4623
|
}
|
|
2853
4624
|
xslMessage(node, context, _output) {
|
|
2854
4625
|
const terminate = node.getAttribute("terminate") === "yes";
|
|
@@ -2932,7 +4703,8 @@ var XsltEngine = class {
|
|
|
2932
4703
|
context.position,
|
|
2933
4704
|
context.currentNodeList.length,
|
|
2934
4705
|
{ ...context.variables, ...context.parameters },
|
|
2935
|
-
context.namespaces
|
|
4706
|
+
context.namespaces,
|
|
4707
|
+
context
|
|
2936
4708
|
);
|
|
2937
4709
|
return this.xpathEvaluator.evaluate(ast, xpathContext);
|
|
2938
4710
|
}
|
|
@@ -2952,6 +4724,94 @@ var XSLTProcessor = class {
|
|
|
2952
4724
|
this._engine = null;
|
|
2953
4725
|
this._stylesheet = null;
|
|
2954
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;
|
|
2955
4815
|
}
|
|
2956
4816
|
/**
|
|
2957
4817
|
* Imports the XSLT stylesheet.
|
|
@@ -2961,14 +4821,17 @@ var XSLTProcessor = class {
|
|
|
2961
4821
|
* <xsl:stylesheet> or <xsl:transform> element.
|
|
2962
4822
|
*
|
|
2963
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.
|
|
2964
4827
|
* @returns {void}
|
|
2965
4828
|
*
|
|
2966
4829
|
* @example
|
|
2967
4830
|
* const parser = new DOMParser();
|
|
2968
4831
|
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
2969
|
-
* processor.importStylesheet(xslDoc);
|
|
4832
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
2970
4833
|
*/
|
|
2971
|
-
importStylesheet(style) {
|
|
4834
|
+
importStylesheet(style, stylesheetUri) {
|
|
2972
4835
|
if (!style) {
|
|
2973
4836
|
throw new TypeError(
|
|
2974
4837
|
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present."
|
|
@@ -2984,11 +4847,14 @@ var XSLTProcessor = class {
|
|
|
2984
4847
|
throw new Error("XSLT stylesheet contains parse errors");
|
|
2985
4848
|
}
|
|
2986
4849
|
this._stylesheet = style;
|
|
2987
|
-
this._engine = new XsltEngine(
|
|
4850
|
+
this._engine = new XsltEngine({
|
|
4851
|
+
stylesheetLoader: this._stylesheetLoader,
|
|
4852
|
+
documentLoader: this._documentLoader
|
|
4853
|
+
});
|
|
2988
4854
|
for (const [key, value] of this._parameters) {
|
|
2989
|
-
this._engine.
|
|
4855
|
+
this._engine.setParameterValue(key, value);
|
|
2990
4856
|
}
|
|
2991
|
-
this._engine.importStylesheet(style);
|
|
4857
|
+
this._engine.importStylesheet(style, stylesheetUri);
|
|
2992
4858
|
}
|
|
2993
4859
|
/**
|
|
2994
4860
|
* Transforms the node source by applying the XSLT stylesheet.
|
|
@@ -3069,6 +4935,45 @@ var XSLTProcessor = class {
|
|
|
3069
4935
|
return null;
|
|
3070
4936
|
}
|
|
3071
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
|
+
}
|
|
3072
4977
|
/**
|
|
3073
4978
|
* Sets a parameter in the XSLT stylesheet.
|
|
3074
4979
|
*
|
|
@@ -3095,7 +5000,7 @@ var XSLTProcessor = class {
|
|
|
3095
5000
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3096
5001
|
this._parameters.set(key, value);
|
|
3097
5002
|
if (this._engine) {
|
|
3098
|
-
this._engine.
|
|
5003
|
+
this._engine.setParameterValue(key, value);
|
|
3099
5004
|
}
|
|
3100
5005
|
}
|
|
3101
5006
|
/**
|
|
@@ -3152,7 +5057,7 @@ var XSLTProcessor = class {
|
|
|
3152
5057
|
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
3153
5058
|
this._parameters.delete(key);
|
|
3154
5059
|
if (this._engine) {
|
|
3155
|
-
|
|
5060
|
+
this._engine.clearParameterValue(key);
|
|
3156
5061
|
}
|
|
3157
5062
|
}
|
|
3158
5063
|
/**
|
|
@@ -3168,12 +5073,19 @@ var XSLTProcessor = class {
|
|
|
3168
5073
|
clearParameters() {
|
|
3169
5074
|
this._parameters.clear();
|
|
3170
5075
|
if (this._engine) {
|
|
3171
|
-
this._engine.
|
|
5076
|
+
this._engine.clearParameterValues();
|
|
3172
5077
|
}
|
|
3173
5078
|
}
|
|
3174
5079
|
/**
|
|
3175
5080
|
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
3176
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
|
+
*
|
|
3177
5089
|
* @returns {void}
|
|
3178
5090
|
*
|
|
3179
5091
|
* @example
|
|
@@ -3242,9 +5154,9 @@ function selectFirst(expression, contextNode, options = {}) {
|
|
|
3242
5154
|
}
|
|
3243
5155
|
|
|
3244
5156
|
// src/index.js
|
|
3245
|
-
var VERSION = "1.
|
|
5157
|
+
var VERSION = "1.1.1";
|
|
3246
5158
|
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
3247
|
-
var isNode = typeof process !== "undefined" && process.versions
|
|
5159
|
+
var isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
3248
5160
|
export {
|
|
3249
5161
|
VERSION,
|
|
3250
5162
|
XPathContext,
|
|
@@ -3259,8 +5171,12 @@ export {
|
|
|
3259
5171
|
isBrowser,
|
|
3260
5172
|
isNativeXSLTSupported,
|
|
3261
5173
|
isNode,
|
|
5174
|
+
isRawText,
|
|
5175
|
+
markRawText,
|
|
3262
5176
|
parse as parseXPath,
|
|
5177
|
+
resolveOutputSettings,
|
|
3263
5178
|
selectFirst as selectFirstXPath,
|
|
3264
|
-
select as selectXPath
|
|
5179
|
+
select as selectXPath,
|
|
5180
|
+
serializeResult
|
|
3265
5181
|
};
|
|
3266
5182
|
//# sourceMappingURL=xslt-processor.js.map
|