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结果。

这是 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>
4

1 回答 1

6

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

  • "Integer-XML" 总是显示为一个四舍五入的数字,没有小数位
  • "Decimal-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加起来是一个整数时,这个“失败”(截断小数) 。

没有吸引力的替代方法是使用 Becker 方法(以HU Berlin的Oliver Becker命名):

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我猜也是。

于 2009-03-22T10:59:57.567 回答