Skip to content

Commit 12e3609

Browse files
Partially implementing <xsl:import>. (#102)
1 parent a7c518a commit 12e3609

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ XSLT-processor TODO
66
* XSL:number
77
* `attribute-set`, `decimal-format`, etc. (check `src/xslt.ts`)
88
* `/html/body//ul/li|html/body//ol/li` has `/html/body//ul/li` evaluated by this XPath implementation as "absolute", and `/html/body//ol/li` as "relative". Both should be evaluated as "absolute".
9+
* Implement `<xsl:import>` with correct template precedence.
910

1011
Help is much appreciated. It seems to currently work for most of our purposes, but fixes and additions are always welcome!

src/xslt/xslt.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class Xslt {
7878
outputMethod: 'xml' | 'html' | 'text' | 'name';
7979
outputOmitXmlDeclaration: string;
8080
version: string;
81+
firstTemplateRan: boolean;
8182

8283
constructor(
8384
options: Partial<XsltOptions> = {
@@ -110,6 +111,7 @@ export class Xslt {
110111
digit: '#',
111112
patternSeparator: ';'
112113
};
114+
this.firstTemplateRan = false;
113115
}
114116

115117
/**
@@ -210,7 +212,8 @@ export class Xslt {
210212
await this.xsltIf(context, template, output);
211213
break;
212214
case 'import':
213-
throw new Error(`not implemented: ${template.localName}`);
215+
await this.xsltImport(context, template, output);
216+
break;
214217
case 'include':
215218
await this.xsltInclude(context, template, output);
216219
break;
@@ -636,6 +639,40 @@ export class Xslt {
636639
}
637640
}
638641

642+
/**
643+
* Implements `<xsl:import>`. For now the code is nearly identical to `<xsl:include>`, but there's
644+
* no precedence evaluation implemented yet.
645+
* @param context The Expression Context.
646+
* @param template The template.
647+
* @param output The output.
648+
*/
649+
protected async xsltImport(context: ExprContext, template: XNode, output?: XNode) {
650+
if (this.firstTemplateRan) {
651+
throw new Error('<xsl:import> should be the first child node of <xsl:stylesheet> or <xsl:transform>.');
652+
}
653+
654+
// We need to test here whether `window.fetch` is available or not.
655+
// If it is a browser environemnt, it should be.
656+
// Otherwise, we will need to import an equivalent library, like 'node-fetch'.
657+
if (!global.globalThis.fetch) {
658+
global.globalThis.fetch = fetch as any;
659+
global.globalThis.Headers = Headers as any;
660+
global.globalThis.Request = Request as any;
661+
global.globalThis.Response = Response as any;
662+
}
663+
664+
const hrefAttributeFind = template.childNodes.filter(n => n.nodeName === 'href');
665+
if (hrefAttributeFind.length <= 0) {
666+
throw new Error('<xsl:import> with no href attribute defined.');
667+
}
668+
const hrefAttribute = hrefAttributeFind[0];
669+
670+
const fetchTest = await global.globalThis.fetch(hrefAttribute.nodeValue);
671+
const fetchResponse = await fetchTest.text();
672+
const includedXslt = this.xmlParser.xmlParse(fetchResponse);
673+
await this.xsltChildNodes(context, includedXslt.childNodes[0], output);
674+
}
675+
639676
/**
640677
* Implements `xsl:include`.
641678
* @param context The Expression Context.
@@ -765,6 +802,7 @@ export class Xslt {
765802
// in relative path, we force a 'self-and-siblings' axis.
766803
const nodes = this.xsltMatch(match, context, 'self-and-siblings');
767804
if (nodes.length > 0) {
805+
this.firstTemplateRan = true;
768806
if (!context.inApplyTemplates) {
769807
context.baseTemplateMatched = true;
770808
}

tests/xslt/import.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import assert from 'assert';
2+
3+
import { XmlParser } from "../../src/dom";
4+
import { Xslt } from "../../src/xslt";
5+
6+
describe('xsl:import', () => {
7+
it('Trivial', async () => {
8+
const xmlSource = `<html></html>`;
9+
10+
const xsltSource = `<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
11+
<xsl:output method="html" indent="yes"/>
12+
<xsl:import href="https://raw.githubusercontent.com/DesignLiquido/xslt-processor/main/examples/head.xsl"/>
13+
</xsl:stylesheet>`;
14+
15+
const xsltClass = new Xslt();
16+
const xmlParser = new XmlParser();
17+
const xml = xmlParser.xmlParse(xmlSource);
18+
const xslt = xmlParser.xmlParse(xsltSource);
19+
const resultingXml = await xsltClass.xsltProcess(xml, xslt);
20+
assert.equal(resultingXml, '<html><head><link rel="stylesheet" type="text/css" href="style.css"><title/></head><body><div id="container"><div id="header"><div id="menu"><ul><li><a href="#" class="active">Home</a></li><li><a href="#">about</a></li></ul></div></div></div></body></html>');
21+
});
22+
23+
it('Not the first child of `<xsl:stylesheet>` or `<xsl:transform>`', async () => {
24+
const xmlSource = `<html></html>`;
25+
26+
const xsltSource = `<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
27+
<xsl:template match="/">
28+
Anything
29+
</xsl:template>
30+
<xsl:import href="https://raw.githubusercontent.com/DesignLiquido/xslt-processor/main/examples/head.xsl"/>
31+
</xsl:stylesheet>`;
32+
33+
const xsltClass = new Xslt();
34+
const xmlParser = new XmlParser();
35+
const xml = xmlParser.xmlParse(xmlSource);
36+
const xslt = xmlParser.xmlParse(xsltSource);
37+
assert.rejects(async () => await xsltClass.xsltProcess(xml, xslt));
38+
});
39+
});

0 commit comments

Comments
 (0)