XSLT Десятичное форматирование на основе входного XML - PullRequest
0 голосов
/ 21 марта 2009

У меня есть ситуация, когда мои XSLT-файлы должны отображать цены вместе с десятичными знаками условно, в зависимости от того, содержит ли входной XML десятичные знаки или нет. Таким образом, я могу получить XML-файлы с двумя типами значений - XML ​​будет содержать все цены, отформатированные с десятичными знаками до двух мест (я называю это «Десятичным-XML»), или цены будут округлены до ближайшего целого числа (я называю это один "Integer-XML").

Моя проблема заключается в том, что мне нужно как можно меньше реорганизовать файлы XSLT и разрешить им применять преобразование к XHTML в том же формате, что и входные данные XML. Чтобы достичь этого, я реализовал и предложил моей команде три руководства:

  1. Удалить все вызовы функции format-number(), когда значение вычисляется для расчетов или сохраняется в переменной. Вместо этого используйте number(<value>). Однако к этому правилу применяются определенные условия (см. Ниже).
  2. Когда необходимо отобразить значение, используйте формат format-number(<value>, '#.##'). Это должно гарантировать, что целые или десятичные значения будут отображаться как изначально присутствующие в XML.
  3. Для необязательных тегов (таких как «Скидка») используйте функцию format-number(<value>, '0.00'), даже если значение только вычисляется. Это необходимо, потому что, если тег отсутствует, попытка получить значение даст результат NaN.

Вот иллюстративный пример XSLT:

  <x:stylesheet version="1.0" xmlns:x="http://www.w3.org/1999/XSL/Transform">
  <x:template match="/">
    <html>
      <body>
        <table border="1" width="60%">
          <tr>
            <th>Simple</th>
            <th>number()</th>
            <th>format-number(&lt;expression&gt;, '0.00')</th>
            <th>format-number(&lt;expression&gt;, '#.##')</th>
          </tr>
          <x:apply-templates />
        </table>
      </body>
    </html>
  </x:template>

  <x:template match="Item">
    <x:variable name="qty" select="number(@numItems)" />
    <x:variable name="cost" select="number(ItemCost) * $qty" />
    <x:variable name="extraCharges" select="(number(Tax) + number(TxnFee)) * $qty"/>
    <x:variable name="discount" select="format-number(Discount, '0.00') * $qty"/>
    <tr>
      <td>
      <!-- Works for Integer-XML, but values in Decimal-XML are
      *sometimes* rendered upto 14 decimal places. Even though Quickwatch
      shows it correctly in the debugger in VS. I cannot figure out what's
      special about the error cases. -->
        <x:value-of select="$cost + $extraCharges - $discount"/>
      </td>
      <td>
        <!-- Works same as the above case. -->
        <x:value-of select="number($cost + $extraCharges - $discount)"/>
      </td>
      <td>
      <!-- Works for Decimal-XML, but values in Integer-XML are always
      rendered with decimal digits. -->
        <x:value-of select="format-number(($cost + $extraCharges - $discount), '0.00')"/>
      </td>
      <td>
      <!-- Works for Integer-XML, but some values in Decimal-XML are
      rendered incorrectly. For example, 95.20 is rendered as 95.2;
      95.00 is rendered as 95 -->
        <x:value-of select="format-number(($cost + $extraCharges - $discount), '#.##')"/>
      </td>
    </tr>
  </x:template>
</x:stylesheet>

Как отмечают комментарии HTML, он работает в большинстве случаев, но не во всех.

Я хотел бы использовать одно выражение для форматирования всех цен, аналогичных входным, вместо того, чтобы везде применять конструкции «когда-либо», что дополнительно требовало бы логического параметра, передаваемого в XSLT, для определения формата отображения. Текущее состояние XSLT состоит в том, что все числа округляются независимо от ввода, используя format-number(<expression>, '0').

Как мне это сделать?


Редактировать: После комментария Димитра я решил создать образец XML (и XSLT выше), чтобы эксперты могли легко его опробовать.

Пример XML (содержит десятичные дроби):

<ShoppingList>
  <Item numItems="2">
    <ItemCost>10.99</ItemCost>
    <Tax>3.99</Tax>
    <TxnFee>2.99</TxnFee>
    <Discount>2.99</Discount>
  </Item>
  <Item numItems="4">
    <ItemCost>15.50</ItemCost>
    <Tax>5.50</Tax>
    <TxnFee>3.50</TxnFee>
    <Discount>3.50</Discount>
  </Item>
</ShoppingList>

1 Ответ

6 голосов
/ 22 марта 2009

Если я вас правильно понимаю, вы хотите:

  • "Integer-XML" всегда отображается как округленное число, без десятичных разрядов
  • «Десятичный XML» всегда отображается с двумя десятичными знаками
  • не использовать шаблон, который выполняет форматирование, а однострочник XPath

Самое близкое к тому, что вы хотите, это выражение:

<xsl:value-of select="
  format-number($cost + $extraCharges - $discount, '0.00')
" />

Поскольку в XPath 1.0 нет простого в использовании условного выражения ( XPath 2.0 имеет if/then/else), компромисс будет следующим:

<xsl:value-of select="
  substring-before(
    concat(format-number($cost + $extraCharges - $discount, '0.00'), '.00')
    , '.00'
  )
" />

Однако это «не получается» (обрезание десятичных дробей), когда $cost, $extraCharges и $discount складываются в круглое число.

Непривлекательной альтернативой является использование метода Беккера (названного в честь Оливер Беккер из HU Berlin ):

concat(
  substring($s1, 1, number($condition)      * string-length($s1)),
  substring($s2, 1, number(not($condition)) * string-length($s2))
)

Технически это однострочник. Две sub-string() части являются взаимоисключающими на основе $condition, поэтому concat() будет возвращать только одно значение или другое.

Практически (особенно для вашего случая) это будет неуправляемый беспорядок, медленный и полностью скрывающий намерение:

concat( 
  substring(
    format-number($cost + $extraCharges - $discount, '0.00')
    , 1
    , number(
      contains($cost, '.')
    ) * string-length(format-number($cost + $extraCharges - $discount, '0.00'))
  ),
  substring(
    format-number($cost + $extraCharges - $discount, '#.##')
    , 1
    , number(
      not(contains($cost, '.')) 
    ) * string-length(format-number($cost + $extraCharges - $discount, '#.##'))
  )
)

Поскольку вы указали, что либо все входные значения содержат десятичные дроби, либо ни одного из них, вышеприведенное выражение только проверяет, содержит ли $cost десятичную точку. Думаю, было бы правильно проверить $extraCharges и $discount.

...