Project

Profile

Help

Bug #4690

closed

generate-id problem when chaining stylesheets and storing intermediary results as a tree

Added by Martin Honnen over 3 years ago. Updated about 3 years ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
-
Sprint/Milestone:
-
Start date:
2020-08-21
Due date:
% Done:

100%

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

Description

While trying to run Schematron with Saxon-JS 2 instead of Saxon Java I have run into an odd problem: the schematron compilation consists of three XSLT steps to convert a Schematron schema to an XSLT stylesheet; when I run these steps with Saxon-JS 2 SaxonJS.transform using a tree as the intermediary result format the third step doing the compilation fails with an error by Saxon-JS that a required parameter has not been supplied.

On inspecting the stylesheet it seems the parameter is passed on using generate-id() but somehow that doesn't bind a value in the receiving template.

This only happens when I use a tree as the intermediary result, if I serialize intermediary results the problem doesn't occur.

I have managed to reduce the problem to a two step process where the first does an identity spelled out as a template:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="2.0">
    
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

and the second XSLT tries to pass on a generate-id() inside of for-each-group:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    
    <xsl:mode on-no-match="shallow-copy"/>
    
    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:for-each-group select="item" group-by=".">
                <xsl:apply-templates select=".">
                    <xsl:with-param name="id" as="xs:string" select="generate-id()"/>
                </xsl:apply-templates>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="item">
        <xsl:param name="id" as="xs:string" required="yes"/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:attribute name="generated-id" select="$id"/>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

When I run the two stylesheets with Node.js using code

require('saxon-js');

const step1Result = SaxonJS.transform({
    stylesheetFileName : 'identity-2.sef.json',
    sourceFileName : 'sample1.xml'
});

const step2Result = SaxonJS.transform({
    stylesheetFileName : 'generate-id-test2.sef.json',
    sourceNode : step1Result.principalResult,
    destination : 'serialized'
});

console.log(step2Result.principalResult);

I get an error

  message: 'Required parameter $Q{}id not supplied',
  code: 'XTDE0700',
  xsltLineNr: '21',
  xsltModule: 'generate-id-test2.xsl',

A sample input would be

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <item>a</item>
    <item>b</item>
</root>

If I run the second stylesheet alone all works fine, somehow the first step, although doing nothing but an identity transformation, causes the problem with the binding of generate-id() in the second step.

The used sef.json files were simply generated using xslt3 from above shown XSLT stylesheets.

Actions #1

Updated by Debbie Lockett over 3 years ago

  • Assignee set to Debbie Lockett
Actions #2

Updated by Michael Kay about 3 years ago

  • Status changed from New to In Progress
  • Assignee changed from Debbie Lockett to Michael Kay
  • Priority changed from Low to Normal

I've attempted without success to reproduce this by running the two stylesheets from a third stylesheet using fn:transform(). It seems the two transformations really do need to be separate calls on SaxonJS.transform(). Unfortunately this makes debugging a whole lot more difficult...

I have now reproduced the problem within the API testing framework.

Actions #3

Updated by Michael Kay about 3 years ago

It seems that the principal result of the first transformation is an element node (named root), not a document node. This means that in the second transformation, the item template is being called directly ( the match="/*" template does not fire because this pattern requires the element to have a document node as its parent.)

So, is this correct?

I think not. The API specification for SaxonJS.transform() says that the default for destination is application, and that in the absence of a build-tree option, this is equivalent to document, which should cause the result to be wrapped in a document node.

The actual logic (transform.js#407) is

} else if (["raw", "document", "application"].includes(destination)) {
            let buildTree = destination === "document";
            if (destination === "application") {
                if (typeof outputProps["build-tree"] !== "undefined") {
                    buildTree = outputProps["build-tree"];
                }
            }

which with these default options leads to buildTree = false. We should add

} else if (["raw", "document", "application"].includes(destination)) {
            let buildTree = destination === "document";
            if (destination === "application") {
                if (typeof outputProps["build-tree"] !== "undefined") {
                    buildTree = outputProps["build-tree"];
                }  else {
                    buildTree = true;
                }
            }
       

to make it match the documentation.

Tried this; the test case now works.

Actions #4

Updated by Michael Kay about 3 years ago

Unfortunately quite a few of the Node.js API tests are written to expect an element node to be returned. For example sync-trans-001 has

expect(result.principalResult.nodeType).to.equal(1);

(NodeType=1 is an element node, NodeType=9 is a document node)

But I think the documentation is clear, and users will expect a document node to be returned in this scenario, so I think we should apply the change and fix the tests to expect NodeType=9.

Actions #5

Updated by Michael Kay about 3 years ago

With the patch applied, these tests are actually returning a DOCUMENT_FRAGMENT node (11) rather than a DOCUMENT node (9). Of course, these are two different DOM representations of an XDM document node. I think returning a document fragment is correct, because there's no guarantee that the result document will have a single element root, but the documentation should perhaps be clarified.

Actions #6

Updated by Community Admin about 3 years ago

  • Applies to JS Branch 2 added
  • Applies to JS Branch deleted (2.0)
Actions #7

Updated by Norm Tovey-Walsh about 3 years ago

  • Status changed from In Progress to Resolved
Actions #8

Updated by Debbie Lockett about 3 years ago

  • Fix Committed on JS Branch 2 added
Actions #9

Updated by Debbie Lockett about 3 years ago

  • Status changed from Resolved to Closed
  • % Done changed from 0 to 100
  • Fixed in JS Release set to Saxon-JS 2.1

Bug fix applied in the Saxon-JS 2.1 maintenance release.

Please register to edit this issue

Also available in: Atom PDF Tracking page