@tradik/xslt-processor 1.0.2 → 1.1.1

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