I have a problem. I get the data from xml then transform it with xslt.
Let us say I have a xml file:
<?xml version="1.0"?>
<root>
<row id="1" fname="Dan" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1234 LottaWork Ave.</street>
<city>AnyTown</city>
<zip>85786</zip>
</address>
</row>
<row id="2" fname="Elaine" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1233 Books Way</street>
<city>AnyTown</city>
<zip>85784</zip>
</address>
</row>
</root>
And this stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="no"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
<xsl:attribute name="fname">
<xsl:value-of select="name/fname"/>
</xsl:attribute>
<xsl:attribute name="lname">
<xsl:value-of select="name/lname"/>
</xsl:attribute>
<xsl:for-each select="address">
<xsl:copy-of select="."/>
</xsl:for-each> </row>
</xsl:template>
</xsl:stylesheet
How can limit this to 3 records, then after 3 records it create a tr tag?
For example:
<table>
<tr>
<td>Address1</td>
<td>Address2</td>
<td>Address3</td>
</tr>
<tr>
<td>Address4</td>
<td>Address5</td>
<td>Address6</td>
</tr>
</table
try something like
<xsl:for-each select="PATH">
<xsl:variable name="pos" select="position() mod 3" />
</xsl:for-each>
then you can work with
<xsl:if test="$pos = 0">
</xsl:if>
and
<xsl:if test="$pos != 0">
</xsl:if>
if $pos = 0 means that you reached the 3rd row
Here are some good resources to learn more about XSLT and XPath
http://w3schools.com/xsl/default.asp
http://w3schools.com/xpath/default.asp
Related
I'm creating an HTML table that is based on dynamic columns(Hostname) and rows(VLAN). I'm running into an issue after the first position data(1 row for all hosts) in the is written to the table; selects the 2nd position data just fine, but the $vCol variable takes it back to the first line of the $vCols variable.
Appreciate any direction you can offer. Thanks
XSLT-2.0 code:
<?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="2.0">
<xsl:key name="kHostNameByValue" match="Hostname" use="."/>
<xsl:key name="kVLAN" match="Hostname" use="."/>
<xsl:variable name="vCols" select="/*/*/Hostname[generate-id()=generate-id(key('kHostNameByValue',.)[1])]"/>
<xsl:variable name="vMaxRows">
<xsl:for-each select="$vCols">
<xsl:sort data-type="number" order="descending" select="count(key('kVLAN', .))"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('kVLAN', .))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="DocumentRoot">
<table border="1">
<!-- Print out column headings by Hostname -->
<tr>
<xsl:apply-templates select="$vCols"/>
</tr>
<!-- Print out VLANs by Hostname -->
<xsl:for-each-group select="(//Row)[not(position() > $vMaxRows)]" group-by="VLAN">
<tr>
<xsl:variable name="vPos" select="position()"/>
<!-- Issue on 2nd position when $vCols goes back to 1st hostname at line 3 -->
<xsl:for-each select="$vCols">
<td>
<xsl:value-of select="..[$vPos]/VLAN"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
<xsl:template match="Hostname">
<td>
<b>
<xsl:value-of select="." />
</b>
</td>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Here's the sample XML data.
<?xml version="1.0" encoding="UTF-8"?>
<DocumentRoot>
<Row>
<Hostname>switch-1</Hostname>
<HostIP>10.29.178.102</HostIP>
<VLAN>10</VLAN>
<VLANName>VLAN-10</VLANName>
</Row>
<Row>
<Hostname>switch-1</Hostname>
<HostIP>10.29.178.102</HostIP>
<VLAN>500</VLAN>
<VLANName>VLAN-500</VLANName>
</Row>
<Row>
<Hostname>switch-2</Hostname>
<HostIP>10.29.178.103</HostIP>
<VLAN>11</VLAN>
<VLANName>VLAN-11</VLANName>
</Row>
<Row>
<Hostname>switch-2</Hostname>
<HostIP>10.29.178.103</HostIP>
<VLAN>501</VLAN>
<VLANName>VLAN-500</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>15</VLAN>
<VLANName>VLAN-15</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>25</VLAN>
<VLANName>VLAN-25</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>35</VLAN>
<VLANName>VLAN-35</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>45</VLAN>
<VLANName>VLAN-45</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>55</VLAN>
<VLANName>VLAN-55</VLANName>
</Row>
</DocumentRoot>
Output (Actual and desired):
Firstly, I think your maxRows variable can be simplified to this
<xsl:variable name="maxRows" select="max(//Row/count(key(hostNameByValue, Hostname)))" />
Where I have defined hostNameByValue key like so:
<xsl:key name="hostNameByValue" match="Row" use="Hostname"/>
You can also use distinct-values to get the distinct column names
<xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />
So, assuming $rowNum is the current number (within a <xsl:for-each select="1 to $maxRows"> block, the code to get the current cell value would be this
<xsl:for-each select="$cols">
<th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
</xsl:for-each>
(Where $doc is a reference to the initial XML document, because within the xsl:for-each is now a sequence of atomic values)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="hostNameByValue" match="Row" use="Hostname"/>
<xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />
<xsl:variable name="maxRows" select="max(//Row/count(key('hostNameByValue', Hostname)))" />
<xsl:variable name="doc" select="/" />
<xsl:template match="DocumentRoot">
<table>
<tr>
<xsl:for-each select="$cols">
<th><xsl:value-of select="."/></th>
</xsl:for-each>
</tr>
<xsl:for-each select="1 to $maxRows">
<xsl:variable name="rowNum" select="position()"/>
<tr>
<xsl:for-each select="$cols">
<th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/6r5Gh3N
I think using a group by might make this more complicated than it needs to be. Basically for each row you need to iterate over all columns and output the cell, if it exists, or an empty cell otherwise. This means you should iterate over an index rather then row elements:
<xsl:for-each select="1 to $numRows">
<xsl:variable name="rowIndex" select="position()" />
<tr>
<xsl:for-each select="$vCols">
<xsl:variable name="cell" select="//Row[string(Hostname) = .][position() = $rowIndex]" />
<xsl:apply-templates select="$cell">
<xsl:if test="not($cell)">
<td></td>
</xsl:if>
</xsl:for-each>
<tr>
</xsl:for.each>
I don't think in XSLT 2 or 3, once you use for-each-group, you need any of the keys, you can just store the grouping result and then process it, for instance to store the grouping result as XML in XSLT 2 or 3 you could use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="DocumentRoot">
<table>
<xsl:variable name="cols" as="element(col)*">
<xsl:for-each-group select="Row" group-by="Hostname">
<col name="{current-grouping-key()}">
<xsl:sequence select="current-group()"/>
</col>
</xsl:for-each-group>
</xsl:variable>
<thead>
<tr>
<xsl:for-each select="$cols">
<th>{#name}</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:variable name="rows" select="max($cols!count(Row))"/>
<xsl:for-each select="1 to $rows">
<xsl:variable name="row" select="."/>
<tr>
<xsl:for-each select="$cols">
<td>{Row[$row]/VLAN}</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq6Q/3 is XSLT 3 with the ! map operator and the text value templates but https://xsltfiddle.liberty-development.net/94rmq6Q/4 rewrites that as XSLT 2 with value-of instead.
Or in XSLT 3 you could store the grouping result in a sequence of arrays:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="DocumentRoot">
<table>
<xsl:variable name="cols" as="array(element(Row))*">
<xsl:for-each-group select="Row" group-by="Hostname">
<xsl:sequence select="array{ current-group() }"/>
</xsl:for-each-group>
</xsl:variable>
<thead>
<tr>
<xsl:for-each select="$cols">
<th>{?1/Hostname}</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:variable name="rows" select="max($cols!array:size(.))"/>
<xsl:for-each select="1 to $rows">
<xsl:variable name="row" select="."/>
<tr>
<xsl:for-each select="$cols">
<td>{if ($row le array:size(.))
then .($row)/VLAN
else ()}</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq6Q/2
I'm trying to get the most recent tax data for a locality. I'm using the following input and xslt transformation:
<?xml version='1.0' encoding='UTF-8'?>
<root>
<row>
<Applies_To>Abbott Twp (Pennsylvania:Potter)</Applies_To>
<Tax_Code>42530035</Tax_Code>
<Tax>Local City Withholding (Resident)</Tax>
<Start_Date>2011-07-01</Start_Date>
<Tax_Rate>0.005</Tax_Rate>
</row>
<row>
<Applies_To>Abbott Twp (Pennsylvania:Potter)</Applies_To>
<Tax_Code>42530035</Tax_Code>
<Tax>Local City Withholding (Resident)</Tax>
<Start_Date>2015-01-01</Start_Date>
<Tax_Rate>0.099</Tax_Rate>
</row>
<row>
<Applies_To>Abbott Twp (Pennsylvania:Potter)</Applies_To>
<Tax_Code>42530035</Tax_Code>
<Tax>Local City Withholding (Work)</Tax>
<Start_Date>2011-07-01</Start_Date>
<Tax_Rate>0</Tax_Rate>
</row>
<row>
<Applies_To>Abbottstown Boro (Pennsylvania:Adams)</Applies_To>
<Tax_Code>42010033</Tax_Code>
<Tax>Local City Withholding (Resident)</Tax>
<Start_Date>2011-07-01</Start_Date>
<Tax_Rate>0.005</Tax_Rate>
</row>
<row>
<Applies_To>Abbottstown Boro (Pennsylvania:Adams)</Applies_To>
<Tax_Code>42010033</Tax_Code>
<Tax>Local City Withholding (Work)</Tax>
<Start_Date>2012-07-01</Start_Date>
<Tax_Rate>0.01</Tax_Rate>
</row>
<row>
<Applies_To>Abbottstown Boro (Pennsylvania:Adams)</Applies_To>
<Tax_Code>42010033</Tax_Code>
<Tax>xxxx</Tax>
<Start_Date>2012-07-01</Start_Date>
<Tax_Rate>0.01</Tax_Rate>
</row>
</root>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="*/row"
group-by="Tax_Code">
<xsl:sort order="descending" select="Start_Date"/>
<row>
<Tax_Code>
<xsl:attribute name="Code" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="Tax">
<xsl:call-template name="Tax_Code"/>
</xsl:for-each-group>
</Tax_Code>
</row>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="Tax_Code">
<Tax_Rate>
<xsl:attribute name="taxtype">
<xsl:value-of select="Tax"/>
</xsl:attribute>
<xsl:value-of select="Tax_Rate"/>
</Tax_Rate>
<date>
<xsl:value-of select="Start_Date"></xsl:value-of>
</date>
</xsl:template>
</xsl:stylesheet>
I then get the following output:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xs="http://www.w3.org/2001/XMLSchema">
<row>
<Tax_Code Code="42530035">
<Tax_Rate taxtype="Local City Withholding (Resident)">0.005</Tax_Rate>
<date>2011-07-01</date>
<Tax_Rate taxtype="Local City Withholding (Work)">0</Tax_Rate>
<date>2011-07-01</date>
</Tax_Code>
</row>
<row>
<Tax_Code Code="42010033">
<Tax_Rate taxtype="Local City Withholding (Resident)">0.005</Tax_Rate>
<date>2011-07-01</date>
<Tax_Rate taxtype="Local City Withholding (Work)">0.01</Tax_Rate>
<date>2012-07-01</date>
<Tax_Rate taxtype="xxxx">0.01</Tax_Rate>
<date>2012-07-01</date>
</Tax_Code>
</row>
</root>
As you can see, the output doesn't have the most recent tax rate for tax code 42530035. Even if I try to add position() = 1, I still don't get the most entry. I can't find an explanation of why the sorting doesn't work.
The items in your inner-most xsl:for-each-group are returned in document order. If you want to process the first sorted item, you could use an xsl:for-each and xsl:sort the current-group() items, and then only process the first one:
<xsl:for-each-group select="current-group()" group-by="Tax">
<xsl:for-each select="current-group()">
<xsl:sort order="descending" select="Start_Date" />
<xsl:if test="position() = 1">
<xsl:apply-templates select="." mode="Tax_Code"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each-group>
Complete stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="*/row"
group-by="Tax_Code">
<xsl:sort order="descending" select="Start_Date"/>
<row>
<Tax_Code>
<xsl:attribute name="Code" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="Tax">
<xsl:for-each select="current-group()">
<xsl:sort order="descending" select="Start_Date" />
<xsl:if test="position() = 1">
<xsl:apply-templates select="." mode="Tax_Code"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each-group>
</Tax_Code>
</row>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="Tax_Code" match="row" mode="Tax_Code">
<Tax_Rate>
<xsl:attribute name="taxtype">
<xsl:value-of select="Tax"/>
</xsl:attribute>
<xsl:value-of select="Tax_Rate"/>
</Tax_Rate>
<date>
<xsl:value-of select="Start_Date"/>
</date>
</xsl:template>
</xsl:stylesheet>
Use
<date>
<xsl:value-of select="max(current-group()/xs:date(Start_Date))"></xsl:value-of>
</date>
to output the highest date in the group you have. The ordering with the xsl:sort you have only applied in the outer grouping and only for the groups, in the inner group you currently output the date of the first item in the group (in the original document order).
I am trying to find the last element in my input xml, which looks like
<ROOT>
<Items>
<Item>
<FieldTag>AMOUNT</FieldTag>
</Item>
<Item>
<FieldTag>CAT_TYPE</FieldTag>
</Item>
<Item>
<FieldTag>NUMBER</FieldTag>
</Item>
</Items>
<getResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MAIN>
<ArrayItem>
<NUMBER>123456789</NUMBER>
<CTRY>GB</CTRY>
<NAME>TEST NAME</NAME>
<RS>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<AMOUNT>11111</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<AMOUNT>22222</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<CAT_TYPE>10000</CAT_TYPE>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<CAT_TYPE>20000</CAT_TYPE>
</ArrayOfRSItem>
</RS>
</ArrayItem>
</MAIN>
</getResponse>
</ROOT>
and I use following XSLT file for transformation
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:urn="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02"
xmlns:func="http://www.test.com/"
exclude-result-prefixes="urn func" xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon">
<xsl:output method ="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:if test="count(/ROOT/getResponse/MAIN/ArrayItem) > 0" >
<Data>
<xsl:for-each select="/ROOT/getResponse/MAIN/ArrayItem" >
<xsl:element name="NodeItem">
<xsl:variable name="TempNo" select="NUMBER"/>
<xsl:element name="Number">
<xsl:value-of select="NUMBER"/>
</xsl:element>
<xsl:element name="Country">
<xsl:value-of select="CTRY"/>
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="NAME"/>
</xsl:element>
<xsl:element name="DataChanges">
<xsl:for-each select="./RS/ArrayOfRSItem/*" >
<xsl:if test="name()!='DATE_1' and name()!='DATE_2' ">
<xsl:variable name="ChangedNodeName" select="name()"></xsl:variable>
<xsl:for-each select="/ROOT/Items/Item">
<xsl:variable name="DisplayNodeName" select="FieldTag"></xsl:variable>
<xsl:if test="$DisplayNodeName = $ChangedNodeName">
<xsl:element name="FieldItem">
<xsl:attribute name="Tag">
<xsl:value-of select="FieldTag"/>
</xsl:attribute>
<xsl:variable name="NodeFullText" select="concat('../../getResponse/MAIN/ArrayItem[NUMBER=',$TempNo,']/RS/ArrayOfRSItem/',$DisplayNodeName)"/>
<xsl:attribute name="Value">
<xsl:value-of select="saxon:evaluate($NodeFullText)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each>
</Data>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
to get the desired output in the form of
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
But I am getting the output xml as
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
</DataChanges>
</NodeItem>
</Data>
I need to get the latest AMOUNT entry and CAT_TYPE. Any help or guidance would be much appreciated. Thanks in advance.
Couldn't this be (much) simpler?
XSLT 1.0 (or 2.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:template match="/">
<Data>
<xsl:apply-templates select="ROOT/getResponse/MAIN/ArrayItem"/>
</Data>
</xsl:template>
<xsl:template match="ArrayItem">
<NodeItem>
<Number><xsl:value-of select="NUMBER"/></Number>
<Country><xsl:value-of select="CTRY"/></Country>
<Name><xsl:value-of select="NAME"/></Name>
<DataChanges>
<xsl:apply-templates select="RS/ArrayOfRSItem/AMOUNT">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
<xsl:apply-templates select="RS/ArrayOfRSItem/CAT_TYPE">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
</DataChanges>
</NodeItem>
</xsl:template>
<xsl:template match="AMOUNT | CAT_TYPE">
<xsl:if test="position() = 1">
<FieldItem Tag="{local-name()}" Value="{.}"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Result, when applied to your example input:
<?xml version="1.0" encoding="utf-8"?>
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
Input:
<persons>
<person name="John" role="Writer"/>
<person name="John" role="Poet"/>
<person name="Jacob" role="Writer"/>
<person name="Jacob" role="Poet"/>
<person name="Joe" role="Poet"/>
</persons>
Expected Output:
<groups>
<group roles="Wriet, Poet" persons="John, Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
As in the above example, I first need to group on person names and find everyone's roles. If more than one person is found to have the same set of roles (e.g. both John and Jacob are both Writer and Poet), then I need to group on each set of roles and list the person names.
I can do this for the first level of grouping using Muenchian method or EXSLT set:distinct etc.
<groups>
<group roles="Wriet, Poet" persons="John"/>
<group roles="Wriet, Poet" persons="Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
The above was transformed using XSLT 1.0 and EXSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sets="http://exslt.org/sets" extension-element-prefixes="sets">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="persons-by-name" match="person" use="#name"/>
<xsl:template match="persons">
<groups>
<xsl:for-each select="sets:distinct(person/#name)">
<group>
<xsl:attribute name="persons"><xsl:value-of select="."/></xsl:attribute>
<xsl:attribute name="roles">
<xsl:for-each select="key('persons-by-name', .)">
<xsl:value-of select="#role"/>
<xsl:if test="position()!=last()"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
However, I need help to understand how to group on the grouped roles.
If XSLT 1.0 solution is not available, please feel free to recommend XSLT 2.0 approach.
Try it this way?
XSLT 1.0
(using EXSLT node-set() and distinct() functions)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="exsl set">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:key name="person-by-name" match="person" use="#name" />
<xsl:key name="person-by-roles" match="person" use="#roles" />
<xsl:variable name="distinct-persons">
<xsl:for-each select="set:distinct(/persons/person/#name)">
<person name="{.}">
<xsl:attribute name="roles">
<xsl:for-each select="key('person-by-name', .)/#role">
<xsl:sort/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</person>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<groups>
<xsl:for-each select="set:distinct(exsl:node-set($distinct-persons)/person/#roles)">
<group roles="{.}">
<xsl:attribute name="names">
<xsl:for-each select="key('person-by-roles', .)/#name">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Poet, Writer" names="John, Jacob"/>
<group roles="Poet" names="Joe"/>
</groups>
I did exactly the same thing as you already did and then went a step further and grouped again. Now I get the following output with your input:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Writer,Poet" persons="John,Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
This is the XSLT 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns="" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:avintis="http://www.avintis.com/esb" exclude-result-prefixes="#all" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/persons">
<groups>
<xsl:variable name="persons" select="."/>
<!-- create a temporary variable containing all roles of a person -->
<xsl:variable name="roles">
<xsl:for-each select="distinct-values(person/#name)">
<xsl:sort select="."/>
<xsl:variable name="name" select="."/>
<xsl:element name="group">
<xsl:attribute name="roles">
<!-- sort the roles of each person -->
<xsl:variable name="rolesSorted">
<xsl:for-each select="$persons/person[#name=$name]">
<xsl:sort select="#role"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join($rolesSorted/person/#role,',')"/>
</xsl:attribute>
<xsl:attribute name="persons" select="."/>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<!-- now loop again over all roles of the persons and group persons having the same roles -->
<xsl:for-each select="distinct-values($roles/group/#roles)">
<xsl:element name="group">
<xsl:variable name="name" select="."/>
<xsl:attribute name="roles" select="$name"/>
<xsl:attribute name="persons">
<xsl:value-of select="string-join($roles/group[#roles=$name]/#persons,',')"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</groups>
</xsl:template>
<xsl:template match="*|text()|#*">
<xsl:copy>
<xsl:apply-templates select="*|text()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The roles get also sorted - so independent from the input order of the roles and persons.
Excuse my ignorance. I am just beginning XSL and XML transformations.
I receive xml data from a vendor.
I only need to include certain "ids" in my transformation.
I also need to add a "display name" based on the ID to the final output.
I would be able to manual add the ID and Display names necessary into the XSL.
XML ex.
<root>
<DATA>
<ID>rd_bl</ID>
<travel>15</travel<
<delay>7</delay>
</DATA>
<DATA>
<ID>yl_gr</ID>
<travel>18</travel<
<delay>9</delay>
</DATA>
<DATA>
<ID>pu_gr</ID>
<travel>17</travel<
<delay>6</delay>
</DATA>
</root>
I would like to write a list of IDs and "display names" in the xsl - only the records with the listed IDs would be included.
ID - Display Name
rd_bl - Red to Blue
pu_gr - Purple to Green
In this example the data from yl_gr would be ignored and not show up in the transformation.
Any help is greatly appreciated.
Thanks!
Here’s a simple stylesheet that checks whether an ID is within an approved list of IDs and uses a “display name” for it in the output.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="desired-ids">
<id name="rd_bl">Red to Blue</id>
<id name="pu_gr">Purple to Green</id>
</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:if test="$desired-ids/id[#name=$current-id]">
<entry>
<name>
<xsl:value-of select="$desired-ids/id[#name=$current-id]" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output using your example XML after correcting the closing tag errors:
<root>
<entry>
<name>Red to Blue</name>
<travel>15</travel>
<delay>7</delay>
</entry>
<entry>
<name>Purple to Green</name>
<travel>17</travel>
<delay>6</delay>
</entry>
</root>
EDIT: in case you’re stuck with XSL 1.0:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="desired-ids">rd_bl="Red to Blue" pu_gr="Purple to Green"</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:variable name="id-with-equals" select="concat($current-id, '=')" />
<xsl:if test="contains($desired-ids, $id-with-equals)">
<xsl:variable name="id-with-open-quote" select="concat($id-with-equals, '"')" />
<xsl:variable name="display-name" select="substring-before(substring-after($desired-ids, $id-with-open-quote), '"')" />
<entry>
<name>
<xsl:value-of select="$display-name" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can see this is much less elegant, it uses awkward string-matching to check for a valid ID and extract the display name.
Will this stylesheet help?
<?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:template match="/">
<table>
<thead>
<th>ID</th>
<th>Display Name</th>
</thead>
<tbody>
<xsl:apply-templates select="root/DATA"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="DATA">
<xsl:choose>
<xsl:when test="ID='rd_bl'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Red to Blue</td>
</tr>
</xsl:when>
<xsl:when test="ID='pu_gr'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Purple to Green</td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>