Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT Compare Numbers as Strings

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() &lt; 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()) &lt; 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()) &lt; '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>
like image 836
JohnLBevan Avatar asked Jan 12 '16 14:01

JohnLBevan


People also ask

What is the syntax of XSLT format-number?

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

How to compare two strings in XPath?

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

What's new in XSLT 3?

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.

What is the difference between positive and negative characters in XSLT?

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?


2 Answers

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(.) &lt; 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[. &lt; '7']">
          <xsl:copy-of select="."></xsl:copy-of>
        </xsl:for-each>
      </AsString>
    </element>
 </xsl:template>
</xsl:stylesheet>
like image 110
Mads Hansen Avatar answered Oct 28 '22 16:10

Mads Hansen


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) &lt; '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>
like image 32
Martin Vitek Avatar answered Oct 28 '22 17:10

Martin Vitek