0
votes

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>

1
Yes, it's possible. Where exactly are you stuck with this?michael.hor257k
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).Martin Honnen
I've used the xsl updated in the question but is not working.James Taylor
"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".michael.hor257k
It keeps 2 lines but the field1=AAA instead of BBB.James Taylor

1 Answers

1
votes

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.