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

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.

Related

XSLT select node without loosing attributes

I am trying to do xslt transformation on xml tags and the output is coming as expected but all attribute value are getting trimmed off . I want to have all attributes need to be applied but specific attributes need to be modified like attribute names.
Please find below example.
<section>
<line number='1' style='none' bold='true' size='10pt'>Line 1</line>
<line number='2' bold='true' >Line 2</line>
<line number='3' style='none' bold='true' size='10pt' color='red'>Line 3</line>
</section>
This i want to transform to
<div>
<p num='1' style='none' bold='true' size='10pt'>Line 1</p>
<p num='2' b='true' >Line 2</p>
<p num='3' style='none' bold='true' size='10pt' color='red'>Line 3</p>
</div>
This is the example i have write so far. But is but complex because i cant assume which attribute applied so i don't want to give the names of attributes explicitly.
<xsl:template match="section"><div use-attribute-sets="default"><xsl:apply-templates select="node()"/></div></xsl:template>
<xsl:template match="p"><p use-attribute-sets="default"><xsl:apply-templates select="node()"/></p></xsl:template>
<xsl:attribute-set name="default">
<xsl:attribute name="number"><xsl:value-of select="#num"/></xsl:attribute>
<xsl:attribute name="style"><xsl:value-of select="#style"/></xsl:attribute>
<xsl:attribute name="b"><xsl:value-of select="#bold"/></xsl:attribute>
<xsl:attribute name="size"><xsl:value-of select="#size"/></xsl:attribute>
<xsl:attribute name="color"><xsl:value-of select="#color"/></xsl:attribute>
</xsl:attribute-set>
If you want to copy all existing attributes by default with some exceptions you can achieve that like the following code:
<xsl:template match="*"> <!-- This matches all nodes. Note that specific templates have higher priority and will hit earlier (You can probably use your match="p" or "line") -->
<xsl:apply-templates select="#*"/> <!-- Default value if no select is given is '*' so no attributes would be hit -->
</xsl:template>
<xsl:template match="#*"> <!-- Match all the attributes so you can copy them -->
<xsl:copy/>
</xsl:template>
<xsl:template match="#bold"> <!-- Note that this will hit instead of #* because its more specific as described above -->
<xsl:attribute name="b" select="."/>
</xsl:template>
<!-- Here you can specify more of such attribute matches if needed -->

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>

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.
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!

java: replace namespace uri string in xml file with xslt [duplicate]

I am trying to replace namespace string using xslt.
I have the source namespace string and target namespace string in another xml file in the format of "namespace source="xxx" target="xxx"". All source namespace strings in my to-be-transformed xml should be changed to the corresponding target value. Only elements need to be considered as attributes don't have namespace.
I'm using the JDK default xalan xslt processor, which supports xslt 1.0. I'd like to stick to xslt 1.0 if possible, but I can change processor and use xslt 2.0 if really needed.
For example, to-be-transformed input xml:
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-old">
<ElementB/>
<ns2:elementD/>
</ElementA>
</root>
The output xml should be ("http://ns2-old" is changed to "http://ns2-new", and "http://ns4-old" is changed to "http://ns4-new"):
<root xmlns="http://ns1" xmlns:ns2="http://ns2-new" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ns2:elementD/>
</ElementA>
</root>
Here is my xsl that is not working:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="nsmapdoc" select="document('my-map-file')"/>
<xsl:key name="nsmap" match="//namespace/#target" use="#source"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- process each element -->
<xsl:template match="element()">
<xsl:for-each select="namespace::*">
<!-- for each namespace node of the element, save the string value-->
<xsl:variable name="sourceuri"><xsl:value-of select="."/>
</xsl:variable>
<!-- get the target value for this namespace-->
<xsl:variable name="targeturi">
<xsl:for-each select="$nsmapdoc">
<xsl:value-of select="key('nsmap', $sourceuri)"/>
</xsl:for-each>
</xsl:variable>
<!-- if target value exists, replace the current namespace node string value with the target value-->
<xsl:if test="$targeturi">
<xsl:value-of select="$targeturi"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have a few questions:
For the identity template that do the copy, why doing "match="node()|#*" instead of just "match="node()"? Is attribute also a node?
I am not sure if I did correctly to get the namespace string value (like "http://ns1", "http://ns2-old") for the element.
I think I got the key correctly. However, I am not sure if I used the type correctly---looks like targeturi variable is not a string. If key does not have the entry, what will lookup the entry return? In my case, I should replace the namespace value only for the namespace that has an entry in the map.
How to write a new string value for the namespace node?
I need to process each namespace nodes for the element. Is it the right way to define a variable inside ?
please help to see what is wrong with my xsl. Any suggestion is appreciated.
I think you have two, possibly three, separate questions here.
The first question is: how to move elements from one namespace to another, using a "map" of source-to-target namespaces. Let me answer this question first. Given:
XML
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-old">
<ElementB/>
<ns2:ElementD/>
</ElementA>
</root>
map.xml
<root>
<namespace source="http://ns2-old" target="http://ns2-new"/>
<namespace source="http://ns4-old" target="http://ns4-new"/>
</root>
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:param name="nsmapdoc" select="document('map.xml')"/>
<xsl:template match="*">
<xsl:variable name="old-ns" select="namespace-uri()"/>
<xsl:variable name="map-entry" select="$nsmapdoc/root/namespace[#source=$old-ns]"/>
<xsl:variable name="new-ns">
<xsl:choose>
<xsl:when test="$map-entry">
<xsl:value-of select="$map-entry/#target"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$old-ns"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{local-name()}" namespace="{$new-ns}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://ns1">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ElementD xmlns="http://ns2-new"/>
</ElementA>
</root>
Note:
ElementA and its child ElementB have been moved from namespace URI "http://ns4-old" to URI "http://ns4-new";
ElementD has been moved from namespace URI "http://ns2-old" to URI "http://ns2-new";
The prefix of ElementD has been stripped off; this is a meaningless, cosmetic change, and it should not present any problems for the receiving application;
The root element has remained in its original namespace;
Unused namespace declarations have been discarded.
Note also that we are not using a key to lookup the new namespace: using a key across documents is quite awkward in XSLT 1.0 and I have chosen to do without.
The second question is how to copy the unused namespace declarations. There are several possible answers to choose from:
It's not necessary to copy them, since that are not used for anything;
It's not possible to copy them;
It is possible to copy them by copying some element from the source document; however copying an element also copies its namespace - so this can be done only if there is a known element that is supposed to stay in its original namespace. For example, if you know beforehand that the root element is not supposed to be moved to another namespace, you can add another template to the stylesheet:
to get this result:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ElementD xmlns="http://ns2-new"/>
</ElementA>
</root>

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.

Categories

Resources