0

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>

5
  • Yes, it's possible. Where exactly are you stuck with this? Commented Jun 9, 2021 at 9:30
  • XSLT 2 and 3 grouping is shown in stackoverflow.com/tags/xslt-grouping/info, sorting is done by processing the current-group() and using xsl:sort or you can just select the item in the current-group() with the max(current-group()/date/xs:dateTime(.)) = xs:dateTime(date). Commented Jun 9, 2021 at 13:33
  • I've used the xsl updated in the question but is not working. Commented Jun 9, 2021 at 15:42
  • "not working" is not a good description of a problem. What is the actual result? And are you're your XSLT processor supports XSLT 2.0? Your stylesheet declares version="1.0". Commented Jun 9, 2021 at 15:49
  • It keeps 2 lines but the field1=AAA instead of BBB. Commented Jun 9, 2021 at 15:52

1 Answer 1

1

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.