Project

Profile

Help

Support #6101

closed

xsl:import pathing

Added by John Cotton 11 months ago. Updated 7 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Category:
-
Sprint/Milestone:
-
Start date:
2023-06-28
Due date:
% Done:

100%

Estimated time:
Applies to JS Branch:
2, Trunk
Fix Committed on JS Branch:
2, Trunk
Fixed in JS Release:
SEF Generated with:
Platforms:
Company:
-
Contact person:
-
Additional contact persons:
-

Description

Hello

I'm running some Node on AWS Lambda, but struggling to get xsl:imports to work there.

My sources are both strings, with the XSL being XSL rather than compiled SEF.

This code works locally, when supplying a (Windows) path as TEMP_DIR.

const transformXml = (xml, xsl) => {
	try {
		const env = SaxonJS.getPlatform();
		const xslDoc = env.parseXmlFromString(xsl);

		// hack: avoid error "Required cardinality of value of parameter $static-base-uri is exactly one; supplied value is empty"
		xslDoc._saxonBaseUri = `file://${TEMP_DIR}`;

		const sef = SaxonJS.compile(xslDoc);

		const result = SaxonJS.transform({
			stylesheetInternal: sef,
			sourceText: xml,
			logLevel: 1,
		});

		return SaxonJS.serialize(result.principalResult, { method: "xml" });
	} catch (error) {
		console.error(`Transform err: ${error.message}`);
	}
};

However, on Lambda, when the files that are xsl:import[ed] sit in /tmp, I get a "can't load file" error.

Where am I going wrong? Is SaxonJS.getResource a better approach? If so, could you point me to an example - I'm struggling to get something based on your docs working.

Regards John

Actions #1

Updated by Martin Honnen 11 months ago

It seems const sef = SaxonJS.compile(xslDoc); is not part of the publicly documented API. Do you get the error on the compile call on Node.js? Or later, on the attempt to transform? I would try to check/infer whether the compile call takes an argument or option, similar to the command line, to declare -relocate:on. That might help to have the files found.

Actions #2

Updated by Martin Honnen 11 months ago

You haven't shown in all detail how your base stylesheet URI looks for the /tmp directory, I would think it needs to be e.g. file:/tmp/.

What should be possible is to use SaxonJS.XPath.evaluate to call fn:transform:

'use strict';

const SaxonJS = require('saxon-js');

const transformXml = (xml, xslt, xsltBaseUri) => {
	try {

		const result = SaxonJS.XPath.evaluate(`
			  transform(
				  map {
					  'stylesheet-text' : $xslt,
					  'stylesheet-base-uri' : $xsltBaseUri,
					  'source-node' : parse-xml($xml),
					  'delivery-format' : 'raw'
				  }
			  )
		  `,
			[],
			{
				params: {
					xml: xml,
					xslt: xslt,
					xsltBaseUri: xsltBaseUri
				}
			}

		);

		return SaxonJS.serialize(result.output, { method: "xml" });
	} catch (error) {
		console.error(`Transform err: ${error.message}`);
	}
};

const xml = `<root>
  <item>b</item>
  <item>c</item>
  <item>a</item>
</root>`;

const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:import href="module-test1.xsl"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output indent="yes"/>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:copy>
	  <xsl:apply-templates/>
	  <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
	</xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>`;

console.log(transformXml(xml, xslt, `file:///C:/Users/username/AppData/Local/Temp/`));
Actions #3

Updated by John Cotton 11 months ago

Thanks for replying Martin.

No, there seems no public documentation on that, but I found it somewhere and it certainly works.

Actions #4

Updated by Martin Honnen 11 months ago

If I use the following adaption of your code with the undocumented API I can run it successfully under both Windows and Linux:

'use strict';

const SaxonJS = require('saxon-js');

const url = require('url');


const transformXml = (xml, xslt, xsltBaseUri) => {
	try {
		const env = SaxonJS.getPlatform();
		const xslDoc = env.parseXmlFromString(xslt);

		xslDoc._saxonBaseUri = xsltBaseUri; 

		const sef = SaxonJS.compile(xslDoc);

		const result = SaxonJS.transform({
			stylesheetInternal: sef,
			sourceText: xml,
			logLevel: 1,
		});

		return SaxonJS.serialize(result.principalResult, { method: "xml" });
	} catch (error) {
		console.error(`Transform err: ${error.message}`);
	}
};

const xml = `<root>
  <item>b</item>
  <item>c</item>
  <item>a</item>
</root>`;

