Background
I was recently surprised to notice that XSL was able to intelligently handle numbers; i.e. knowing to treat numbers in text as numeric when performing comparisons (i.e. it understood that 7 < 10
rather than thinking '10' < '7'
). In my case that's what I wanted; just not what I'd expected.
Out of curiosity I then tried to force XSLT to compare the numbers as strings (i.e. by using the string()
function, but with no luck.
Question
Is it possible to get XSLT to compare numbers as strings; e.g. so '10' < '7'
?
Example
Source XML:
<?xml version="1.0" encoding="utf-8"?>
<element>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
<x>7</x>
<x>8</x>
<x>9</x>
<x>10</x>
</element>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="element">
<element>
<AsItComes>
<xsl:for-each select="./x">
<xsl:if test="./text() < 7">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>
</AsItComes>
<AsNumber>
<xsl:for-each select="./x">
<xsl:if test="number(./text()) < 7">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>
</AsNumber>
<AsString>
<xsl:for-each select="./x">
<xsl:if test="string(./text()) < '7'">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>
</AsString>
</element>
</xsl:template>
</xsl:stylesheet>
Expected Output:
<?xml version="1.0" encoding="utf-8"?>
<element>
<AsItComes>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
<x>10</x>
</AsItComes>
<AsNumber>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
</AsNumber>
<AsString>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
<x>10</x>
</AsString>
</element>
Actual Output:
<?xml version="1.0" encoding="utf-8"?>
<element>
<AsItComes>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
</AsItComes>
<AsNumber>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
</AsNumber>
<AsString>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
</AsString>
</element>
XSLT format-number is defined to convert a number into a string value using the second argument’s pattern. This is the type conversion formatting output. We use a different notation for numbers like comma for decimal formats and some points for thousands representation. To use all these, XSL uses format-number. Syntax of XSLT format-number
In Xpath 1.0 strings can be compared only for equality (or inequality), using the operator =and the function not()together with the operator =. $str1 = $str2
In XSLT 3.0 some of the capabilities of the xsl:number instruction - specifically the formatting capabilities - are more conveniently packaged via the new format-integer () function, which is also available in XPath and XQuery. With large numbers, the digits may be split into groups.
The first pattern is for positive, and the second is for negative. It shows as a percentage character. Any characters can be added. Required for a quite specific. How format-number Function Works in XSLT?
It appears that in XSLT/XPATH 1.0, the string()
value is still evaluated as a number when performing the comparison.
https://www.w3.org/TR/xpath/#booleans
When neither object to be compared is a node-set and the operator is <=, <, >= or >, then the objects are compared by converting both objects to numbers and comparing the numbers according to IEEE 754. The < comparison will be true if and only if the first number is less than the second number.
With XSLT/XPATH 2.0 (and 3.0, and 3.1), you can explicitly set the data type as xs:string
to ensure that the comparison is performed against strings and not coerced into number values.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:template match="element">
<element>
<AsString>
<xsl:for-each select="./x">
<xsl:if test="xs:string(.) < xs:string('7')">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>
</AsString>
</element>
</xsl:template>
</xsl:stylesheet>
But it is sufficient to compare the value to the string '7'
(also, you could eliminate the <xsl:if>
and put your filter in a predicate):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="element">
<element>
<AsString>
<xsl:for-each select="./x[. < '7']">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:for-each>
</AsString>
</element>
</xsl:template>
</xsl:stylesheet>
if you are going for the first number a work-around could be to just substring the first position.
<xsl:if test="substring(./text(), 1, 1) < '7'">
returns
<AsString>
<x>1</x>
<x>2</x>
<x>3</x>
<x>4</x>
<x>5</x>
<x>6</x>
<x>10</x>
</AsString>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With