Wrapping element in choose construction. How to improve? - xslt

Is there any way to optimize this code.
<xsl:choose>
<xsl:when test="val1 = val2">
<xsl:apply-templates select="node"/>
</xsl:when>
<xsl:otherwise>
<div>
<xsl:apply-templates select="node"/>
</div>
</xsl:otherwise>
</xsl:choose>
I do not like having to write twice the same <xsl:apply-templates select="node"/>.
Update:
The idea is that depending on the result of comparison to do one of two things:
Just print some information (which we obtain after applying the template <xsl:apply-templates select="node"/>).
Print the same information, but in advance "wrapping" it in the container (div for example).

Use:
<xsl:apply-templates select="node">
<xsl:sort select="not(val1 = val2)"/>
</xsl:apply-templates>
Here is a complete example. This transformation:
<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="/*">
<t>
<xsl:apply-templates select="node">
<xsl:sort select="not(val1 = val2)"/>
</xsl:apply-templates>
</t>
</xsl:template>
<xsl:template match="node[not(val1 = val2)]">
<div>
<node>
<xsl:apply-templates/>
</node>
</div>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<node>
<val1>1</val1>
<val2>2</val2>
</node>
<node>
<val1>3</val1>
<val2>3</val2>
</node>
</t>
produces the wanted, correct result:
<t>
<node>
<val1>3</val1>
<val2>3</val2>
</node>
<div>
<node>
<val1>1</val1>
<val2>2</val2>
</node>
</div>
</t>
Explanation of the solution:
Whenever an <xsl:apply-templates> has an <xsl:sort> child, the nodes that are selected are sorted according the data provided in the <xsl:sort> child(ren) and the results of applying templates on each selected node appear in the output in that (sort) order -- not in document order.
In the transformation above we have:
<xsl:apply-templates select="node">
<xsl:sort select="not(val1 = val2)"/>
</xsl:apply-templates>
This means that the results of applying templates to the elements named node for which it is true that val1=val2 will appear before the results of applying templates to the elements named node for which val1=val2 is not true. This is because false sorts before true.
If this explanation is not clear, the reader is directed to read more about <xsl:sort>.

This is hardy possible for that simple example, but if the amount of the wrapping code and the duplicate code are worth bothering, you have two options:
introduce a chain of templates with different modes:
<!-- instead of choose -->
<xsl:apply-template match="." mode="container" />
...
<xsl:template match="container" mode="container">
<xsl:apply-templates select="." mode="dup-group"/>
</xsl:template>
<xsl:template match="container[val1=val2]" mode="container">
<div>
<xsl:apply-templates select="." mode="dup-group"/>
</div>
</xsl:template>
<xsl:template match="container" mode="dup-group">
<xsl:apply-templates select="node"/>
<!-- other duplicate code -->
...
</xsl:template>
move a part of code into a separate imported stylesheet and override the template rule in the current stylesheet:
<xsl:template match="container[val1=val2]">
<div>
<xsl:apply-imports />
</div>
</xsl:template>

The xslt you are posting here is not very optimizable. If the code that you don't want to duplicate would be more than two lines, you could optimize by creating a named template and calling that instead. In this example it really doesn't make much sense, but you'll get the idea:
<xsl:when test="val1=val2">
<xsl:call-template name="optimized"/>
</xsl:when>
<xsl:otherwise>
<div>
<xsl:call-template name="optimized"/>
</div>
</xsl:otherwise>
And the template:
<xsl:template name="optimized">
<xsl:apply-templates select="node"/>
</xsl:template>
The called template is in the same context as the calling code, so you can just copy the code you don't want to duplicate to the named template.

Related

XSLT: Copying node data to another node by matching attribute value, Efficiently?

I coded the XSLT to copy one node data to another by validating the attribute value, I got the desired output but I'm curious to know whether there is an efficient way to do this or if this is the only way to do it. [I'm not an XSLT expert] Can someone help !!!
Please use this link to check instantly.
https://xsltfiddle.liberty-development.net/pNvtBH2/3
Actual XML:
<?xml version="1.0" encoding="utf-8" ?>
<section>
<p>note 1 : 1</p>
<p>note 2 : 2</p>
<p>note 3 : 3</p>
<note id="test1">hello one</note>
<note id="test2">hello two</note>
<note id="test3">hello <i>three</i></note>
<note id="test4">hello <i>four</i></note>
</section>
Output:
<?xml version="1.0" encoding="UTF-8"?><section>
<p>note 1 : <a>hello one</a></p>
<p>note 2 : <a>hello two</a></p>
<p>note 3 : <a>hello <i>three</i></a></p>
<note id="test4">hello <i>four</i></note>
</section>
XSLT Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<a>
<xsl:variable name="href" select="#href" />
<xsl:choose>
<xsl:when test="$href = //note/#id">
<xsl:copy-of select="//note[#id=$href]/node()" />
</xsl:when>
</xsl:choose>
</a>
</xsl:template>
<xsl:template match="note">
<xsl:choose>
<xsl:when test="#id = //a/#href">
<xsl:apply-templates select="node" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Whether it's efficient or not depends on how smart the optimizer in your XSLT processor is. Saxon-EE will do a pretty good job on this, Saxon-HE less so.
If you want to make it efficient on all processors, use keys. Replace an expression like //note[#id=$href] with a call on the key() function. Declare the key as
<xsl:key name="k" match="note" use="id"/>
and then you can get the matching nodes using key('k', $href).
The xsl:when test="$href = //note/#id" is redundant, as xsl:copy-of will do nothing if nothing is selected.
In the note template, I'm not sure what
<xsl:when test="#id = //a/#href">
<xsl:apply-templates select="node" />
</xsl:when>
is trying to achieve, because you don't have any elements named "node". If the aim is to avoid processing a note element at this stage if it was already processed from an a element, then you could build another index with
<xsl:key name="a" match="a" use="1"/>
and replace test="#id = //a/#href" with test="key('a', #href)"

