Reputation: 3
I have a set of nodes that display compensation of workers along with effective date from two different systems (different nodes). I need to sort these values in ascending / descending order irrespective of whether they are from previous system or current system.
I tried to use the sort function in the for-each, but I am getting an error that not more than 1 node can be passed while sorting.
<?xml version="1.0" encoding="UTF-8"?>
<Workers xmlns:ch="test/Compensation_History">
<ch:Worker_Entry>
<ch:Employee_ID>12345</ch:Employee_ID>
<ch:Compensation_History_Previous_System>
<ch:Amount>100000</ch:Amount>
<ch:Effective_Date>2018-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
<ch:Compensation_History_Previous_System>
<ch:Amount>95000</ch:Amount>
<ch:Effective_Date>2017-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
<ch:Compensation_History_Previous_System>
<ch:Amount>90000</ch:Amount>
<ch:Effective_Date>2016-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
<ch:Compensation_History_Current_System>
<ch:Amount>105000</ch:Amount>
<ch:Effective_Date>2019-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Current_System>
<ch:Compensation_History_Current_System>
<ch:Amount>110000</ch:Amount>
<ch:Effective_Date>2020-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Current_System>
</ch:Worker_Entry>
</Workers>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<Workers xmlns:ch="test/Compensation_History">
<ch:Worker_Entry>
<ch:Employee_ID>12345</ch:Employee_ID>
<ch:Compensation_History_Current_System>
<ch:Amount>110000</ch:Amount>
<ch:Effective_Date>2020-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Current_System>
<ch:Compensation_History_Current_System>
<ch:Amount>105000</ch:Amount>
<ch:Effective_Date>2019-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Current_System>
<ch:Compensation_History_Previous_System>
<ch:Amount>100000</ch:Amount>
<ch:Effective_Date>2018-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
<ch:Compensation_History_Previous_System>
<ch:Amount>95000</ch:Amount>
<ch:Effective_Date>2017-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
<ch:Compensation_History_Previous_System>
<ch:Amount>90000</ch:Amount>
<ch:Effective_Date>2016-01-01-07:00</ch:Effective_Date>
</ch:Compensation_History_Previous_System>
</ch:Worker_Entry>
</Workers>
Below is the code that I tried:
<?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"
xmlns:ch="test/Compensation_History" exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="/">
<Workers>
<xsl:for-each-group select="ch:Workers" group-by="ch:Employee_ID">
<xsl:sort select="//ch:Effective_Date" order="descending" />
<xsl:copy-of select="." />
</xsl:for-each-group>
</Workers>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Views: 1285
Reputation: 101
If you really need an XSLT 2 solution then the following should work:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ch="test/Compensation_History" exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="Workers">
<xsl:copy>
<xsl:for-each select="ch:Worker_Entry">
<xsl:copy>
<xsl:copy-of select="ch:Employee_ID"/>
<xsl:for-each select="ch:Compensation_History_Previous_System | ch:Compensation_History_Current_System">
<xsl:sort select="xs:date(ch:Effective_Date)" order="descending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Reputation: 167571
In XSLT 3 using the sort
function you could use
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="test/Compensation_History"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:template match="Worker_Entry">
<xsl:copy>
<xsl:apply-templates
select="@*,
Employee_ID,
(* except Employee_ID)
=> sort((), function($c) { $c/Effective_Date => xs:date() } )
=> reverse()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
fn:sort
as a higher-order function needs Saxon 10 all editions or Saxon 9.8 and later PE or EE or Saxon-JS 2.
In the code you have added you are trying to group and sort although the <xsl:for-each-group select="ch:Workers" group-by="ch:Employee_ID">
doesn't make any sense as the root element is not in the ch
namespace and grouping a single element only makes sense if you provide a group-by expression resulting in several grouping keys for the element; your expression ch:Employee_ID
does not make sense in that context. As for the attempt to use xsl:sort
, use a relative expression there so e.g. <xsl:for-each-group select="//ch:Worker_Entry" group-by="ch:Employee_ID"><xsl:sort select="ch:Effective_Date" order="descending"/>
could make sense if you want to group and sort the groups.
Upvotes: 2