Bug #5490


Saxon-JS 2.4 fails to run Schxslt pipeline XSLT that tries to first compile Schematron schema to XSLT and then to apply that schema to validate an XML instance document: TypeError: Cannot read property 'childNodes' of undefined

Added by Martin Honnen 7 months ago. Updated about 2 months ago.

XSLT Conformance
Start date:
Due date:
% Done:


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


I have found a major regression with Saxon 2.4, I have a stylesheet that runs two XSLT transformations in a pipeline, the first step uses Schxslt 1.8.6 to compile a Schematron schema to XSLT, the second step applies the generated XSLT against an input XML document to validate that input XML against the Schematron schema and to generate an SVRL report. That worked fine for me with Saxon-JS 2.2 and 2.3 but in 2.4 fails with an error:

C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest>xslt3 -t -xsl:run-pipeline-for-svrl-and-apply-to-schema.sef.json -s:sample1.xml schema-uri=schematron-schema1.sch
SaxonJS 2.4 from Saxonica
Node.js version v14.16.0
SEF generated by SaxonJS 2.4 at 2022-05-14T12:47:24.253+02:00
Transformation failure: TypeError: Cannot read property 'childNodes' of undefined
Cannot read property 'childNodes' of undefined

When I run the code from Node.js I get a longer stack trace included that hopefully helps:

