Project

Profile

Help

Bug #6132

open

documentPool passed to SaxonJS.transform doesn't seem to be used if the stylesheet uses fn:transform or xsl:evaluate

Added by Martin Honnen over 1 year ago. Updated over 1 year ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
API
Sprint/Milestone:
Start date:
2023-07-18
Due date:
% Done:

0%

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

Description

For SaxonJS.transform, I can pass a documentPool, mapping URLs to resources, to allow preloading resources or loading resources from a string.

An example of that is e.g. a stylesheet (shown here as XSLT, for SaxonJS it will be compiled to sef.json)

<?xml version="1.0" encoding="utf-8"?>
<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:template name="xsl:initial-template">
    <xsl:sequence select="doc('doc1.xml') => serialize(map{'method':'xml'})"/>
  </xsl:template>

</xsl:stylesheet>

and (for Node.js) Javascript code like

const path = require('path');

const url = require('url');


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

var xmlResource1 = SaxonJS.getResource({'type': 'xml', 'text' : '<root>foo</root>'  });

var doc1Uri = 'doc1.xml';

var docPool = {};

xmlResource1.then(doc => { docPool[url.pathToFileURL(path.resolve('.', doc1Uri))] = doc; })
.then(() => {
  //console.log(docPool);
  SaxonJS.transform({ documentPool: docPool, stylesheetLocation: 'documentPoolTest1.xsl.sef.json' }, 'async').then(result => {
    console.log(SaxonJS.serialize(result.principalResult));
  });
});

When I run that with Node I get e.g.

<?xml version="1.0" encoding="UTF-8"?>&lt;root&gt;foo&lt;/root&gt;

so the doc1.xml is resolved from my passed in documentPool property.

However, when I do the same with an XSLT stylesheet having a function exposing xsl:evaluate with a public function and then call that function with SaxonJS.transform to pass in e.g. a string doing doc("doc1.xml") it seems the documentPool is not taken into account and instead I get an error that the file doc1.xml is not found:

Transformation failure: Error FODC0002 at exposeXslEvaluate.xsl#17
  Cannot read file file:///C:/Users/marti/OneDrive/Documents/xslt/saxonjs-documentPool/doc1.xml - ENOENT: no such file or directory, open 'C:\Users\marti\OneDrive\Documents\xslt\saxonjs-documentPool\doc1.xml'

node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^
Error
    at new L (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4109:549)
    at Object.readFile (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4713:171)
    at Object.readFile (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4108:74)
    at C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4340:316
    at a (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4338:294)
    at Object.Ec (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4340:280)
    at doc (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4513:298)
    at C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4632:444
    at Object.I [as evaluate] (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4600:203)
    at Object.evaluateXDM (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4933:464)
    at C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4630:290
    at Object.push (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4390:245)
    at e (C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:4987:320)
    at C:\Users\marti\AppData\Roaming\npm\node_modules\saxon-js\SaxonJS2N.js:5015:342 {
  message: "Cannot read file file:///C:/Users/marti/OneDrive/Documents/xslt/saxonjs-documentPool/doc1.xml - ENOENT: no such file or directory, open 'C:\\Users\\marti\\OneDrive\\Documents\\xslt\\saxonjs-documentPool\\doc1.xml'",
  name: 'XError',
  code: 'FODC0002',
  xsltLineNr: '17',
  xsltModule: 'exposeXslEvaluate.xsl'
}

A sample XSLT is e.g.

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:mf="http://example.com/mf"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all">

  <xsl:function name="mf:eval" as="item()*" visibility="public">
    <xsl:param name="expression" as="xs:string"/>
    <xsl:param name="context-item" as="item()?"/>
    <xsl:param name="params" as="map(xs:QName, item()*)"/>
    <xsl:evaluate xpath="$expression" context-item="$context-item" with-params="$params"/>
  </xsl:function>

  <xsl:function name="mf:eval" as="item()*" visibility="public">
    <xsl:param name="expression" as="xs:string"/>
    <xsl:evaluate xpath="$expression" context-item="()" with-params="map{}"/>
  </xsl:function>

  <xsl:function name="mf:eval" as="item()*" visibility="public">
    <xsl:param name="expression" as="xs:string"/>
    <xsl:param name="context-item" as="item()?"/>
    <xsl:evaluate xpath="$expression" context-item="$context-item" with-params="map{}"/>
  </xsl:function>

