James Taylor
James Taylor

Reputation: 93

xslt to keep most recent entries grouped by key

I need to keep the most recent entries from an input xml grouping by a key value. Is it possible to do it with xsl 2.0? Input

<root>
<line>
    <date>2021-01-01T00:00:00</date>
    <field1>AAA</field1>
    <field2>1</field2>
</line>
<line>
    <date>2021-01-01T23:00:00</date>
    <field1>BBB</field1>
    <field2>1</field2>
</line>
<line>
    <date>2021-01-02T00:00:00</date>
    <field1>CCC</field1>
    <field2>2</field2>
</line>
Output should be
<root>
<line>
    <date>2021-01-01T23:00:00</date>
    <field1>BBB</field1>
    <field2>1</field2>
</line>
<line>
    <date>2021-01-02T00:00:00</date>
    <field1>CCC</field1>
    <field2>2</field2>
</line>
The xsl keeps the most recent entry grouping by field2, so the result is two entries (field2= 1 and field2=2).
<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
    <xsl:for-each-group select="root/line" group-by="field2">
        <xsl:sort select="date"/>
        <line>
            <xsl:copy-of select="node()"/>
        </line>
    </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Views: 561

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116959

Your attempt sorts the groups, not the lines within each group. It then copies the first node in each group.

To do this by sorting, you would need to do something like:

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:strip-space elements="*"/>

<xsl:template match="/root">
    <xsl:copy>
        <xsl:for-each-group select="line" group-by="field2">
            <xsl:variable name="sorted-group">
                <xsl:perform-sort select="current-group()">
                    <xsl:sort select="date" order="descending"/>
                </xsl:perform-sort>            
            </xsl:variable>
            <xsl:copy-of select="$sorted-group/line[1]"/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Note that this copies only the first line in descending date order. If there are two lines that have the same date, only the first one of these (in document order) will be copied to the output.


Alternatively you could select the lines that have the maximal dateTime within their group:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/root">
    <xsl:copy>
        <xsl:for-each-group select="line" group-by="field2">
            <xsl:variable name="max-dateTime" select="max(current-group()/date/xs:dateTime(.))" />
            <xsl:copy-of select="current-group()[date=$max-dateTime]"/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Here a tie would be resolved by copying both lines.

Upvotes: 1

Related Questions