C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest>node app.js
push:function(h,M,d){if("undefined"===typeof d)throw ob.fb(Error().stack),new L("No output for "+m(h));if(u[h.N]){var c=e(h);try{c(M,d)}catch(n){throw n instanceof L&&!n.xsltLineNr&&nb(n,h),n;}}else{c=Wd.elaborate(h);try{for(var l=c(M),q;null!=(;)d.append(q)}catch(n){throw n instanceof L&&!n.xsltLineNr&&nb(n,h),n;}}},bj:g,Of:t,mh:r}}();var Ve=function(){function a(Q,ja,ca,z,A){function F(E){return null===n.Ma(E)}function I(E){return E.nodeType===B.nodeType&&(null===R?!}var S=A.fixed.evaluator;if(ja){var B=S.evaluate(ja,A).next();if(!B||!n.Q(B))throw new L("xsl:number/@select must yield a single node","XTTE1000");}else A.failIfNoCurrentFocus("XTTE0990","xsl:number"),B=A.getCurrentFocus();var,J=I;if(null!==ca){var fa=Ae.pd(ca);J=function(E){return Ae.matches(fa,E,A)}}if(null===z)ja=F;else{var ka=Ae.pd(z);

TypeError: Cannot read property 'childNodes' of undefined
    at transform (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4408:426)
    at C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4463:173
    at C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4477:57
    at Object.push (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4224:232)
    at e (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4816:245)
    at t (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4820:320)
    at Object.transform (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\node_modules\←[4msaxon-js←[24m\SaxonJS2N.js:4840:413)
    at Object.<anonymous> (C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest\app.js:16:26)
←[90m    at Module._compile (internal/modules/cjs/loader.js:1063:30)←[39m
←[90m    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)←[39m
←[90m    at Module.load (internal/modules/cjs/loader.js:928:32)←[39m
←[90m    at Function.Module._load (internal/modules/cjs/loader.js:769:14)←[39m
←[90m    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)←[39m
←[90m    at internal/main/run_main_module.js:17:47←[39m

I put the files in a Github repository:

Actions #1

Updated by Martin Honnen 7 months ago

I have also created the branch that uses Saxon-JS 2.3 and where the code works fine and outputs an SVRL report instead of giving that error:

C:\Users\marti\source\repos\SaxonJSSchxsltPipelineTest\SaxonJSSchxsltPipelineTest>node app.js
<?xml version="1.0" encoding="UTF-8"?>
<svrl:schematron-output xmlns:error="" xmlns:rdf="" xmlns:sch="" xmlns:schxslt-api="" xmlns:schxslt="" xmlns:svrl="" xmlns:xs="">
   <svrl:metadata xmlns:dct="" xmlns:skos="">
         <rdf:Description xmlns:dc="">
                  <skos:prefLabel>SchXslt/1.8.6 Saxon-JS/2.3</skos:prefLabel>
                  <schxslt.compile.typed-variables xmlns="">true</schxslt.compile.typed-variables>
   <svrl:active-pattern documents="file:///C:/Users/marti/source/repos/SaxonJSSchxsltPipelineTest/SaxonJSSchxsltPipelineTest/run-pipeline-for-svrl-and-apply-to-schema.sef.json"/>
   <svrl:fired-rule context="root"/>
   <svrl:failed-assert location="/Q{}root[1]" test="@*">
      <svrl:text>root has no attributes.</svrl:text>
   <svrl:successful-report location="/Q{}root[1]" test=". = 'This is an example.'">
      <svrl:text>root element has value 'This is an example.'</svrl:text>
Actions #2

Updated by Martin Honnen 7 months ago

To see whether the problem only occurs in the context of (a relatively complex code like) Schematron compilation I have now also added some files identity.xsl, identity.sef.json, run-and-apply-identity.js, run-and-apply-identity.xsl, run-and-apply-identity.sef.json and if you run node run-and-apply-identity.js you get the same error with Saxon-JS 2.4 as with the more complex Schematron samples while Saxon-JS 2.3 runs the identity transformation fine.

Actions #3

Updated by Norm Tovey-Walsh 7 months ago

Well, that is disappointing. I shall investigate and see if we need to do a 2.5.

Actions #4

Updated by Martin Honnen 7 months ago

I think the regression is due to the fix of, previously the Saxon-JS code created always a document node, now it might create a document fragment node but the code processing a stylesheet expects a document nodes as at some point it wants to process all top level nodes by processing e.g. stylesheet.documentElement.childNodes, only now, with 2.4, stylesheet can be a document fragment node and document fragment nodes do not have a documentElement at all. That way the attempt to process a stylesheet that is formed as a fragment fails with the attempt to access childNodes on the undefined documentElement of the stylesheet.

Actions #5

Updated by Martin Honnen 7 months ago

If I change a line doing

for (ma = Aa(ha.documentElement.childNodes),
                    y =; !y.done; y =


for (ma = Aa(ha.documentElement ? ha.documentElement.childNodes : ha.childNodes),
                    y =; !y.done; y =

the Saxon-JS 2.4 code seems to be able to process the stylesheet fine.

I haven't checked whether there is other stylesheet processing code around that tries to process stylesheet.documentElement.childNodes.

Actions #6

Updated by Martin Honnen 7 months ago

I think the fix should rather be

                    for (ma = Aa(ha.documentElement ? ha.documentElement.childNodes : ha.firstElementChild.childNodes),
                    y =; !y.done; y =
Actions #7

Updated by Norm Tovey-Walsh 6 months ago

  • Status changed from New to In Progress

That does appear to be the only place where documentNode.childNodes is called. I persuaded myself when I was working on the fix that stylesheets wouldln't be a problem because they would always be documents.

I'm torn about the best solution. Your proposal fixes the problem by changing the code so that a single-rooted document fragment will be processed correctly. But if there's a code path that allows a document fragment to reach there, then the possibility exists that a non-single-rooted fragment might be passed in. In that case, just treating the children of the first child as the content is probably wrong.

Where we previously always attempted to coerce inputs into documents (failing if a fragment couldn't be), I wonder if what we should do instead is check the fragment and, if it is single rooted, coerce it into a document. Otherwise, leave it as a fragment.

I shall have to experiment a bit.

Actions #8

Updated by Martin Honnen 6 months ago

I just tried the following XQuery with Saxon CS 11.3 and Saxon HE 11.3:

transform(map {
  'stylesheet-node' : document { <xsl:stylesheet xmlns:xsl="" version="3.0">
    <xsl:mode on-no-match="shallow-copy"/>
    <xsl:template match="/">
      <xsl:comment>Fragment 1</xsl:comment>
  <xsl:stylesheet xmlns:xsl="" version="3.0">
    <xsl:mode on-no-match="shallow-copy"/>
    <xsl:template match="/">
      <xsl:comment>Fragment 2</xsl:comment>
  'source-node' : document { <root>test</root> }

Result: <?xml version="1.0" encoding="UTF-8"?><root>test</root><!--Fragment 1-->

No idea whether that is coincidence or just an accident or quirk but for some reason my suggested fix seems to make Saxon-JS treat a fragment with several top level elements like Saxon Java or CS seem to do, they process the first child element of the fragment as the XSLT stylesheet.

Perhaps it is better to ask Mike what he thinks how a node that is an XDM document node having more than one child element should be treated if used as the stylesheet-node of fn:transform.

Actions #10

Updated by Norm Tovey-Walsh 6 months ago

  • Status changed from In Progress to Resolved

I've pushed a fix for this. My fix is (currently) to force the stylesheetNode to be a document.

Actions #11

Updated by Michael Kay 6 months ago

Looking at this, I'm wondering what happens if stylesheetNode is the xsl:stylesheet element node?

My reading of the specs (both the XSLT 3.0 spec, §3.6, and the fn:transform spec, is that we should allow a document node containing a single element node, or an element node, but not a document node with multiple element children.

Actions #12

Updated by Martin Honnen 6 months ago

I think both Saxon-JS 2.3 and 2.4 fail on an element node for stylesheet-node in fn:transform when trying to convert the SEF XML to SEF JSON, instead of having the root element of the stylesheet package they process a text node and try to access the .attributes.length of the text node (which should be an element node) and then fail with an error that length of undefined can't be read.

Actions #13

Updated by Martin Honnen 6 months ago doesn't seem to have a test case where an xsl:stylesheet element node instead of a document node is passed to fn:transform.

Actions #14

Updated by Michael Kay 6 months ago

Added test case fn-transform-7a to pass an xsl:stylesheet element. It passes in SaxonJ 10.x (and therefore presumably 11.x).

Also added -7b and -7c to pass a simplified stylesheet, both as a document node and as an element node.

Also added -7d and -7e to check that the xsl:stylesheet element can be the second child of a document fragment node.

Actions #15

Updated by Debbie Lockett about 2 months ago

  • Sprint/Milestone changed from SaxonJS 2.4 to SaxonJS 2.5
  • Applies to JS Branch Trunk added
  • Fix Committed on JS Branch 2, Trunk added
Actions #16

Updated by Norm Tovey-Walsh about 2 months ago

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

Fixed in SaxonJS 2.5.

Please register to edit this issue

Also available in: Atom PDF Tracking page