apply the whole template or template parts depending on the condition - xslt

I have the template
<xsl:template match="node" mode="some_mode">
<xsl:value-of select="child1" />
<xsl:value-of select="child2" />
<xsl:value-of select="child3" />
</xsl:template>
I want to apply the template so it would select all the nodes specified in the template in one case like this
<xsl:apply-templates select="node" mode="some_node" /> <!-- select all inside the node tag -->
and in another case I want to limit the output and for example not to select <child1> or <child2> nodes. Can I do it with a variable or a param? Or do I have to write another template from scratch?
<xsl:apply-templates select="node" mode="some_node" /> <!-- select only some tags from the node tag -->
In other word I will use this templates several times and I want to contorl the output when applying. I can define the variable, but the documentation says that I can't change the value of a variable once it was defined. Probably the param will wrok but I'm not good with it.

I'm afraid that's not possible without modification of the template you have.
You could think about storing template output in a variable and then processing it to remove child1 and child2 values. But I guess you would be unable to guess which part of your output is coming from child1 and/or child2.
Or you can develop another template that performs that alternative action.
EDIT: Another idea:
Maybe it is possible to apply filtering to get rid of child1 and child2 before applying your template (that would be a sort of multi-pass XSL tranformation).

Use the xsl:if instruction inside your template to branch.
Example:
<xsl:template match="node">
<xsl:value-of select="child1" />
<xsl:if test="price > 10">
<xsl:value-of select="child2" />
<xsl:value-of select="child3" />
</xsl:if>
</xsl:template>
I was hoping for a simple example in the question. As of the face-of-it one is not forthcoming, I'll make up a simple example here, which emodies your question, and show you how the xsl:if instruction is the perfect answer.
Let's say you have a music collection like so ...
<?xml version="1.0"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
And you want to output a html table of your CD's listing title and price. Normally you want both title and price, but on some conditions (say price is less than $10), you want to suppress the output of price, and just have some static text in its place, like "cheaper than 10.00".
So your expected output should be:
<?xml version="1.0" encoding="utf-8"?>
<table>
<tr>
<td>Empire Burlesque</td>
<td>cheaper than 10.00</td>
</tr>
<tr>
<td>Hide your heart</td>
<td>9.90</td>
</tr>
</table>
The style-sheet to produce this transformation could be:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="//cd"/>
</table>
</xsl:template>
<xsl:template match="cd">
<tr>
<td><xsl:value-of select="title"/></td>
<xsl:if test="price < 10">
<td><xsl:value-of select="price"/></td>
</xsl:if>
<xsl:if test="not(price < 10)">
<td>cheaper than 10.00</td>
</xsl:if>
</tr>
</xsl:template>
</xsl:stylesheet>
And there you have the perfect answer - how to apply the whole template or just parts of it depending on a condition. In this case the condition is that the price is less than $10.

I recommend using separate templates with different match patterns and/or in different modes. This is to be preferred to using conditionals and thus writing messy and potentially buggy code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates mode="allChildren"/>
==========
<xsl:apply-templates mode="child3Only"/>
</xsl:template>
<xsl:template match="node" mode="allChildren">
<xsl:copy-of select="*"/>
</xsl:template>
<xsl:template match="node" mode="child3Only">
<xsl:copy-of select="child3"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following document (similar to the one you used in a previous question):
<document>
<node>
<child1>Child 1</child1>
<child2>Child 2</child2>
<child3>Child 3</child3>
</node>
<anotherNode />
</document>
the wanted result is produced:
<child1>Child 1</child1><child2>Child 2</child2><child3>Child 3</child3>
==========
<child3>Child 3</child3>

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)"

XSL cross reference

