XSLT: parametrize template - xslt

This is my xslt fragment:
<informacioPacient>
<talla>
<xsl:value-of select="//ns3:observation[ns3:code/#code='50373000' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</talla>
<pes>
<xsl:value-of select="//ns3:observation[ns3:code/#code='27113001' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</pes>
<edad>
<xsl:value-of select="//ns3:observation[ns3:code/#code='424144002' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</edad>
<etnia>
<xsl:value-of select="//ns3:observation[ns3:code/#code='372148003' and ns3:code/#codeSystem='2.16.840.1.113883.2.19.1.26']//ns3:value/#value" />
</etnia>
<fumador>
<xsl:value-of select="//ns3:observation[ns3:code/#code='230056004' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</fumador>
</informacioPacient>
I'd like to know if I could to improve readness. As you can see, select statement is always similar. The only elements change are literal values.
Could I do something more generic in order to get the same behavior?

xslt 1.0 with a key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:ns3="urn:local"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="code-and-system" match="ns3:observation" use="concat(ns3:code/#code,'#', ns3:code/#codeSystem)"/>
<xsl:template match="/">
<informacioPacient>
<talla>
<xsl:value-of select="key('code-and-system', '50373000#2.16.840.1.113883.6.96')//ns3:value/#value"/>
</talla>
<pes>
<xsl:value-of select="key('code-and-system','27113001#2.16.840.1.113883.6.96')//ns3:value/#value" />
</pes>
<edad>
<xsl:value-of select="key('code-and-system','424144002#2.16.840.1.113883.6.96')//ns3:value/#value" />
</edad>
<etnia>
<xsl:value-of select="key('code-and-system','372148003#2.16.840.1.113883.2.19.1.26')//ns3:value/#value" />
</etnia>
<fumador>
<xsl:value-of select="key('code-and-system','230056004#2.16.840.1.113883.6.96')//ns3:value/#value" />
</fumador>
</informacioPacient>
</xsl:template>
</xsl:stylesheet>
xslt 2.0: As Martin Honnen commented, use a function
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:ns3="urn:local"
xmlns:f="local"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xsl:variable name="observations" as="element()*" select="//ns3:observation"/>
<xsl:template match="/">
<informacioPacient>
<talla>
<xsl:value-of select="f:getValue('50373000','2.16.840.1.113883.6.96')" />
</talla>
<pes>
<xsl:value-of select="f:getValue('27113001','2.16.840.1.113883.6.96')" />
</pes>
<edad>
<xsl:value-of select="f:getValue('424144002','2.16.840.1.113883.6.96')" />
</edad>
<etnia>
<xsl:value-of select="f:getValue('372148003','2.16.840.1.113883.2.19.1.26')" />
</etnia>
<fumador>
<xsl:value-of select="f:getValue('230056004','2.16.840.1.113883.6.96')" />
</fumador>
</informacioPacient>
</xsl:template>
<xsl:function name="f:getValue">
<xsl:param name="code" as="xs:string"/>
<xsl:param name="codeSystem" as="xs:string"/>
<xsl:value-of select="$observations[ns3:code[#code=$code and #codeSystem=$codeSystem]]//ns3:value/#value"/>
</xsl:function>
</xsl:stylesheet>

Related

XSLT Filtering based on nodes and attributes

I'm new on XSLT and have a requirement to use XSLT to select values from an XML file of this form :
<?xml version="1.0" encoding="utf-8"?>
<deviceInstallation>
<order>
<orderID>296</orderID>
<orderPosID>1</orderPosID>
<action rvcd="2">unInstall</action>
</order>
<deviceInfo>
<actionInfo rvcd="1">Software Install</actionInfo>
<device>
<deviceID>1436</deviceID>
</device>
</deviceInfo>
<deviceInfo>
<actionInfo rvcd="2">Software Uninstall</actionInfo>
<device>
<deviceID>4112</deviceID>
</device>
</deviceInfo>
</deviceInstallation>
I need to filter the elements deviceinfo based on the attribute rvcd = 2 because this is what is defined on the same attribute of child element action of the order element.
I tried to write and xslt and used a var to get the value to filter but don't know how to use it :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" />
<xsl:variable name="separator" select="';'" />
<xsl:variable name="newline" select="'
'" />
<xsl:variable name="actionFilter" select="/deviceInstallation/order/action[]/#rvcd" />
<xsl:template match="/">
<xsl:text>orderID;DeviceID</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="/deviceInstallation">
<!--OrderID-->
<xsl:value-of select="/deviceInstallation/order/orderID"/>
<xsl:value-of select="$separator"/>
<!--DeviceID-->
<xsl:value-of select="/deviceInstallation/deviceInfo/device/deviceID"/> <!-- here want to filter on rvcd-->
<xsl:value-of select="$separator"/>
<xsl:value-of select="$newline" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Any help appreciated
IIUC, you want to do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/deviceInstallation">
<xsl:variable name="orderID" select="order/orderID" />
<xsl:variable name="actionFilter" select="order/action/#rvcd" />
<xsl:text>orderID;DeviceID
</xsl:text>
<xsl:for-each select="deviceInfo[actionInfo/#rvcd=$actionFilter]">
<xsl:value-of select="$orderID" />
<xsl:text>;</xsl:text>
<xsl:value-of select="device/deviceID" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
or perhaps a bit more elegantly:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="dev" match="deviceInfo" use="actionInfo/#rvcd" />
<xsl:template match="/deviceInstallation">
<xsl:variable name="orderID" select="order/orderID" />
<xsl:text>orderID;DeviceID
</xsl:text>
<xsl:for-each select="key('dev', order/action/#rvcd)">
<xsl:value-of select="$orderID" />
<xsl:text>;</xsl:text>
<xsl:value-of select="device/deviceID" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT - extract all text but last subsection

