v0.3.1 with Saxon fully working

This commit is contained in:
2026-06-05 03:19:04 +02:00
parent fc828363f0
commit 71f2f3d44b
51 changed files with 12683 additions and 1 deletions

View File

@@ -0,0 +1,122 @@
import { beforeAll, describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import vm from 'node:vm';
import {
availableTransformEngines,
runTransformation,
} from '../src/transform/transformService';
import { compileXsltTextToSefJson } from '../src/transform/saxonJsDynamicCompiler';
const xmlText = `<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="b1"><title>Practical XSLT</title><author>Ada</author></book>
<book id="b2"><title>Browser XML</title><author>Lin</author></book>
</library>`;
const xslt10Text = `<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/library">
<titles><xsl:for-each select="book"><title><xsl:value-of select="title"/></title></xsl:for-each></titles>
</xsl:template>
</xsl:stylesheet>`;
const xslt20Text = `<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/library">
<books><xsl:value-of select="upper-case(string-join(book/title, '|'))"/></books>
</xsl:template>
</xsl:stylesheet>`;
const xslt30Text = `<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="book/title/text()">
<xsl:value-of select="upper-case(.)"/>
</xsl:template>
</xsl:stylesheet>`;
beforeAll(() => {
if (window.SaxonJS) return;
const scriptPath = resolve(process.cwd(), 'public/vendor/saxon/SaxonJS2.js');
const script = readFileSync(scriptPath, 'utf8');
vm.runInContext(script, vm.createContext(window));
});
describe('transform engine registry', () => {
it('keeps the native browser XSLTProcessor engine selectable', () => {
expect(availableTransformEngines.map((engine) => engine.id)).toContain(
'native-xsltprocessor'
);
});
it('exposes the dynamic SaxonJS engine for XSLT 2.0/3.0', () => {
expect(availableTransformEngines.map((engine) => engine.id)).toContain(
'saxon-js-dynamic'
);
});
});
describe('SaxonJS dynamic compiler', () => {
it('compiles raw XSLT text to SEF JSON', async () => {
const sef = await compileXsltTextToSefJson(xslt30Text);
const parsed = JSON.parse(sef) as { N?: string; target?: string };
expect(parsed.N).toBe('package');
expect(parsed.target).toBe('JS');
});
it('runs an XSLT 1.0 stylesheet', async () => {
const result = await runTransformation({
xmlText,
xsltText: xslt10Text,
engine: 'saxon-js-dynamic',
});
expect(
result.diagnostics.filter((item) => item.severity === 'error')
).toHaveLength(0);
expect(result.output).toContain('<title>Practical XSLT</title>');
expect(result.output).toContain('<title>Browser XML</title>');
});
it('runs an XSLT 2.0 stylesheet with XPath 2.0 functions', async () => {
const result = await runTransformation({
xmlText,
xsltText: xslt20Text,
engine: 'saxon-js-dynamic',
});
expect(
result.diagnostics.filter((item) => item.severity === 'error')
).toHaveLength(0);
expect(result.output).toContain('PRACTICAL XSLT|BROWSER XML');
});
it('runs an XSLT 3.0 stylesheet with xsl:mode on-no-match', async () => {
const result = await runTransformation({
xmlText,
xsltText: xslt30Text,
engine: 'saxon-js-dynamic',
});
expect(
result.diagnostics.filter((item) => item.severity === 'error')
).toHaveLength(0);
expect(result.output).toContain('<title>PRACTICAL XSLT</title>');
expect(result.output).toContain('<title>BROWSER XML</title>');
});
it('returns diagnostics for invalid XSLT', async () => {
const result = await runTransformation({
xmlText,
xsltText: '<xsl:stylesheet version="3.0">',
engine: 'saxon-js-dynamic',
});
expect(result.diagnostics.some((item) => item.severity === 'error')).toBe(
true
);
});
});