XSLT 1.0 template Muenchian grouping

I use a tool where a xslt template is pre-defined and it is not desirable to remove it.
<xsl:template match="/">
<Msg xmlns="urn:com.sap.b1i.vplatform:entity">
<xsl:copy-of select="/vpf:Msg/#*"></xsl:copy-of>
<xsl:copy-of select="/vpf:Msg/vpf:Header"></xsl:copy-of>
<Body>
<xsl:copy-of select="/vpf:Msg/vpf:Body/*"></xsl:copy-of>
<Payload Role="X" id="{$atom}">
<xsl:call-template name="transform"></xsl:call-template>
</Payload>
</Body>
</Msg>
<xsl:template name="transform">
<!-- In this area we write our xpath and build the xml-file-->
</xsl:template>
Now I want to use the Muenchian grouping method. But for this method you also need to define a template en key. Like this:
<xsl:key name="KeyOrder" match="/vpf:Msg/vpf:Body/vpf:Payload[#id='atom8']/Orders/jdbc:Row" use="jdbc:RecId2" />
<xsl:template match="Orders" >
<Documents>
<xsl:for-each select="jdbc:Row[count(. | key('KeyOrder', jdbc:RecId2)[1]) = 1]">
<xsl:sort select="jdbc:RecId2" />
<Document>
<xsl:copy-of select="jdbc:RecId2" />
<xsl:for-each select="key('KeyOrder', jdbc:RecId2)">
<xsl:sort select="jdbc:OrderNrRef" />
<xsl:copy-of select="." />
</xsl:for-each>
</Document>
</xsl:for-each>
</Documents>
</xsl:template>
The problem is that the 2 templates won't work togetheter the way I copied it here. That means, I don't get the Muenchian grouping results. It's only works when I 'disable' xsl:template match="/", but then I lose a lot of other information which is necessary further in the process.
So how can I get in my XML file the results of both templates?

How to fold by a tag a group of selected (neighbor) tags with XSLT1?