const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:import href="module-test1.xsl"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output indent="yes"/>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:copy>
	  <xsl:apply-templates/>
	  <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
	</xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>`;

console.log(process.env.TEMP)

const temp_dir = process.env.TEMP;

var temp_dir_url = url.pathToFileURL(temp_dir).href;

if (!temp_dir_url.endsWith('/')) {
	temp_dir_url += '/';
}

console.log(transformXml(xml, xslt, temp_dir_url));

It assumes an environment variable TEMP is set to a directory where the module-test1.xsl is found. Somehow WSL Ubuntu Linux or perhaps Ubuntu Linux in general does not have a predefined TMP o TEMP environment variable.

Actions #5

Updated by John Cotton 11 months ago

Really appreciate the code, Martin, thank you.

However, the result is the same.

2023-06-28T10:21:47.338+01:00	2023-06-28T09:21:47.338Z a6e0ec33-4751-4e2d-9850-8e487da51dec INFO baseURI set to: file:///tmp

2023-06-28T10:21:47.338+01:00	2023-06-28T09:21:47.338Z a6e0ec33-4751-4e2d-9850-8e487da51dec INFO fs.existsSync("/tmp/landing-pages.xsl") true

2023-06-28T10:21:47.692+01:00	2023-06-28T09:21:47.692Z a6e0ec33-4751-4e2d-9850-8e487da51dec ERROR Transform err: xsl:import of file:///landing-pages.xsl failed:Cannot read file file:///landing-pages.xsl - ENOENT: no such file or directory, open '/landing-pages.xsl'
Actions #6

Updated by John Cotton 11 months ago

Seemingly, it's because the Transform is not looking in the correct place. Question is: how do I get it to do that?

Actions #7

Updated by John Cotton 11 months ago

const temp_dir = process.env.TEMP;

..is exactly what I have, although no trailing backslash.

But, even with one, the result is the same.

2023-06-28T10:28:02.887+01:00	2023-06-28T09:28:02.887Z bcdf61bd-14b3-468b-b26f-1c3fd3e58652 INFO baseURI set to: file:///tmp/

2023-06-28T10:28:02.887+01:00	2023-06-28T09:28:02.887Z bcdf61bd-14b3-468b-b26f-1c3fd3e58652 INFO fs.existsSync("/tmp//landing-pages.xsl") true

2023-06-28T10:28:03.231+01:00	2023-06-28T09:28:03.231Z bcdf61bd-14b3-468b-b26f-1c3fd3e58652 ERROR Transform err: xsl:import of file:///landing-pages.xsl failed:Cannot read file file:///landing-pages.xsl - ENOENT: no such file or directory, open '/landing-pages.xsl'
Actions #8

Updated by John Cotton 11 months ago

FYI, this works:

<xsl:import href="/tmp/landing-pages.xsl" />

But, I don't want to include the path since the same stylesheet is used in another application.

Actions #9

Updated by Martin Honnen 11 months ago

Yes, I am sorry, I don't know how to use that API, as I commented, I guess the -relocate:on setting from the command line somehow needs to be set from the API but I don't know where/how and the minimized source code of the released module is hard to read.

Wait whether someone from Saxonica tells you whether and how you can use that API for your use case.

Actions #10

Updated by John Cotton 11 months ago

Thanks Martin. I appreciate your replies.

Actions #11

Updated by Norm Tovey-Walsh 11 months ago

Wait whether someone from Saxonica tells you whether and how you can
use that API for your use case.

I’m afraid that API is only accidentally visible. We have plans to make
that an official API, but it wasn’t supposed to be visible yet. It’s
undocumented because the design is unfinished.

You should compile SEF files in advance or use the fn:transform function
for the time being.

Sorry about that.

Be seeing you,
norm

--
Norm Tovey-Walsh
Saxonica

Actions #12

Updated by John Cotton 11 months ago

Thanks to both.

I finally got it working with Martin's

const result = SaxonJS.XPath.evaluate(` ....

It seems the trailing slash on the BaseUri is critical. With it, it works. Without it, it fails and (slightly oddly) the path doesn't get reported in the error (ie the unfound file:///tmpimported.xsl gets reported as file:///imported.xsl).

Actions #13

Updated by John Cotton 11 months ago

John Cotton wrote in #note-12:

Thanks to both.

I finally got it working with Martin's

const result = SaxonJS.XPath.evaluate(` ....

It seems the trailing slash on the BaseUri is critical. With it, it works. Without it, it fails and (slightly oddly) the path doesn't get reported in the error (ie the unfound file:///tmpimported.xsl gets reported as file:///imported.xsl).

Actions #14

Updated by Martin Honnen 11 months ago

John Cotton wrote in #note-12:

Thanks to both.

I finally got it working with Martin's

const result = SaxonJS.XPath.evaluate(` ....

It seems the trailing slash on the BaseUri is critical. With it, it works. Without it, it fails and (slightly oddly) the path doesn't get reported in the error (ie the unfound file:///tmpimported.xsl gets reported as file:///imported.xsl).

You can try resolve-uri('imported.xsl', 'file:///tmp') with other XPath 2/XSLT 2 or later or XQuery implementations like BaseX, the result is indeed file:///imported.xsl. So file URI resolution if you have a file URI denoting a folder or directory as the base URI is only ending up with a URI in that directory if the base URI ends in a slash.

Actions #15

Updated by Norm Tovey-Walsh 7 months ago

  • Status changed from New to Resolved
  • Applies to JS Branch 2, Trunk added
  • Fix Committed on JS Branch 2, Trunk added

I've removed the compile method from the public API. When the design is complete and we're ready to document it, we can put it back.

Actions #16

Updated by Debbie Lockett 7 months ago

  • Status changed from Resolved to Closed
  • % Done changed from 0 to 100
  • Fixed in JS Release set to SaxonJS 2.6

Bug fix applied in the SaxonJS 2.6 maintenance release.

Please register to edit this issue

Also available in: Atom PDF Tracking page