XSLT基于输入XML的十进制格式

时间:2009-03-21 13:19:30

标签: xml xslt formatting decimal xslt-1.0

我的情况是我的XSLT文件应该有条件地显示价格和小数,具体取决于输入XML是否包含小数。所以,我可以接收带有两种类型值的XML文件 - XML将包含格式化为小数的所有价格,最多两个地方(我称之为“十进制 - XML”),或者价格将四舍五入到最接近的整数(我称之为一个“整数XML”)。

我的问题是我需要在XSLT文件中尽可能少地重构,然后允许它们以与XML输入相同的格式将转换应用于XHTML。为了实现这一目标,我实施并向我的团队提出了三条指导原则:

  1. 在为计算计算值或存储在变量中时,删除所有format-number()函数调用。请改用number(<value>)。但是,某些条件适用于此规则(见下文)。
  2. 要显示该值时,请使用format-number(<value>, '#.##')格式。这应该确保整数或十进制值将显示为最初存在于XML中。
  3. 对于可选标签(例如“折扣”),即使仅计算值,也要使用format-number(<value>, '0.00')功能。这是必要的,因为如果标签不存在,尝试获取值将得到NaN结果。
  4. 以下是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评论所说,它适用于大多数情况但不是全部。

    我想使用单个表达式来格式化与输入相同的所有价格,而不是在任何地方应用“when-otherwise”构造,这些构造还需要传递给XSLT的布尔参数来确定显示格式。 XSLT的当前状态是使用format-number(<expression>, '0')无论输入如何都将舍入所有数字。

    我如何做到这一点?


    编辑:在Dimitre的评论之后,我决定创建一个示例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 个答案:

答案 0 :(得分:6)

如果我理解正确,你想要:

  • “Integer-XML”始终显示为舍入数字,无小数位
  • “Decimal-XML”始终显示两位小数
  • 不使用执行格式化的模板,而是使用XPath one-liner

你最接近你想要的是这个表达式:

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

由于在XPath 1.0(XPath 2.0 has if/then/else)中没有易于使用的条件表达式,因此会有一个妥协:

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

然而,当$cost$extraCharges$discount加起来为圆形时,这会“失败”(截止小数点)。

没有吸引力的替代方法是使用Becker的方法(以Oliver BeckerHU 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也是正确的。