XSLT сравнивает числа как строки

Фон

Недавно я с удивлением заметил, что XSL был в состоянии разумно обрабатывать числа; т.е. умение обращаться с числами в тексте как с числовыми при выполнении сравнений (т.е. 7 < 10 вместо того, чтобы думать '10' < '7'). В моем случае это то, что я хотел; только не то, что я ожидал.

Из любопытства я попытался заставить XSLT сравнивать числа в виде строк (т. Е. Используя string() функционировать, но без удачи.

Вопрос

Можно ли заставить XSLT сравнивать числа в виде строк; например, так '10' < '7'?

пример

Исходный 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>

Ожидаемый результат:

<?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>

Фактический выход:

<?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>

3 ответа

Решение

Похоже, что в XSLT/XPATH 1.0string() значение по-прежнему оценивается как число при выполнении сравнения.

https://www.w3.org/TR/xpath/

Когда ни один объект для сравнения не является набором узлов и оператор <=, <,>= или>, тогда объекты сравниваются путем преобразования обоих объектов в числа и сравнения чисел в соответствии с IEEE 754. Сравнение<будет истина тогда и только тогда, когда первое число меньше второго числа.

С XSLT / XPATH 2.0 (и 3.0, и 3.1) вы можете явно установить тип данных как xs:string чтобы гарантировать, что сравнение выполняется со строками и не приведено к числовым значениям.

<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>

Но достаточно сравнить значение со строкой '7' (Кроме того, вы могли бы устранить <xsl:if> и поместите свой фильтр в предикат)

<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>

Если вы выбираете первое число, то обходным путем может быть просто подстановка первой позиции.

<xsl:if test="substring(./text(), 1, 1) &lt; '7'">

возвращается

<AsString>
  <x>1</x>
  <x>2</x>
  <x>3</x>
  <x>4</x>
  <x>5</x>
  <x>6</x>
  <x>10</x>
</AsString>

Обратите внимание, что в XSLT 1.0 оба 'a' > 'b' а также 'b' > 'a' оценивать как false,

Другие вопросы по тегам