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

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.

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.

Use:
<xsl:variable name="lowestDocumentIndex" select="min($documents/document/index)" />
<xsl:variable name="selectedDocument" select="$documents/document[index=$lowestDocumentIndex]" />
It seems that the <xsl:value-of> in your code extracted the value of a selected node (concatenated text nodes) not the node itself.
By the way: Your code contains $documents/documents.
Note that the last s should be removed.

A stacktrace saying "Saxon internal error" means it's a Saxon bug. Possibly one we already know about and may have fixed, but a bug all the same. It's best to report Saxon bugs on the bug tracker at saxonica.plan.io. We will almost certainly need a "repro" - that is, a stylesheet and source document, and any other necessary details like configuration parameters, that enable us to reproduce the problem.
Just to give a little bit of preliminary analysis, the key failure is
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)
That tells us that it's a run-time error and that we're evaluating a "simple step expression" which is something like $lhs/child::X where the LHS is an expression returning a singleton, and the RHS is an axis expression. I can tell from the line number that you aren't executing the latest maintenance release (because the cast to NodeInfo is now on line 110). The ClassCastException occurs because the LHS of the expression is expected to be a node, but is actually an atomic value, which means something has gone wrong in the type checking which should have caught this earlier. It could be an error in your code that we're failing to pick up and diagnose correctly.
Unfortunately a stack trace doesn't give us enough information to tell exactly what code in your stylesheet is executing at the point of failure. That's why we need the repro, so we can run it in a debugging environment and catch it at the point of failure.
That suggests two actions: (a) please try it on the latest Saxon maintenance release (currently 9.7.0.14, but watch this space), and (b) if it crashes again, submit a full repro at saxonica.plan.io. Thanks!

Related

xslt transformation in java , attribute value not encoded

I am writing a java program where in some cases I have to perform a xslt transformation.
In this case i need to add an attribute named type at the level of the work.
Its value should be the same as the value of the element ns2:type_work
For example:
<ns2:work>
<ns2:type_work>PROP</ns2:type_work>
<ns2:identifier_work>2017/375/943030</ns2:identifier_work>
<ns2:work>
should become
<ns2:work type="PROP">
<ns2:type_work>PROP</ns2:type_work>
<ns2:identifier_work>2017/375/943030</ns2:identifier_work>
<ns2:work>
I have made the following XSLT
<xsl:template match="ns2:work">
<ns2:work>
<xsl:attribute name="type" select="ns2:type_work/node()" />
<xsl:apply-templates select="#*|child::node()" />
</ns2:work>
</xsl:template>
and I apply it using the proper java functios (javax.xml.transform.),
I get no erros,
the attribute -type- is created
but it is empty.
Does it have to do something with the XSLT version is my xslt not compatible with 1.0?
How can I bypass this?
If you are using XSLT 1.0, then the code needs to look like this, as select is not valid on xsl:attribute in XSLT 1.0
<xsl:attribute name="type">
<xsl:value-of select="ns2:type_work/node()" />
</xsl:attribute>
(Note that you can just do <xsl:value-of select="ns2:type_work" /> here)
Better still, use Attribute Value Templates
<ns2:work type="{ns2:type_work}" />
<xsl:apply-templates select="#*|child::node()" />
</ns2:work>

Fetch an XML node-name as regex in XSL Stylesheet transformation

I have an XML (I know it is incorrect as per XML standards but I am restricted to changes as I am processing response from external party) as follows,
XML Snippet : <root>
<3party>some_value</3party>
</root>
I would like to fetch <3party> from above snippet in XSL stylesheet transformation. The fact is the <3party> element is invalid so I can no refer it as follows as it fails the xsl compilation. I would need a way to refer it as a partial element may be using some regx way? Following is incorrect.
<xsl:variable select="$response/root/3party" />
Any answers would help me out.
Edit : Possible solution to above usecase would be.
<xsl:for-each select="$response/root">
<!-- Node name -->
<xsl:variable name="name" select="local-name()" />
<!-- check if it contains party -->
<xsl:if test="contains($name, 'party')">
<!-- Node value -->
<xsl:variable name="value" select="node()" />
</xsl:if>
</xsl:for-each>
With regard to the code added to your question:
<xsl:for-each select="$response/root">
<!-- Node name -->
<xsl:variable name="name" select="local-name()" />
<!-- check if it contains party -->
<xsl:if test="contains($name, 'party')">
<!-- Node value -->
<xsl:variable name="value" select="node()" />
</xsl:if>
</xsl:for-each>
The test contains($name, 'party') will never return true. The context node's name is root and "root" does not contain "party".
On a more general note: you seem to think that the problem is how to get your XSLT stylesheet to compile. That is not so. The real problem here is that your input is not well-formed XML. The input must be parsed before it can be transformed, and if it's not well-formed XML, the process will fail well before even considering your stylesheet.

Calling dateNow() with XALAN