I have a set of sequential nodes that must be enclosed into a new element. Example:
<root>
<c>cccc</c>
<a gr="g1">aaaa</a> <b gr="g1">1111</b>
<a gr="g2">bbbb</a> <b gr="g2">2222</b>
</root>
that must be enclosed by fold tags, resulting (after XSLT) in:
<root>
<c>cccc</c>
<fold><a gr="g1">aaaa</a> <b gr="g1">1111</b></fold>
<fold><a gr="g2">bbbb</a> <b gr="g2">2222</b></fold>
</root>
So, I have a "label for grouping" (#gr) but not imagine how to produce correct fold tags.
I am trying to use the clues of this question, or this other one... But I have a "label for grouping", so I understand that my solution not needs the use of key() function.
My non-general solution is:
<xsl:template match="/">
<root>
<xsl:copy-of select="root/c"/>
<fold><xsl:for-each select="//*[#gr='g1']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
<fold><xsl:for-each select="//*[#gr='g2']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
</root>
</xsl:template>
I need a general solution (!), looping by all #gr and coping (identity) all context that not have #gr... perhaps using identity transform.
Another (future) problem is to do this recursively, with fold of foldings.
In XSLT 1.0 the standard technique to handle this sort of thing is called Muenchian grouping, and involves the use of a key that defines how the nodes should be grouped and a trick using generate-id to extract just the first node in each group as a proxy for the group as a whole.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:key name="elementsByGr" match="*[#gr]" use="#gr" />
<xsl:template match="#*|node()" name="identity">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<!-- match the first element with each #gr value -->
<xsl:template match="*[#gr][generate-id() =
generate-id(key('elementsByGr', #gr)[1])]" priority="2">
<fold>
<xsl:for-each select="key('elementsByGr', #gr)">
<xsl:call-template name="identity" />
</xsl:for-each>
</fold>
</xsl:template>
<!-- ignore subsequent ones in template matching, they're handled within
the first element template -->
<xsl:template match="*[#gr]" priority="1" />
</xsl:stylesheet>
This achieves the grouping you're after, but just like your non-general solution it doesn't preserve the indentation and the whitespace text nodes between the a and b elements, i.e. it will give you
<root>
<c>cccc</c>
<fold>
<a gr="g1">aaaa</a>
<b gr="g1">1111</b>
</fold>
<fold>
<a gr="g2">bbbb</a>
<b gr="g2">2222</b>
</fold>
</root>
Note that if you were able to use XSLT 2.0 then the whole thing becomes one for-each-group:
<xsl:template match="root">
<xsl:for-each-group select="*" group-adjacent="#gr">
<xsl:choose>
<!-- wrap each group in a fold -->
<xsl:when test="#gr">
<fold><xsl:copy-of select="current-group()" /></fold>
</xsl:when>
<!-- or just copy as-is for elements that don't have a #gr -->
<xsl:otherwise>
<xsl:copy-of select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>

XSLT apply templates to tree part

I have the XML tree:
<text>
<plain>abcd<c>efgh</c>ijklm</plain>
<plain>nopq<c>rst</c>uvw<c>xyz</c></plain>
<rp><first><c>asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c>bbbb</c>ccccc<c>xyz</c></plain>
</text>
Then I have code in my XSLT stylesheet ($product_text contains above tree):
<xsl:template name="text_list">
<xsl:if test="$text_count > 0">
<xsl:apply-templates mode="text_item" select="$product_text/text">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template mode="text_item" match="*">
<div class="cmp-post">
<xsl:copy-of select="./*" />
</div>
</xsl:template>
This fragment copies all tree as-is. But I need all "c" nodes to be replaced/modified like this:
<c>efgh</c>
to
<cmp attr="efgh">efgh</c>
<c>rst</c>
to
<cmp attr="rst">rst</c>
etc
(edited) Result I expect:
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
How should I modify text_item template?
Basically, you want to do apply-templates instead of copy-of. copy-of just copies the node; it doesn't do template matching and invocation on the copied elements.
As such, you'll need a few additional templates to get what you want.
<!-- Copy attributes as-is -->
<xsl:template match="#*" mode="text_item">
<xsl:copy-of select="."/>
</xsl:template>
<!-- By default, copy element and text as-is then apply matching on children -->
<xsl:template match="node()" mode="text_item">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="text_item"/>
</xsl:copy>
</xsl:template>
<!-- For 'text' elements, use div instead of direct copy -->
<xsl:template match="text" mode="text_item">
<div class="cmp-post">
<xsl:apply-templates mode="text_item" />
</div>
</xsl:template>
<xsl:template match="c" mode="text_item">
<xsl:copy>
<xsl:attribute name='attr'><xsl:value-of select="."/></xsl:attribute>
<xsl:apply-templates mode="text_item" />
</xsl:copy>
</xsl:template>
(Note that the #* template is just for completeness. Your current input doesn't have any attributes, but if it did, this would copy them to the output.)
Running the above templates on your input with this as a caller
<xsl:template match="/">
<xsl:apply-templates select="." mode="text_item">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:template>
gives the output
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
It should be the same when called against a node variable.

How do I Handle multiple XSLT copy statements on same match?

Are there any XSLT statements that will execute in consideration of other XSLT statements within the same stylesheet?
For example, if I have two copy statements matched to the same node (but only desire one copied node that contains the modifications declared in BOTH copy statements) is there a statement that will do this?
Assume that I cannot put all the transformations in one copy node, but instead have to use two or more.
---Clearer example---
//XML
<toy></toy>
//XSLT
<xsl:template match="toy">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="toy">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
My desired result would be a new toy node that is copied to a new file that has both things applied to it, so something like:
<toy label='SOME TOY'>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
Not two different copies
Is this possible? Is there some way I can redo the first template so that will make this one outcome?
There is rule in XSLT specification, which forbid this - Conflict Resolution for Template Rules.
If node fits several templates - only one template will be executed - in relation to the template import precedence, priority or document order, etc.
But you can separate it with named templates:
<xsl:template match="toy">
<xsl:call-template name="toyAttribute" />
<xsl:call-template name="toyElements" />
</xsl:template>
<xsl:template name="toyAttribute">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template name="toyElements">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
Update:
If you asking about only updating <toy> node with attribute and elements you don't need separate templates:
<!--toy template -->
<xsl:template match="/toys/toy">
<!--copy toy node with namespaces -->
<xsl:copy>
<!-- copy toy node attributes -->
<xsl:apply-templates select="#*" />
<!-- add new attribute or xsl:call-template name="toyAttribute"-->
<xsl:attribute name="label">SOME TOY</xsl:attribute>
<!-- copy toy node child elements -->
<xsl:apply-templates select="node()" />
<!-- add new elements - or xsl:call-template name="toyElements"-->
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
<!--Copy node content -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
which for XML:
<?xml version="1.0" encoding="UTF-8"?>
<toys>
<toy name="a">
<toy-part/>
</toy>
<toy name="b">
<toy-part/>
</toy>
</toys>
will give following result:
<?xml version="1.0" encoding="utf-8"?><toys>
<toy name="a" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
<toy name="b" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
</toys>