Search code examples
javaxmlxsltxslt-2.0saxon

Accessing sub-tree that is stored in a variable causes exception


I'm using Saxon HE 9.7.0 to transform some XSLT 2.0. In my file I'm trying to select a sub-tree and put it in a variable to use it later on.

Input XML:

<?xml version="1.0"?>
<documents>
    <document>
        <code>009190</code>
        <index>3</index>
    </document>
    <document>
        <code>583876</code>
        <index>1</index>
    </document>
    <document>
        <code>277410</code>
        <index>2</index>
    </document>
</documents>

Now in my XSLT I select the document with the lowest index and want to use its code later (the variable documents contains the root tree):

<xsl:variable name="selectedDocument">
    <xsl:variable name="lowestDocumentIndex" select="min($documents/document/orderIndex)" />
    <xsl:value-of select="$documents/documents[index=$lowestDocumentIndex]" />
</xsl:variable>

<!-- Now later on, I want to use the contents of the selected document: -->
<xsl:value-of select="$selectedDocument/code" />

This causes an exception in the parser which looks like this:

Caused by: java.lang.RuntimeException: Internal error evaluating template rule  at line 23 in module 
    at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:366)
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:456)
    at net.sf.saxon.Controller.transformDocument(Controller.java:2291)
    at net.sf.saxon.Controller.transform(Controller.java:1863)
    at net.sf.saxon.s9api.XsltTransformer.transform(XsltTransformer.java:579)
    at net.sf.saxon.jaxp.TransformerImpl.transform(TransformerImpl.java:185)
    at de.haba.genex.exp.core.model.nodes.XSLTTransformationNode.lambda$process$0(XSLTTransformationNode.java:146)
    ... 40 more
Caused by: java.lang.ClassCastException: net.sf.saxon.value.UntypedAtomicValue cannot be cast to net.sf.saxon.om.NodeInfo
    at net.sf.saxon.expr.SimpleStepExpression.iterate(SimpleStepExpression.java:108)
    at net.sf.saxon.expr.Atomizer.iterate(Atomizer.java:293)
    at net.sf.saxon.expr.AtomicSequenceConverter.iterate(AtomicSequenceConverter.java:249)
    at net.sf.saxon.expr.SystemFunctionCall.evaluateArguments(SystemFunctionCall.java:360)
    at net.sf.saxon.expr.FunctionCall.iterate(FunctionCall.java:544)
    at net.sf.saxon.expr.Expression.evaluateItem(Expression.java:763)
    at net.sf.saxon.expr.Expression.evaluateAsString(Expression.java:859)
    at net.sf.saxon.expr.instruct.SimpleNodeConstructor.processLeavingTail(SimpleNodeConstructor.java:186)
    at net.sf.saxon.expr.instruct.ValueOf.processLeavingTail(ValueOf.java:283)
    at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:653)
    at net.sf.saxon.expr.LetExpression.processLeavingTail(LetExpression.java:711)
    at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:653)
    at net.sf.saxon.expr.LetExpression.processLeavingTail(LetExpression.java:711)
    at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:653)
    at net.sf.saxon.expr.LetExpression.processLeavingTail(LetExpression.java:711)
    at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:653)
    at net.sf.saxon.expr.instruct.NamedTemplate.expand(NamedTemplate.java:257)
    at net.sf.saxon.expr.instruct.CallTemplate.process(CallTemplate.java:356)
    at net.sf.saxon.expr.LetExpression.process(LetExpression.java:568)
    at net.sf.saxon.expr.instruct.ForEach.processLeavingTail(ForEach.java:453)
    at net.sf.saxon.expr.LetExpression.processLeavingTail(LetExpression.java:711)
    at net.sf.saxon.expr.instruct.Choose.processLeavingTail(Choose.java:835)
    at net.sf.saxon.expr.instruct.Instruction.process(Instruction.java:149)
    at net.sf.saxon.expr.LetExpression.process(LetExpression.java:568)
    at net.sf.saxon.expr.instruct.ForEach.processLeavingTail(ForEach.java:453)
    at net.sf.saxon.expr.LetExpression.processLeavingTail(LetExpression.java:711)
    at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:653)
    at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:353)
    ... 46 more

With XSLT 2.0 no more storing RTFs but keeping real temporary trees, shouldn't this work?

This is just a cut-down example of what I'm trying to do, I'm actually having a <xsl:choose> within my <xsl:variable> and the document structure is more complex, but the idea should be the same.


EDIT: It seems that my problem is related to that I pass the list of documents to another template first. Using the xsl:copy-of as written in the answers seems to work, but not when I do the following:

(Try it out on http://xsltransform.net/ejivdHL/2)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

    <xsl:template match="/">
        <xsl:call-template name="processDocuments">
            <xsl:with-param name="docs" select="documents" />
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="processDocuments">
        <xsl:param name="docs" />

        <xsl:variable name="selectedDocument">
            <xsl:variable name="lowestDocumentIndex" select="min(doc/document/index)" />
            <xsl:copy-of select="doc/document[index=$lowestDocumentIndex]" />
        </xsl:variable>

        <xsl:value-of select="$selectedDocument/document/code" />
    </xsl:template>

</xsl:transform>

Any more ideas of what's going wrong? I need to use a second template for this, because I use the template multiple times for various nodes.


Solution

  • To store a copy of nodes you need <xsl:copy-of select="$documents/documents[index=$lowestDocumentIndex]" /> instead of <xsl:value-of select="$documents/documents[index=$lowestDocumentIndex]" />. value-of creates a text node with the string value(s) of the selected node(s).

    Furthermore, some references/paths don't seem to matched, when fixing that I get

    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
        <xsl:template match="/">
            <xsl:variable name="selectedDocument">
                <xsl:variable name="lowestDocumentIndex" select="min(documents/document/index)" />
                <xsl:copy-of select="documents/document[index=$lowestDocumentIndex]" />
            </xsl:variable>
    
            <!-- Now later on, I want to use the contents of the selected document: -->
            <xsl:value-of select="$selectedDocument/document/code" />
        </xsl:template>
    
    </xsl:transform>
    

    outputting 583876 at http://xsltransform.net/ejivdHL.

    There is really no need to create a temporary tree, you could simply select an original input element:

    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
        <xsl:template match="/">
            <xsl:variable name="lowestDocumentIndex" select="min(documents/document/index)" />
            <xsl:variable name="selectedDocument" select="documents/document[index=$lowestDocumentIndex]" />
    
    
            <!-- Now later on, I want to use the contents of the selected document: -->
            <xsl:value-of select="$selectedDocument/code" />
        </xsl:template>
    
    </xsl:transform>
    

    Online at http://xsltransform.net/ejivdHL/1.