xslt-processor 4.8.5 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ _A JavaScript XSLT processor without native library dependencies._
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://github.com/DesignLiquido/xslt-processor/issues" target="_blank">
7
- <img src="https://img.shields.io/github/issues/Designliquido/xslt-processor" />
7
+ <img src="https://img.shields.io/github/issues/DesignLiquido/xslt-processor" />
8
8
  </a>
9
9
  <img src="https://img.shields.io/github/stars/Designliquido/xslt-processor" />
10
10
  <img src="https://img.shields.io/github/forks/Designliquido/xslt-processor" />
@@ -37,7 +37,7 @@ ohpm install xslt-processor
37
37
  yarn add xslt-processor
38
38
  ```
39
39
 
40
- Within your ES2015+ code, import the `Xslt` class, the `XmlParser` class and use this way:
40
+ Within your ES2015+ code, import the `Xslt` class, the `XmlParser` class and use it this way:
41
41
 
42
42
  ```js
43
43
  import { Xslt, XmlParser } from 'xslt-processor'
@@ -80,11 +80,44 @@ const xslt = new Xslt(options);
80
80
  - `cData` (`boolean`, default `true`): resolves CDATA elements in the output. Content under CDATA is resolved as text. This overrides `escape` for CDATA content.
81
81
  - `escape` (`boolean`, default `true`): replaces symbols like `<`, `>`, `&` and `"` by the corresponding [HTML/XML entities](https://www.tutorialspoint.com/xml/xml_character_entities.htm). Can be overridden by `disable-output-escaping`, that also does the opposite, unescaping `&gt;` and `&lt;` by `<` and `>`, respectively.
82
82
  - `selfClosingTags` (`boolean`, default `true`): Self-closes tags that don't have inner elements, if `true`. For instance, `<test></test>` becomes `<test />`.
83
- - `outputMethod` (`string`, default `xml`): Specifies the default output method. if `<xsl:output>` is declared in your XSLT file, this will be overridden. Valid values: `xml`, `html`, `text`, `name`, `xhtml`, `json`, `adaptive`.
83
+ - `outputMethod` (`string`, default `xml`): Specifies the default output method. If `<xsl:output>` is declared in your XSLT file, this will be overridden. Valid values: `xml`, `html`, `text`, `xhtml`, `json`, `adaptive`.
84
84
  - `parameters` (`array`, default `[]`): external parameters that you want to use.
85
85
  - `name`: the parameter name;
86
86
  - `namespaceUri` (optional): the namespace;
87
87
  - `value`: the value.
88
+ - `fetchFunction` (`(uri: string) => Promise<string>`, optional): a custom function for loading external resources referenced by `<xsl:import>` and `<xsl:include>`. Receives the URI and must return the fetched content as a string. Defaults to the global `fetch` API. This is useful for:
89
+ - Denying external loading entirely;
90
+ - Loading from the local filesystem or other non-HTTP sources;
91
+ - Transforming or remapping URIs before fetching.
92
+
93
+ **`fetchFunction` examples:**
94
+
95
+ ```js
96
+ import { readFileSync } from 'fs';
97
+
98
+ // Deny all external loading
99
+ const xslt = new Xslt({
100
+ fetchFunction: async (uri) => {
101
+ throw new Error(`External loading is not allowed: ${uri}`);
102
+ }
103
+ });
104
+
105
+ // Load from local filesystem
106
+ const xslt = new Xslt({
107
+ fetchFunction: async (uri) => {
108
+ return readFileSync(uri, 'utf-8');
109
+ }
110
+ });
111
+
112
+ // Remap URIs before fetching
113
+ const xslt = new Xslt({
114
+ fetchFunction: async (uri) => {
115
+ const remapped = uri.replace('https://example.com/', '/local/stylesheets/');
116
+ const response = await fetch(remapped);
117
+ return response.text();
118
+ }
119
+ });
120
+ ```
88
121
 
89
122
  #### JSON Output Format
90
123
 
@@ -182,10 +215,10 @@ console.log(result2); // "<users><user>John</user></users>" (XML)
182
215
  You can simply add a tag like this:
183
216
 
184
217
  ```html
185
- <script type="application/javascript" src="https://www.unpkg.com/xslt-processor@latest/umd/xslt-processor.global.js"></script>
218
+ <script type="application/javascript" src="https://unpkg.com/xslt-processor@latest/umd/xslt-processor.global.js"></script>
186
219
  ```
187
220
 
188
- All the exports will live under `globalThis.XsltProcessor` and `window.XsltProcessor`. [See a usage example here](https://github.com/DesignLiquido/xslt-processor/blob/main/interactive-tests/xslt.html).
221
+ All the exports will live under `globalThis.XsltProcessor` and `window.XsltProcessor`. [See a usage example here](https://github.com/DesignLiquido/xslt-processor/blob/main/interactive-tests/xslt.html).
189
222
 
190
223
  ## XPath Parser
191
224
 
@@ -204,7 +237,7 @@ import { XPath } from 'xslt-processor'
204
237
  const xPath = new XPath();
205
238
  ```
206
239
 
207
- `XPath` class is an external dependency, [living in its own repository](https://github.com/DesignLiquido/xpath).
240
+ `XPath` class is an external dependency, [living in its own repository](https://github.com/DesignLiquido/xpath).
208
241
 
209
242
  ## Introduction
210
243
 
@@ -215,13 +248,40 @@ XSLT-processor builds on Google's [AJAXSLT](https://github.com/4031651/ajaxslt)
215
248
 
216
249
  This implementation of XSLT operates at the DOM level on its input documents. It internally uses a DOM implementation to create the output document, but usually returns the output document as text stream. The DOM to construct the output document can be supplied by the application, or else an internal minimal DOM implementation is used. This DOM comes with a minimal XML parser that can be used to generate a suitable DOM representation of the input documents if they are present as text.
217
250
 
251
+ ## Building from source
252
+
253
+ The XPath engine lives in a Git submodule at `src/xpath/lib`. A regular `git clone` does **not** fetch it automatically, so the build will fail unless the submodule is initialised.
254
+
255
+ ### Fresh clone
256
+
257
+ ```sh
258
+ git clone --recurse-submodules https://github.com/DesignLiquido/xslt-processor.git
259
+ cd xslt-processor
260
+ yarn install
261
+ yarn build
262
+ ```
263
+
264
+ ### Already cloned without submodules
265
+
266
+ ```sh
267
+ git submodule update --init --recursive
268
+ yarn install
269
+ yarn build
270
+ ```
271
+
272
+ ### Updating the submodule to the latest commit
273
+
274
+ ```sh
275
+ git submodule update --remote src/xpath/lib
276
+ ```
277
+
218
278
  ## Tests and usage examples
219
279
 
220
- New tests are written in Jest an can be run by calling: `yarn test`.
280
+ New tests are written in Jest and can be run by calling: `yarn test`.
221
281
 
222
282
  The files `xslt.html` and `xpath.html` in the directory `interactive-tests` are interactive tests. They can be run directly from the file system; no HTTP server is needed.
223
283
 
224
- Both interactive tests and automatic tests demonstrate the use of the library functions.
284
+ Both interactive tests and automatic tests demonstrate the use of the library functions.
225
285
 
226
286
  ## Conformance
227
287
 
@@ -233,9 +293,9 @@ So far, we have implemented XQuery functions for versions 1.0 and 2.0, but this
233
293
 
234
294
  The DOM implementation is minimal so as to support the XSLT processing, and not intended to be complete.
235
295
 
236
- The implementation is all agnostic about namespaces. It just expects XSLT elements to have tags that carry the `xsl:` prefix, but we disregard all namespace declaration for them.
296
+ The implementation is all agnostic about namespaces. It just expects XSLT elements to have tags that carry the `xsl:` prefix, but we disregard all namespace declarations for them.
237
297
 
238
- [There are a few nonstandard XPath functions](https://github.com/search?q=repo%3ADesignLiquido%2Fxslt-processor%20ext-&type=code).
298
+ [There are a few nonstandard XPath functions](https://github.com/search?q=repo%3ADesignLiquido%2Fxslt-processor%20ext-&type=code).
239
299
 
240
300
  ### HTML Conformance
241
301
 
package/index.d.mts CHANGED
@@ -172,6 +172,7 @@ type XsltOptions = {
172
172
  selfClosingTags: boolean;
173
173
  outputMethod?: 'xml' | 'html' | 'text' | 'xhtml' | 'json' | 'adaptive';
174
174
  parameters?: XsltParameter[];
175
+ fetchFunction?: (uri: string) => Promise<string>;
175
176
  };
176
177
 
177
178
  interface NodeValue {
@@ -760,6 +761,12 @@ declare class Xslt {
760
761
  options: XsltOptions;
761
762
  decimalFormatSettings: XsltDecimalFormatSettings;
762
763
  warningsCallback: (...args: any[]) => void;
764
+ /**
765
+ * Custom fetch function for loading external resources (e.g. xsl:import, xsl:include).
766
+ * Takes a URI and returns the fetched content as a string.
767
+ * Defaults to using the global `fetch` API.
768
+ */
769
+ fetchFunction: (uri: string) => Promise<string>;
763
770
  outputDocument: XDocument;
764
771
  outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml' | 'json' | 'adaptive';
765
772
  outputOmitXmlDeclaration: string;
package/index.d.ts CHANGED
@@ -172,6 +172,7 @@ type XsltOptions = {
172
172
  selfClosingTags: boolean;
173
173
  outputMethod?: 'xml' | 'html' | 'text' | 'xhtml' | 'json' | 'adaptive';
174
174
  parameters?: XsltParameter[];
175
+ fetchFunction?: (uri: string) => Promise<string>;
175
176
  };
176
177
 
177
178
  interface NodeValue {
@@ -760,6 +761,12 @@ declare class Xslt {
760
761
  options: XsltOptions;
761
762
  decimalFormatSettings: XsltDecimalFormatSettings;
762
763
  warningsCallback: (...args: any[]) => void;
764
+ /**
765
+ * Custom fetch function for loading external resources (e.g. xsl:import, xsl:include).
766
+ * Takes a URI and returns the fetched content as a string.
767
+ * Defaults to using the global `fetch` API.
768
+ */
769
+ fetchFunction: (uri: string) => Promise<string>;
763
770
  outputDocument: XDocument;
764
771
  outputMethod: 'xml' | 'html' | 'text' | 'name' | 'xhtml' | 'json' | 'adaptive';
765
772
  outputOmitXmlDeclaration: string;
package/index.js CHANGED
@@ -10973,7 +10973,7 @@ function xmlElementLogicTrivial(node, buffer, options) {
10973
10973
  if (!attribute) {
10974
10974
  continue;
10975
10975
  }
10976
- if (options.outputMethod === "html" && (attribute.nodeName === "xmlns" || attribute.nodeName.startsWith("xmlns:"))) {
10976
+ if (options.outputMethod === "html" && attribute.nodeName === "xmlns" && attribute.nodeValue === "http://www.w3.org/1999/xhtml") {
10977
10977
  continue;
10978
10978
  }
10979
10979
  if (attribute.nodeName && attribute.nodeValue !== null && attribute.nodeValue !== void 0) {
@@ -13908,6 +13908,16 @@ var Xslt = class {
13908
13908
  this.firstTemplateRan = false;
13909
13909
  this.forwardsCompatible = false;
13910
13910
  this.warningsCallback = console.warn.bind(console);
13911
+ this.fetchFunction = options.fetchFunction || ((uri) => __async(this, null, function* () {
13912
+ const globalFetch = typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis.fetch : null;
13913
+ if (!globalFetch) {
13914
+ throw new Error(
13915
+ "No global fetch implementation available. Please provide options.fetchFunction or use a runtime that exposes globalThis.fetch."
13916
+ );
13917
+ }
13918
+ const response = yield globalFetch(uri);
13919
+ return response.text();
13920
+ }));
13911
13921
  this.streamingProcessor = new StreamingProcessor({
13912
13922
  xPath: this.xPath,
13913
13923
  version: ""
@@ -14572,6 +14582,19 @@ var Xslt = class {
14572
14582
  const value = xmlValueLegacyBehavior(documentFragment);
14573
14583
  if (output) {
14574
14584
  domSetAttribute(output, name, value);
14585
+ if (name.includes(":")) {
14586
+ const prefix = name.split(":")[0];
14587
+ if (prefix !== "xmlns") {
14588
+ const explicitNs = xmlGetAttribute(template, "namespace");
14589
+ const nsUri = explicitNs || this.resolveNamespaceUriForPrefix(template, prefix);
14590
+ if (nsUri) {
14591
+ const nsAttr = `xmlns:${prefix}`;
14592
+ if (!this.isNamespaceDeclaredOnAncestor(output, nsAttr, nsUri)) {
14593
+ domSetAttribute(output, nsAttr, nsUri);
14594
+ }
14595
+ }
14596
+ }
14597
+ }
14575
14598
  }
14576
14599
  });
14577
14600
  }
@@ -14674,6 +14697,12 @@ var Xslt = class {
14674
14697
  domAppendChild(destination, node);
14675
14698
  } else if (source.nodeType == DOM_ATTRIBUTE_NODE) {
14676
14699
  domSetAttribute(destination, source.nodeName, source.nodeValue);
14700
+ if (source.prefix && source.namespaceUri && source.prefix !== "xmlns" && !source.nodeName.startsWith("xmlns")) {
14701
+ const nsAttr = `xmlns:${source.prefix}`;
14702
+ if (!this.isNamespaceDeclaredOnAncestor(destination, nsAttr, source.namespaceUri)) {
14703
+ domSetAttribute(destination, nsAttr, source.namespaceUri);
14704
+ }
14705
+ }
14677
14706
  }
14678
14707
  return null;
14679
14708
  }
@@ -15341,16 +15370,6 @@ var Xslt = class {
15341
15370
  xsltImportOrInclude(context, template, output, isImport) {
15342
15371
  return __async(this, null, function* () {
15343
15372
  const elementName = isImport ? "xsl:import" : "xsl:include";
15344
- const [major, minor] = process.versions.node.split(".").map(Number);
15345
- if (major <= 17 && minor < 5) {
15346
- throw new Error(`Your Node.js version does not support \`<${elementName}>\`. If possible, please update your Node.js version to at least version 17.5.0.`);
15347
- }
15348
- if (!global.globalThis.fetch) {
15349
- global.globalThis.fetch = fetch;
15350
- global.globalThis.Headers = Headers;
15351
- global.globalThis.Request = Request;
15352
- global.globalThis.Response = Response;
15353
- }
15354
15373
  const hrefAttributeFind = template.childNodes.filter((n) => n.nodeName === "href");
15355
15374
  if (hrefAttributeFind.length <= 0) {
15356
15375
  throw new Error(`<${elementName}> with no href attribute defined.`);
@@ -15360,8 +15379,7 @@ var Xslt = class {
15360
15379
  if (this.importedStylesheets.has(href)) {
15361
15380
  return;
15362
15381
  }
15363
- const fetchTest = yield global.globalThis.fetch(href);
15364
- const fetchResponse = yield fetchTest.text();
15382
+ const fetchResponse = yield this.fetchFunction(href);
15365
15383
  const includedXslt = this.xmlParser.xmlParse(fetchResponse);
15366
15384
  const currentDepth = this.styleSheetStack.length > 0 ? this.styleSheetStack[this.styleSheetStack.length - 1].importDepth : 0;
15367
15385
  const metadata = {
@@ -17608,6 +17626,12 @@ var Xslt = class {
17608
17626
  const name = attribute.nodeName;
17609
17627
  const value = this.xsltAttributeValue(attribute.nodeValue, elementContext);
17610
17628
  domSetAttribute(newNode, name, value);
17629
+ if (attribute.prefix && attribute.namespaceUri && attribute.prefix !== "xmlns" && !attribute.nodeName.startsWith("xmlns")) {
17630
+ const nsAttr = `xmlns:${attribute.prefix}`;
17631
+ if (!this.isNamespaceDeclaredOnAncestor(newNode, nsAttr, attribute.namespaceUri)) {
17632
+ domSetAttribute(newNode, nsAttr, attribute.namespaceUri);
17633
+ }
17634
+ }
17611
17635
  }
17612
17636
  break;
17613
17637
  default: