Skip to content

Commit

Permalink
Partially implementing <xsl:import>.
Browse files Browse the repository at this point in the history
  • Loading branch information
leonelsanchesdasilva committed Sep 21, 2024
1 parent a7c518a commit 7ad8343
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ XSLT-processor TODO
* XSL:number
* `attribute-set`, `decimal-format`, etc. (check `src/xslt.ts`)
* `/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".
* Implement `<xsl:import>` with correct template precedence.

Help is much appreciated. It seems to currently work for most of our purposes, but fixes and additions are always welcome!
40 changes: 39 additions & 1 deletion src/xslt/xslt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class Xslt {
outputMethod: 'xml' | 'html' | 'text' | 'name';
outputOmitXmlDeclaration: string;
version: string;
firstTemplateRan: boolean;

constructor(
options: Partial<XsltOptions> = {
Expand Down Expand Up @@ -110,6 +111,7 @@ export class Xslt {
digit: '#',
patternSeparator: ';'
};
this.firstTemplateRan = false;
}

/**
Expand Down Expand Up @@ -210,7 +212,8 @@ export class Xslt {
await this.xsltIf(context, template, output);
break;
case 'import':
throw new Error(`not implemented: ${template.localName}`);
await this.xsltImport(context, template, output);
break;
case 'include':
await this.xsltInclude(context, template, output);
break;
Expand Down Expand Up @@ -636,6 +639,40 @@ export class Xslt {
}
}

/**
* Implements `<xsl:import>`. For now the code is nearly identical to `<xsl:include>`, but there's
* no precedence evaluation implemented yet.
* @param context The Expression Context.
* @param template The template.
* @param output The output.
*/
protected async xsltImport(context: ExprContext, template: XNode, output?: XNode) {
if (this.firstTemplateRan) {
throw new Error('<xsl:import> should be the first child node of <xsl:stylesheet> or <xsl:transform>.');
}

// We need to test here whether `window.fetch` is available or not.
// If it is a browser environemnt, it should be.
// Otherwise, we will need to import an equivalent library, like 'node-fetch'.
if (!global.globalThis.fetch) {
global.globalThis.fetch = fetch as any;

Check warning on line 658 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
global.globalThis.Headers = Headers as any;

Check warning on line 659 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
global.globalThis.Request = Request as any;

Check warning on line 660 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
global.globalThis.Response = Response as any;

Check warning on line 661 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 662 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

const hrefAttributeFind = template.childNodes.filter(n => n.nodeName === 'href');
if (hrefAttributeFind.length <= 0) {
throw new Error('<xsl:import> with no href attribute defined.');

Check warning on line 666 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 667 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const hrefAttribute = hrefAttributeFind[0];

const fetchTest = await global.globalThis.fetch(hrefAttribute.nodeValue);
const fetchResponse = await fetchTest.text();
const includedXslt = this.xmlParser.xmlParse(fetchResponse);
await this.xsltChildNodes(context, includedXslt.childNodes[0], output);
}

/**
* Implements `xsl:include`.
* @param context The Expression Context.
Expand Down Expand Up @@ -765,6 +802,7 @@ export class Xslt {
// in relative path, we force a 'self-and-siblings' axis.
const nodes = this.xsltMatch(match, context, 'self-and-siblings');
if (nodes.length > 0) {
this.firstTemplateRan = true;
if (!context.inApplyTemplates) {
context.baseTemplateMatched = true;
}
Expand Down
39 changes: 39 additions & 0 deletions tests/xslt/import.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import assert from 'assert';

import { XmlParser } from "../../src/dom";
import { Xslt } from "../../src/xslt";

describe('xsl:import', () => {
it('Trivial', async () => {
const xmlSource = `<html></html>`;

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">
<xsl:output method="html" indent="yes"/>
<xsl:import href="https://raw.githubusercontent.com/DesignLiquido/xslt-processor/main/examples/head.xsl"/>
</xsl:stylesheet>`;

const xsltClass = new Xslt();
const xmlParser = new XmlParser();
const xml = xmlParser.xmlParse(xmlSource);
const xslt = xmlParser.xmlParse(xsltSource);
const resultingXml = await xsltClass.xsltProcess(xml, xslt);
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>');
});

it('Not the first child of `<xsl:stylesheet>` or `<xsl:transform>`', async () => {
const xmlSource = `<html></html>`;

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">
<xsl:template match="/">
Anything
</xsl:template>
<xsl:import href="https://raw.githubusercontent.com/DesignLiquido/xslt-processor/main/examples/head.xsl"/>
</xsl:stylesheet>`;

const xsltClass = new Xslt();
const xmlParser = new XmlParser();
const xml = xmlParser.xmlParse(xmlSource);
const xslt = xmlParser.xmlParse(xsltSource);
assert.rejects(async () => await xsltClass.xsltProcess(xml, xslt));
});
});

0 comments on commit 7ad8343

Please sign in to comment.