am using XSL which is being called by JAVA method, I have tried to fix it by giving absolute path of a class but I don't think it will work because I didn't find anywhere calling a method in XSL using absolute class path so am trying by keeping in the server environment.here is my code,i have given class path and i have called method also .. but am not getting the proper output. Is this the correct way to call a method?
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:datetime="java:com.ibm.date"
exclude-result-prefixes="xalan"
version="1.0">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes" indent="no" />
<xsl:strip-space elements="*" />
<xsl:template name="RootToAcknowledgeInventoryRequirement">
<xsl:param name="Root" />
<xsl:variable name="PromiseHeader" select="$Root/PromiseHeader" />
<xsl:variable name="today" select="datetime:dateNow()" />
<xsl:variable name="OrganizationCode">
<xsl:value-of select="$PromiseHeader/#OrganizationCode" />
</xsl:variable>
<_inv:AcknowledgeInventoryRequirement releaseID="">
<_wcf:ApplicationArea>
<oa:CreationDateTime xsi:type="udt:DateTimeType">
<xsl:value-of select="datetime:dateNow()" />
</oa:CreationDateTime>
</_wcf:ApplicationArea>
As you have mentioned XLAN processor, its version is XSLT 1.0.
So by default it doesn't possess any date-time function that brings it a current datetime value.
It is XSLT 2.0 that supports
<xsl:value-of select="current-dateTime()"/>
<xsl:value-of select="current-date()"/>
<xsl:value-of select="current-time()"/>
you should go with Machael Kay's Saxon latest version for that.
Now for work around, EXSLT has been into good practice:
Download code from this link, copy date.xsl to your xsl file's location. Import it in your xsl.
<xsl:stylesheet version="1.0"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="date"
...>
<xsl:import href="date.xsl" />
<xsl:template match="//root">
<xsl:value-of select="date:date-time()"/>
</xsl:template>
</xsl:stylesheet>
solution 2: Pass Datetime value as param to XSL. Use it as variable wherever required.

XSLT CallTemplate ForEach XML extension function

I want the $content to be the expected string. I know that copy-of instead of value-of $content produces the expected string. But how do I not use copy-of and pass it to say a java extension function?
I asked a different related question here.
XML
<?xml version="1.0"?>
<a>
<b c="d"/>
<b c="d"/>
<b c="d"/>
</a>
XSL
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="foo">
<xsl:param name="content"></xsl:param>
<!-- <xsl:copy-of select="$content"></xsl:copy-of> -->
<!-- copy-of produces the expected string here, but how to pass to Java -->
<xsl:value-of select="java:someMethod($content)" />
<!-- I want the content to be the expected string -->
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="foo">
<xsl:with-param name="content">
<xsl:for-each select="a/b">
<e>
<xsl:value-of select="#c" />
</e>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Desired String to be passed to Java extension function from $content.
<?xml version="1.0"?>
<e>d</e>
<e>d</e>
<e>d</e>
PS: Calling foo is mandatory.
Ultimately, my objective is to emulate result-document in XSLT 1.0 with extension function.
Calling Java extension functions depends on which XSLT processor you are using, which you haven't told us. If you use Java's TransformerFactory you will get whatever is on the classpath, for example the built-in version of Xalan, the Apache version of Xalan, or Saxon.
Your description suggests you want to pass a string containing lexical XML to your Java extension function. That implies you need to serialize a node to a string. That can't be achieved without an extension function such as saxon:serialize(). It's probably easier to pass the node to your Java method, and have the Java method do the serialization.

Delete XSL output file generation at runtime

I am writing text output files reading an XML file using XSL.
Here i am trying to check weather a particular content is available in the source XML and write that content to a file if available.
But if the content is not available ( not fulfilling "<XSL:if>" condition), then output file would be an empty file.
So I want to add an else condition and in that else condition to avoid XSL output file being created at runtime.
Any body having any clue?
<xsl:message terminate="yes"> wont help because it does generate the output but only terminating the further processing of XSL.
Can any body help or even suggest any other approach to be taken in java code even without deleting files after they have created. [By reading them and identifying empty files]
Currently I am using java to read the created empty files and delete them explicitly. Thanks in adavance.
I will give two examples how this can be done -- the second is what I recommend:
Suppose we have this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
and we want to produce another one from it, in which the num elements with even numbers are "deleted".
One way of doing this is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<nums>
<xsl:apply-templates/>
</nums>
</xsl:template>
<xsl:template match="num">
<xsl:choose>
<xsl:when test=". mod 2 = 1">
<num><xsl:value-of select="."/></num>
</xsl:when>
<!-- <xsl:otherwise/> -->
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The wanted result is produced:
<nums>
<num>01</num>
<num>03</num>
<num>05</num>
<num>07</num>
<num>09</num>
</nums>
Do notice: For "not doing anything" you even don't need the <xsl:otherwise> and it is commented out.
A better solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="num[. mod 2 = 0]"/>
</xsl:stylesheet>
This produces the same correct result.
Here we are overriding the identity rule with a template matching num elements with even value and with empty body -- which does the "delete".
Do notice:
Here we don't use any "if-then-else" explicit instructions at all -- just Xtemplate pattern matching, which is the most distinguishing feature of XSLT.

Categories

Resources