I am stuck with a XSLT 1.0 problem. I tried to find info on StackOverflow but I couldn't apply the examples.
Here is the structure of my XML:
<XML>
<PR>
<AS>
<ID_AS>AS-001</ID_AS>
<FIRST>
<ID_CATALOG>Id-001</ID_CATALOG>
<STATUS>NOK</STATUS>
</FIRST>
<SECOND>
<ID_CATALOG>Id-002</ID_CATALOG>
<STATUS>OK</STATUS>
</SECOND>
</AS>
<AS>
<ID_AS>AS-002</ID_AS>
<FIRST>
<ID_CATALOG>Id-003</ID_CATALOG>
<STATUS>OK</STATUS>
</FIRST>
<SECOND>
<ID_CATALOG>Id-004</ID_CATALOG>
<STATUS>OK</STATUS>
</SECOND>
</AS>
</PR>
<METADATA>
<ID_CATALOG>Id-001</ID_CATALOG>
<ANGLES>32.25</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-002</ID_CATALOG>
<ANGLES>18.75</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-003</ID_CATALOG>
<ANGLES>5.23</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-004</ID_CATALOG>
<ANGLES>12.41</ANGLES>
</METADATA>
</XML>
I want to display for each AS, the FIRST/ID_CATALOG, FIRST/STATUS and ANGLES corresponding to the ID_CATALOG, then SECOND/etc.
The output would be similar to:
AS-001
Id-001 NOK 32.25
Id-002 OK 18.75
AS-002
Id-003 OK 5.23
Id-004 OK 12.41
I tried the following XSL but I only get the ANGLES for the first item
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns="http://earth.google.com/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:hma="http://earth.esa.int/hma" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:output method="xml" indent="yes" encoding="ISO-8859-1"/>
<!--==================MAIN==================-->
<xsl:template match="/">
<html>
<body>
AS List:
<br/><br/>
<xsl:call-template name="ASandCo"/>
</body>
</html>
</xsl:template>
<!--==================TEMPLATES==================-->
<xsl:template name="ASandCo">
<AS>
<xsl:for-each select="XML/PR/AS">
<xsl:value-of select="ID_AS"/>
<br/>
<xsl:value-of select="FIRST/ID_CATALOG"/> - <xsl:value-of select="FIRST/STATUS"/> -
<xsl:if test="contains(/XML/METADATA/ID_CATALOG, FIRST/ID_CATALOG)">
<xsl:value-of select="/XML/METADATA/ANGLES"/>
</xsl:if>
<br/>
<xsl:value-of select="SECOND/ID_CATALOG"/> - <xsl:value-of select="SECOND/STATUS"/> -
<xsl:if test="contains(/XML/METADATA/ID_CATALOG, SECOND/ID_CATALOG)">
<xsl:value-of select="/XML/METADATA/ANGLES"/>
</xsl:if>
<br/><br/>
</xsl:for-each>
</AS>
</xsl:template>
</xsl:stylesheet>
This XSLT will be applied to very large XML files, so I am trying to find the most efficient way.
Thank you very much in advance!
It seems like you want to look up some metadata metadata based on the ID_CATALOG value.
An efficient way to do this is by using a key. You can define a key on the top level:
<xsl:key name="metadata-by-id_catalog" match="METADATA" use="ID_CATALOG"/>
And then you can look up the ANGLES value using the key for a given ID_CATALOG value like this:
<xsl:value-of select="key('metadata-by-id_catalog', FIRST/ID_CATALOG)/ANGLES"/>
and this:
<xsl:value-of select="key('metadata-by-id_catalog', SECOND/ID_CATALOG)/ANGLES"/>

XSLT - how to apply a template to every node of the type?

I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>

one xslt to rule them all

So here is my problem...
I have 2 xsl transformations and they both have xsl:for-each in them as a starting point.
I need to create one (master) xslt which will call them. But of course, there is a catch.
This new xslt should give output one node from first xslt, then one node from second.(both xslt have EmployeeId, but are basically different reports that have to be printed one after another).
Because these existing xslt's have for-each in them, when I include them in this master xslt I get an output: all nodes from first xslt, then all nodes from second.
Also this 2 xslt's have to be backward compatible, so they should work as before if they are not called from this master template.
I'm a starter to xslt and I managed to create some reports when working with only one xslt,but I can't seem to find the solution to this problem, so I appreciate all the help I can get.
I was thinking of creating a new xslt that would be a mix of two that I have, but this was ruled out by my boss.
Thanks,
Benxy
EDIT:
Here goes some examples as requested:
This is the first xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="4.0" encoding="windows-1250" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/" name="testXslt1">
<xsl:for-each select="a">
<table>
<tr>
<td>
<xsl:value-of select="#SomeData"></xsl:value-of>
</td>
etc.
</tr>
<xsl:apply-templates select="b" mode="tmp"/>
</table>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()" mode="tmp">
<tr >
<td><xsl:value-of select="#SomeOtherData"></xsl:value-of></td>
etc.
</tr>
</xsl:template>
Second xslt is similar to first one.
In Master xslt I would import both of them and in for each call templates testXslt1 and testXslt2.
This new xslt should give output one
node from first xslt, then one node
from second
Here is a simple example how this can be done:
This transformation combines the results of two templates that are applied on the XML document.
The first templates produces twice the value of every /*/* node. The second transformation produces the square of the value of every /*/* node. The results of applying the two remplates each on the XML document are mixed together in alternation as required, and this is the final result:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfResults1">
<xsl:apply-templates select="/"
mode="transform1"/>
</xsl:variable>
<xsl:variable name="vrtfResults2">
<xsl:apply-templates select="/"
mode="transform2"/>
</xsl:variable>
<xsl:variable name="vResults1"
select="ext:node-set($vrtfResults1)/node()"/>
<xsl:variable name="vResults2"
select="ext:node-set($vrtfResults2)/node()"/>
<xsl:for-each select="$vResults1">
<xsl:variable name="vPos" select="position()"/>
<xsl:copy-of select=".|$vResults2[$vPos]"/>
</xsl:for-each>
<xsl:if test=
"count($vResults2) > count($vResults2)">
<xsl:copy-of select=
"$vResults2[position()>count($vResults1)]"/>
</xsl:if>
</xsl:template>
<xsl:template match="/" mode="transform1">
<xsl:for-each select="/*/*">
<xsl:copy>
<xsl:value-of select=".+."/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="/" mode="transform2">
<xsl:for-each select="/*/*">
<xsl:copy>
<xsl:value-of select=".*."/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on 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>
the wanted, correct result is produced:
<num>2</num>
<num>1</num>
<num>4</num>
<num>4</num>
<num>6</num>
<num>9</num>
<num>8</num>
<num>16</num>
<num>10</num>
<num>25</num>
<num>12</num>
<num>36</num>
<num>14</num>
<num>49</num>
<num>16</num>
<num>64</num>
<num>18</num>
<num>81</num>
<num>20</num>
<num>100</num>

