Note: the OP has now provided sample XML. The below theories can be trivially adapted to this XML.
I. XSLT 1.0 (part 1)
Here is a simple solution that assumes your assertion ("the codes have four characters and four numerals") will always be the case:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="vNums" select="'1234567890'" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<t>
<xsl:apply-templates>
<xsl:sort select="substring(., 5)"
data-type="number" />
</xsl:apply-templates>
</t>
</xsl:template>
</xsl:stylesheet>
...is applied to an imagined XML document, shuffled into random order:
<?xml version="1.0" encoding="utf-8"?>
<t>
<i>COMP3410</i>
<i>LAWS2202</i>
<i>COMP2400</i>
<i>COMP2100</i>
<i>LAWS2250</i>
</t>
...the correct result is produced:
<?xml version="1.0" encoding="utf-8"?>
<t>
<i>COMP2100</i>
<i>LAWS2202</i>
<i>LAWS2250</i>
<i>COMP2400</i>
<i>COMP3410</i>
</t>
Explanation:
- The
Identity Transform
-- one of the (if not the) most fundamental design patterns in XSLT -- copies all nodes from the source XML document to the result XML document as-is.
- One template overrides the Identity Transform by sorting all children of
<t>
based upon the characters in the string from position 5 to the string's end.
Again, note that this solution assumes your original assertion -- "the codes have four characters and four numerals" -- is (and always will be) true.
II. XSLT 1.0 (part 2)
A (potentially) safer solution would be to assume that there might be numerous non-numeric characters in various positions within the <i>
nodes. In that case, this XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="vNums" select="'1234567890'" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<t>
<xsl:apply-templates>
<xsl:sort select="translate(., translate(., $vNums, ''), '')"
data-type="number" />
</xsl:apply-templates>
</t>
</xsl:template>
</xsl:stylesheet>
...provides the same result:
<?xml version="1.0" encoding="utf-8"?>
<t>
<i>COMP2100</i>
<i>LAWS2202</i>
<i>LAWS2250</i>
<i>COMP2400</i>
<i>COMP3410</i>
</t>
Explanation:
- The
Identity Transform
is once again used.
- In this case, the additional template uses the so-called
Double Translate Method
(first proposed by Michael Kay and first shown to me by Dimitre Novatchev) to remove all non-numeric characters from the value of each <i>
element before sorting.
III. XSLT 2.0 Solution
Here's a possible XSLT 2.0 solution is very similar to part 2 of the XSLT 1.0 solution; it merely replaces the Double Translate Method with XPath 2.0's ability to handle regular expressions:
<xsl:sort select="replace(., '[^\d]', '')" data-type="number" />
Note that by no means are you required to use regular expressions in XPath 2.0; the Double Translate Method works just as well as in XPath 1.0. The replace()
method will, however, most likely be more efficient.