Looking to parse out a namespace from a full class name in xml.
Data example:
<results>
<test-case name="Co.Module.Class.X">
</results>
End result (going to csv format):
,Co.Module.Class
Stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" indent="yes" encoding="ISO-8859-1"/>
<xsl:param name="delim" select="','" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
FullTestName, Namespace
<xsl:apply-templates select="//test-case" />
</xsl:template>
<xsl:template match="test-case">
<xsl:apply-templates />
<xsl:value-of select="#name" />
<xsl:value-of select="$delim" />
<xsl:value-of select="function to go here for nameWithJustNamespace" />
<xsl:value-of select="$break" />
</xsl:template>
I understand the process would need a last index of "." to be called once, yet I'm not finding XSLT to have that function. How to best accomplish this?
To do this in pure XSLT 1.0, you need to call a named recursive template, e.g.:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/results">
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="test-case/#name"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="remove-last-token">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="'.'"/>
<xsl:value-of select="substring-before($text, $delimiter)"/>
<xsl:if test="contains(substring-after($text, $delimiter), $delimiter)">
<xsl:value-of select="$delimiter"/>
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This pure XSLT 1.0 transformation (shorter, no conditional XSLT operations, single template):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring('.', 2 - ($pDotIndex > 0)),$vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<results>
<test-case name="Co.Module.Class.X"/>
</results>
produces the wanted, correct result:
Co.Module.Class
Part 2
With a slight modification the following transformation produces the complete CSV:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring(',', 2 - (position() > 1)),
substring('.', 2 - ($pDotIndex > 0)), $vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<results>
<test-case name="Co.Module.Class.X"/>
<test-case name="Co2.Module2.Class2.Y"/>
<test-case name="Co3.Module3.Class3.Z"/>
</results>
the wanted, correct (CSV) result is produced:
Co.Module.Class,Co2.Module2.Class2,Co3.Module3.Class3

Select a parallel node in xslt

