XSLT Sort by Date Not Working - xslt

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

Related

Grouping 3 elements into a node

I have the following xml where a the xml element name have a number in it..
<Root>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>221</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>222</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>321</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>322</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
<Row>
<Coverage>Full</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>421</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>422</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
</Root>
I would like to group Sequence, Qualifier and Date into a group node called Benefit like below. Also, Based on the first element "Coverage" value, should be merged. XML output should as below.
<Root>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Benefits>
<Benefit>
<Sequence>1</Sequence>
<Qualifier>221</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>2</Sequence>
<Qualifier>222<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
<Benefit>
<Sequence>3</Sequence>
<Qualifier>321</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>4</Sequence>
<Qualifier>322<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
</Benefits>
</Row>
<Row>
<Coverage>Full</Coverage>
<Admission>Self</Admission>
<Benefits>
<Benefit>
<Sequence>1</Sequence>
<Qualifier>421</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>2</Sequence>
<Qualifier>422<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
</Benefits>
</Row>
</Root>
Any help is greatly appreciated.
Here's a simple method, based on the assumption that the 3 elements to be grouped will always come in a contiguous block that starts with a SequenceX:
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:template match="/Row">
<xsl:copy>
<xsl:copy-of select="Coverage | Admission"/>
<Benefits>
<xsl:for-each select="*[starts-with(name(), 'Sequence')]">
<Benefit>
<xsl:for-each select=". | following-sibling::*[position() < 3]">
<xsl:element name="{translate(name(), '0123456789', '')}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</Benefit>
</xsl:for-each>
</Benefits>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If the above assumption is not true, you can perform actual grouping based on the number included in the element names:
XSLT 2.0
<xsl:stylesheet version="2.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="/Row">
<xsl:copy>
<xsl:copy-of select="Coverage | Admission"/>
<Benefits>
<xsl:for-each-group select="*[matches(name(), '^Sequence|^Qualifier|^Date')]" group-by="replace(name(), '\D', '')">
<Benefit>
<xsl:for-each select="current-group()">
<xsl:element name="{replace(name(), '\d', '')}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</Benefit>
</xsl:for-each-group>
</Benefits>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT element selection based on Choose not working

I have the following XML:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Matches>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Matches>
</Row>
</Document>
I want to use XSLT to return the nested /Matches/Row/Period when the Document/Row/Period is undefined (as it is in the second Row of the XML)
So I have the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:variable name="period" select="/Period" />
<xsl:choose>
<xsl:when test="$period = null">
<xsl:copy>
<xsl:copy-of select="KEY | /Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
But it returns the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
</Row>
</Document>
(Note how it is not returning the nested /Matches/Row/Period in the second /Row.
I expect to get the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Document>
What am I doing wrong?
undefined or null are not checked in XSLT/XPath using expression = null, you would rather use (for node-sets) <xsl:when test="expression"> e.g. <xsl:when test="Period"> that there is at least one Period child element for the context node or test="not(Period)" to check there is no Period child.
In the end I would suggest to use template matching based on the identity transformation template and put any conditions into match pattern (predicates), but that is a different issue.
Got it working with this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:choose>
<xsl:when test="not(Period)">
<xsl:copy>
<xsl:copy-of select="KEY | Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
Thanks to #MartinHonnen's guidance.
I believe it could be simply:
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:template match="/Document">
<xsl:copy>
<xsl:for-each select="Row">
<xsl:copy>
<xsl:copy-of select="KEY | descendant::Period[1]" />
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT Apply Templates to derive specific parent & children records, children can be parent too

I need help with my XSLT. Here's my XML structure.
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
<where_used>
<component>subfield_2</component>
<type>Field</type>
</where_used>
<where_used>
<component>report_1</component>
<type>Report</type>
</where_used>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
<where_used>
<component>report_2</component>
<type>report</type>
</where_used>
</row>
<row>
<component>mainfield_3</component>
<type>Field</type>
</row>
</root>
I would like it to be transformed into the following:
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
</row>
<row>
<component>report_1</component>
<type>Report</type>
</row>
<row>
<component>report_2</component>
<type>report</type>
</row>
</root>
Basically, I am trying to get all the distinct dependencies of component mainfield_1. Here's my sample code but it is not enough to find any matching parent that has the same component name as the children.
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
</xsl:template>
If I run the above, I will not be able to get this.
<row>
<component>report_2</component>
<type>report</type>
</row>
Please help.
Consider using a key to look up the row items by component
<xsl:key name="rows" match="row" use="component" />
Then you can have a template for where_used nodes that refer to a separate row, allowing you to select that row instead
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="rows" match="row" use="component" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="* except where_used" />
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<xsl:apply-templates />
</row>
</xsl:template>
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
</xsl:stylesheet>
Note I have used the identity template too, to avoid having to explicitly copy existing nodes that don't need to be changed.

XSLT How to apply recursion to transform from a flat file to a nested tree

I know that there are several other question/answers similar to this, but I haven't read one that addresses my confusion over this transform. I need to move an XML document from this format:
<root>
<row>
<t0>1</t0>
<title>Main Title</title>
</row>
<row>
<t0>2</t0>
<title>Secondary Title</title>
<note>Note</note>
</row>
<row>
<t0>3</t0>
<title>Tertiary Title</title>
</row>
<row>
<t0>3</t0>
<title>Another Title</title>
</row>
<row>
<t0>2</t0>
<title>A Second Secondary Title</title>
<note>Note</note>
</row>
<row>
<t0>3</t0>
<title>Third Level Title</title>
</row>
<row>
<t0>3</t0>
<title>Title at Level Three</title>
</row>
</root>
to this format:
<root>
<header>List</header>
<t01>
<title>Main Title</title>
<t02>
<title>Secondary Title</title>
<note>Note</note>
<t03>
<title>Tertiary Title</title>
</t03>
<t03>
<title>Another Title</title>
</t03>
</t02>
<t02>
<title>A Second Secondary Title</title>
<note>Note</note>
<t03>
<title>Third Level Title</title>
</t03>
<t03>
<title>Title at Level Three</title>
</t03>
</t02>
</t01>
</root>
I'm using XSLT 2.0 and I'm getting hung up on applying for-each-group recursively. Thanks for your time & trouble.
With XSLT 2.0 I would strongly suggest to use the for-each-grouping together with a recursive function or template; below is a sample using a function:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="element()*">
<xsl:param name="elements" as="element(row)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="row[t0 = $level]">
<xsl:element name="t{format-number($level, '00')}">
<xsl:copy-of select="* except t0"/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<header>List</header>
<xsl:sequence select="mf:group(row, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Please give this a try:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root">
<root>
<header>List</header>
<xsl:apply-templates select="row[t0 = 1]" />
</root>
</xsl:template>
<xsl:template match="row">
<xsl:variable name="level" select="t0" />
<xsl:element name="{concat('t0', $level)}">
<xsl:apply-templates select="title | note" />
<xsl:variable name="nextPeer"
select="following-sibling::row[t0 <= $level]" />
<xsl:variable name="children"
select="following-sibling::row[t0 = $level + 1][not($nextPeer)
or (count(preceding-sibling::row) < count($nextPeer[1]/preceding-sibling::row))]" />
<xsl:apply-templates select="$children" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

limit records display using xslt

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