'XSLT sorting (by attribute value) _then_ grouping (by record count)
Seeking assistance to sort then group records using XSLT.
In the example shown here, the OP groups by row count, to create files with a set number of records inside them, however the records are not sorted. XSLT: Split an xml file by number of records and split file if element text differs from the last
Can anyone advise if it is possible to first sort, then group by a xsl:param element? I've seen example which first group, then sort. The inverse logic however does not appear to be documented anywhere.
<root>
<record color="Red">
<PieFlavor>Pie</PieFlavor>
</record>
<record color="Green">
<PieFlavor>Cherry</PieFlavor>
</record>
<record color="Yellow">
<PieFlavor>Cherry</PieFlavor>
</record>
<record color="Red">
<PieFlavor>Orange</PieFlavor>
</record>
<record color="Red">
<PieFlavor>Apple</PieFlavor>
</record>
<record color="Green">
<PieFlavor>Orange</PieFlavor>
</record>
</root>
When first sorting by "/root/record/@color", then grouping (into separate files) with a recordNum of 2, the resulting output should be:
<root>
<record color="Green">
<PieFlavor>Cherry</PieFlavor>
</record>
<record color="Green">
<PieFlavor>Orange</PieFlavor>
</record>
</root>
<root>
<record color="Red">
<PieFlavor>Pie</PieFlavor>
</record>
<record color="Red">
<PieFlavor>Orange</PieFlavor>
</record>
</root>
<root>
<record color="Red">
<PieFlavor>Apple</PieFlavor>
</record>
</root>
<root>
<record color="Yellow">
<PieFlavor>Cherry</PieFlavor>
</record>
</root>
For completeness, here's the XSLT, which does create files with only 2 records in them, but does not support sorting.
<?xml version="1.0" encoding="UTF-8"?>
<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:param name="recordNum" select="2"/>
<xsl:template match="/">
<xsl:for-each-group select="root/record" group-adjacent="(position()-1) idiv $recordNum">
<xsl:result-document href="file:///{encode-for-uri('{WATCHTEMPFOLDER}')}{format-number(position(),'000000000')}.xml">
<root>
<xsl:apply-templates select="current-group()"/>
</root>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Solution 1:[1]
Are you using an XSLT 2 processor? The current version (for 5 years now) is XSLT 3 and that has (from XPath 3.0) a sort function so the easiest would be doing for-each-group select="sort(root/record, (), function($r) { $r/@color })" group-adjacent="...". If you are stuck with XSLT 2 then use xsl:perform-sort, either in a custom function you call then the same as show instead of the sort directly in the select of the for-each-group
<xsl:stylesheet ... xmlns:mf="http://example.com/mf" ...>
<xsl:function name="mf:sort-records" as="element(record)*">
<xsl:param name="records" as="element(record)*"/>
<xsl:perform-sort select="$records">
<xsl:sort select="@color"/>
</xsl:perform-sort>
</xsl:function>
...
<xsl:for-each-group select="mf:sort-records(root/record)" group-adjacent="...">
or store the result in a variable and pass the variable to for-each-group:
<xsl:variable name="sorted-records" as="element(record)*">
<xsl:perform-sort select="root/record">
<xsl:sort select="@color"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:for-each-group select="$sorted-records" group-adjacent="...">
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