Consider following xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<base>
<a>
<b>
<c>Text 1</c>
</b>
</a>
</base>
<base>
<a>
<b>
<c>Text 2</c>
</b>
</a>
</base>
</root>
and this xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:value-of select="text()" />
<!-- i need Text 2 here -->
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
The original xml is much more nested an i don't know the exact structure. But there is a parallel node with the same structure. If my template is at //root/base[1]/a/b/c I want to reference //root/base[2]/a/b/c
However I only know that I am in some node below //root/base[1] and that there is the same node in //root/base[2].
Is there a possibility to accomplish this goal?
This stylesheet requires no extensions, and does not make any assumption on unique names; the boring part is to pass the correct "twin node" every time you call xsl:apply-templates:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]">
<xsl:with-param name="twin" select="root/base[2]"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*">
<xsl:param name="twin"/>
<xsl:if test="text()">
<xsl:value-of select="text()" />
<xsl:value-of select="$twin/text()"/>
</xsl:if>
<xsl:for-each select="*">
<xsl:variable name="pos" select="position()"/>
<xsl:apply-templates select=".">
<xsl:with-param name="twin" select="$twin/*[$pos]"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Solution with using saxon:evaluate extension (http://saxon.sourceforge.net/saxon7.9/extensions.html#evaluate)
You can also use dyn:evaluate if you are using xslt 1.0 processors (http://exslt.org/dyn/index.html):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
xmlns:saxon="http://saxon.sf.net/"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="path">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="rewritedPath" select="concat('/root/base[2]', substring($path, 11))"/>
<xsl:value-of select="text()" />
<xsl:value-of select="saxon:evaluate($rewritedPath)" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="constructPath">
<xsl:param name="node"/>
<xsl:choose>
<xsl:when test="$node/parent::node()/name()">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="$node/parent::node()"/>
</xsl:call-template>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Here we use recursive template "constructPath" to create string and process it in evaluate() function. This will select the exact node as in the parallel branch. Hope this will help.
Try the code below. This code will work only if each base element has children with unique element name.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="text()" />
<xsl:value-of select="ancestor::base/following-sibling::base[1]//*[name() = $name]" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
However you can use dyn:evaluate from exslt extension or saxon:eval

How to pattern match in XSLT and add values

As part of an XSLT, I need to add all the values of the "Duration" element and display the value. Now, the below XML is a part of the larger XML I'm working on. In the below XML, I need to match
a/TimesheetDuration/Day*/Duration, add the values and display them. I dont want to store all the values in variables and add them. Is there any other clean way of doing this?
<?xml version="1.0" ?>
<a>
<TimesheetDuration>
<Day1>
<BusinessDate>6/12/2013</BusinessDate>
<Duration>03:00</Duration>
</Day1>
<Day2>
<BusinessDate>6/13/2013</BusinessDate>
<Duration>04:00</Duration>
</Day2>
<Day3>
<BusinessDate>6/14/2013</BusinessDate>
<Duration>05:00</Duration>
</Day3>
</TimesheetDuration>
</a>
An XPath 2.0 solution, assuming the durations are in the form HH:MM, would be
sum(for $d in a//Duration
return xs:dayTimeDuration(replace($d, '(..):(..)', 'PT$1H$2M')))
In xslt 1.0 you could do it for example with following stylesheet
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Durations>
<xsl:apply-templates select="a/TimesheetDuration/node()[starts-with(name(),'Day')]" />
<xsl:variable name="hours">
<xsl:call-template name="sumHours">
<xsl:with-param name="Day" select="a/TimesheetDuration/node()[starts-with(name(),'Day')][1]" />
</xsl:call-template>
</xsl:variable>
<SumOfHours>
<xsl:value-of select="$hours" />
</SumOfHours>
<!-- Sum of minutes would be calculated similarly -->
</Durations>
</xsl:template>
<xsl:template match="node()[starts-with(name(),'Day')]">
<xsl:copy-of select="Duration" />
</xsl:template>
<xsl:template name="sumHours">
<xsl:param name="tmpSum" select="0" />
<xsl:param name="Day" />
<xsl:variable name="newTmpSum" select="$tmpSum + substring-before($Day/Duration, ':')" />
<xsl:choose>
<xsl:when test="$Day/following-sibling::node()[starts-with(name(),'Day')]">
<xsl:call-template name="sumHours">
<xsl:with-param name="tmpSum" select="$newTmpSum" />
<xsl:with-param name="Day" select="$Day/following-sibling::node()[starts-with(name(),'Day')]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTmpSum" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It produces output
<?xml version="1.0" encoding="UTF-8"?>
<Durations>
<Duration>03:00</Duration>
<Duration>04:00</Duration>
<Duration>01:00</Duration>
<SumOfHours>8</SumOfHours>
</Durations>

call xslt template with parameter

I have this xslt:
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
<xsl:for-each select="$elementToDump/#*">
<xsl:text>
</xsl:text> <!-- newline char -->
<xsl:value-of select="name()" /> : <xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
i want to display every element (as in name/value), how do i call this template?
Since the template expects a node set, you must do:
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="some/xpath" />
</xsl:call-template>
Try something like this:
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump">foo</xsl:with-param>
</xsl:call-template>
The original answer does not use the parameter. It only works if the paramater = the current element. This takes the parameter into account.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="element()">
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="." />
</xsl:call-template>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
Node:
<xsl:value-of select="name($elementToDump)" />
:
<xsl:value-of select="text($elementToDump)" />
<xsl:for-each select="$elementToDump/#*">
Attribute:
<xsl:value-of select="name()" />
:
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
There are a number of issues in your original XSLT, so I worked through it and got you the following code which does what you want I believe:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="element()">
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="." />
</xsl:call-template>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
Node:
<xsl:value-of select="name()" />
:
<xsl:value-of select="text()" />
<xsl:for-each select="attribute::*">
Attribute:
<xsl:value-of select="name()" />
:
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>