xmlsortingxslt

Sort xml segment by certain field if present


I need an xls version 1 to sort ascending some segments of my document if they contain a value.

<root><!-- some content to not touch --><segment><!-- segment not containing the "field" by which to sort --></segment><segment><field>1</field><!-- some fields to not touch --></segment><segment><field>3</field><!-- some fields to not touch --></segment><segment><field>2</field><!-- some fields to not touch --></segment><segment><!-- segment not containing the "field" by which to sort --></segment></root>

Expected Output

<root><!-- some content to not touch --><segment><!-- segment not containing the "field" by which to sort --></segment><segment><field>1</field><!-- some fields to not touch --></segment><segment><field>2</field><!-- some fields to not touch --></segment><segment><field>3</field><!-- some fields to not touch --></segment><segment><!-- segment not containing the "field" by which to sort --></segment></root>

I'm trying with this xsl but it is not working:

<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" indent="yes"/><xsl:template match="/"><xsl:copy><xsl:apply-templates/></xsl:copy></xsl:template><xsl:template match="*"><xsl:choose><xsl:when test="segment"><xsl:copy><xsl:apply-templates select="segment"><xsl:sort select="@field" data-type="number" order="ascending"/></xsl:apply-templates></xsl:copy></xsl:when><xsl:otherwise><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:otherwise></xsl:choose></xsl:template><xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template></xsl:stylesheet>

Can anybody help? Thanks


Solution

  • Here is one way you could look at it:

    XSLT 1.0

    <xsl:stylesheet version="1.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:copy-of select="segment[field][1]/preceding-sibling::node()"/>
            <xsl:for-each select="segment[field]">
                <xsl:sort select="field"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
            <xsl:copy-of select="segment[field][last()]/following-sibling::node()"/>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    As noted in the comments, this assumes all the segment elements that have a field are in a single contiguous block, optionally preceded and/or followed by other nodes.


    Added:

    how to handle non-contiguous elements? In this case, sorting each block separately?
    i can use v.2.0

    You could do:

    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:template match="/root">
        <xsl:copy>
            <xsl:for-each-group select="segment" group-adjacent="boolean(field)">   
                <xsl:choose>
                    <xsl:when test="current-grouping-key()">
                        <xsl:perform-sort select="current-group()">
                            <xsl:sort select="field"/>
                        </xsl:perform-sort> 
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="current-group()"/> 
                    </xsl:otherwise>  
                </xsl:choose>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>