Search code examples
xmlsortingxsltxmlstarlet

How to sort XML nodes by ID values using XSLT?


I have a large xml file with a lot of row nodes. I want to sort it by each row's id value.

So this would be an example input:

<database>
  <table>
    <row>
      <id>10</id>
      <foo>bar</foo>
    </row>
    <row>
      <id>5</id>
      <foo>poit</foo>
    </row>
    <row>
      <id>1</id>
      <foo>narf</foo>
    </row>
  </table>
</database>

and this the expected output:

<database>
  <table>
    <row>
      <id>1</id>
      <foo>narf</foo>
    </row>
    <row>
      <id>5</id>
      <foo>poit</foo>
    </row>
    <row>
      <id>10</id>
      <foo>bar</foo>
    </row>
  </table>
</database>

How can I achieve that? I have xmlstarlet at my disposal. It features a transform and appearantly I can provide a xslt stylecheet in a xsl file.

I haven't worked with xslt before and am unsure how to proceed.

I have found some related sorting questions providing some XSLT examples, yet I could not get them working in my use case.

My current sort.xslt (Note: I don't know what I am doing) looks like:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="@*">
    <xsl:sort select="row()"/>
   </xsl:apply-templates>
   <xsl:apply-templates select="node()">
    <xsl:sort select="id()"/>
   </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

Yet it fails:

$ xmlstarlet tr sort.xsl example.xml 
Invalid number of arguments
xmlXPathCompiledEval: evaluation failed
Invalid number of arguments
xmlXPathCompiledEval: evaluation failed
Invalid number of arguments
xmlXPathCompiledEval: evaluation failed
<database>
  <table/>
</database>

Solution

  • I don't know anything about xmlstarlet, but I can say that your XSLT should really look like this...

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output method="xml"  indent="yes" />
    
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="table">
            <xsl:copy>
                <xsl:apply-templates select="row">
                    <xsl:sort select="id" data-type="number" />
                </xsl:apply-templates>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>
    

    Note how you do not need () after element names in your XSLT.

    See it in action at http://xsltransform.net/pNmBy1b

    (I also note the tag xmlstarlet only has 20 followers. You might want to try out some other tools instead. See https://stackoverflow.com/tags/xslt/info for some help. xsltproc, perhaps?)