Comparing an xs:boolean value in XSLT?

I have an xml schema with an element defined of type xs:boolean like this:
<xs:element name="useful"
minOccurs="1" maxOccurs="1"
type="xs:boolean"/>
I'm trying to use a choose/when/otherwise block in XSL to output something specific based on the value of that, like this:
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
I've tried every variation I can think of for that comparison (using true(), false(), 1, 0, removing the #, using //#) and it always prints out 'Yes'. What am I doing wrong here?
EDIT (by request from Martin Honnen):
Here's a sample of XML that's being used:
<?xml version="1.0"?>
<functions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="functions.xsd">
<function>
<name>func1</name>
<useful>0</useful>
</function>
<function>
<name>func2</name>
<useful>1</useful>
</function>
</functions>
This causes problems with using xsl:template I think, so I'm using xsl:for-each to loop through each function, and trying to output something specific for each useful tag. xsl:template can only be used at the top level of the xslt, correct?
The full XSLT file is
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html><body>
<h2>Functions</h2>
<table border="1">
<tr>
<th>Functions</th>
<th>Useful</th>
</tr>
<xsl:for-each select="functions/function">
<tr>
<td>
<xsl:value-of select="name"/>
</td>
<td>
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
</body></html>
</xsl:template>
</xsl:stylesheet>
I think the schema defines an element, but you are testing the value of an attribute -- this may be your main problem.
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
I've tried every variation I can think
of for that comparison (using
true(), false(), 1, 0,
removing the #, using //#) and it
always prints out 'Yes'. What am I
doing wrong here?
Most probably the current node is not an element that has an attribute named useful.
Here is how to work in XSLT 1.0 with the useful element type as defined by the schema:
<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="useful/text()[. = 'true' or .='1']">
Yes
</xsl:template>
<xsl:template match="useful/text()[. = 'false' or .='0']">
No
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<t>
<useful>true</useful>
<useful>false</useful>
<useful>1</useful>
<useful>0</useful>
</t>
the wanted, correct result is produced:
<t>
<useful>
Yes
</useful>
<useful>
No
</useful>
<useful>
Yes
</useful>
<useful>
No
</useful>
</t>
In XSLT 2.0 (Schema-aware processor), you need to import the schema (use <xsl:import-schema>) then you can simply use:
('no', 'yes')[number(useful)+1]
where the current node is the parent of the useful element.
Your schema defines an element named "useful" but the schema is not relevant at all unless you are trying to use schema aware XSLT 2.0.
Doing #useful in XPath selects an attribute of that name of the context node which does not make any sense with the schema you have, as that defines an element.
So show us your instance XML the XSLT is processing, then we can show you some XSLT code:
<xsl:template match="useful">
<xsl:choose>
<xsl:when test=". = 'false' or . = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
</xsl:template>
With schema-aware XSLT 2.0 you could do
<xsl:template match="useful">
<xsl:value-of select="if (data(.)) then 'Yes' else 'No'"/>
</xsl:template>