</xsl:stylesheet>

the Javascript code is e.g.

const path = require('path');

const url = require('url');


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

var xmlResource1 = SaxonJS.getResource({'type': 'xml', 'text' : '<root>foo</root>'  });

var doc1Uri = 'doc1.xml';

var docPool = {};

xmlResource1.then(doc => { docPool[url.pathToFileURL(path.resolve('.', doc1Uri))] = doc; })
.then(() => {
  console.log(docPool);
  SaxonJS.transform({
      documentPool: docPool,
      stylesheetLocation: 'exposeXslEvaluate.xsl.sef.json',
      stylesheetBaseURI: url.pathToFileURL(path.resolve('.', 'exposeXslEvaluate.xsl')),
      initialFunction: 'Q{http://example.com/mf}eval',
      functionParams: ['doc("doc1.xml")'],
      destination: 'raw'
    }, 'async').then(result => {
    console.log(SaxonJS.serialize(result.principalResult));
  });
});

Files

documentPoolTest1.xsl (394 Bytes) documentPoolTest1.xsl Martin Honnen, 2023-07-18 18:51
documentPoolTest1.xsl.sef.json (976 Bytes) documentPoolTest1.xsl.sef.json Martin Honnen, 2023-07-18 18:51
exposeXslEvaluate.xsl (1.04 KB) exposeXslEvaluate.xsl Martin Honnen, 2023-07-18 18:51
exposeXslEvaluate.xsl.sef.json (3.98 KB) exposeXslEvaluate.xsl.sef.json Martin Honnen, 2023-07-18 18:51
testDocumentPool1.js (587 Bytes) testDocumentPool1.js direct use of documentPool with SaxonJS.transform works Martin Honnen, 2023-07-18 18:51
testDocumentPoolWithXslEvaluate1.js (816 Bytes) testDocumentPoolWithXslEvaluate1.js test case passes in documentPool to SaxonJS.transform but fails to resolve file for exposeXslEvaluate.xsl and some XPath expression accessing the document Martin Honnen, 2023-07-18 18:51
Actions #1

Updated by Martin Honnen over 1 year ago

Just for comparison, SaxonJ (which has a completely different API) does allow me to set up a ResourceResolver (comparable to a documentPool) that is then used for direct XSLT evaluation as well as indirect XPath evaluation in a stylesheet exposing xsl:evaluate as a function to be called with callFunction: https://github.com/martin-honnen/SaxonResourceResolverForXslEvaluateTest1

Actions #2

Updated by Norm Tovey-Walsh over 1 year ago

Hi Martin,

Sorry for the delay in responding to this question, and thank you for opening an issue for it. Debbie and I chatted about it this morning and ... it's complicated.

I think you're correct that there's a bug here. The set of available documents in the dynamic context is not passed to fn:transform or xsl:evaluate and there's no way for you to make that happen. It doesn't appear to be accidental, but the restriction isn't documented and I think it's reasonable for you to expect that they will be passed through. (The spec gives explicit wiggle room here, see 10.4.2 for example.)

It appears, for better or worse, that our test drivers take advantage of this fact. Because the default is not to pass the available documents through, the test driver can setup the context differently for each test.

My sketch for resolving this is:

  1. Change the default behavior so that the avialable documents are passed through by default
  2. Add and document a (vendor extension) option that allows the user to control this behavior. If you don't want the available documents to be passed through, you should be able to prevent it.

Of course there's a question of backwards compatibility here. It's hard to tell if anyone is currently relying on the default behavior. So it might be better to leave the default as it is and provide an option to pass things through. But I'll leave that decision for another day.

Please register to edit this issue

Also available in: Atom PDF Tracking page