XSLT 基于输入 XML 的十进制格式
我遇到的情况是,我的 XSLT 文件应有条件地显示价格和小数,具体取决于输入 XML 是否包含小数。 因此,我可以接收具有两种类型值的 XML 文件 - XML 将包含所有以小数形式格式化的价格,最多两位(我将其称为“Decimal-XML”),或者价格将四舍五入为最接近的整数(我将其称为“Decimal-XML”)一个“整数 XML”)。
我的问题是,我需要在 XSLT 文件中尽可能少地重构,但允许它们以与 XML 输入相同的格式将转换应用到 XHTML。 为了实现这一目标,我向我的团队实施并建议了三个准则:
- 在计算值进行计算或将值存储在变量中时,删除所有
format-number()
函数调用。 请改用number(
。 然而,某些条件适用于该规则(见下文)。) - 当要显示值时,请使用
format-number(
格式。 这应该确保整数或小数值将按照 XML 中最初的形式显示。, '#.##') - 对于可选标签(例如“Discount”),即使仅计算值,也请使用
format-number(
函数。 这是必要的,因为如果标签不存在,尝试获取值将给出, '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(<expression>, '0.00')</th>
<th>format-number(<expression>, '#.##')</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(
进行四舍五入。
我该如何实现这个目标?
编辑: 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>
I have a situation where my XSLT files should display prices alongwith decimals conditionally, depending on whether the input XML contains decimals or not. So, I can receive XML files with two types of values - XML will contain all prices formatted with decimals upto two places (I call this one "Decimal-XML") or the prices will be rounded off to the nearest integer (I call this one "Integer-XML").
My problem is that I need to refactor as little as possible in the XSLT files and yet allow them to apply the transformation to XHTML in the same format as the XML input. In order to accomplish this, I implemented and suggested three guidelines to my team:
- Remove all
format-number()
function calls when the value is being computed for calculations or stored in a variable. Usenumber(<value>)
instead. However, certain conditions apply to this rule (See below). - When the value is to be displayed, use the
format-number(<value>, '#.##')
format. This should ensure that integer or decimal values will be displayed as originally present in the XML. - For optional tags (such as "Discount"), use the
format-number(<value>, '0.00')
function even if the value is only being computed. This is necessary because if the tag is absent, trying to obtain a value will give anNaN
result.
Here is a illustrative example of the 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(<expression>, '0.00')</th>
<th>format-number(<expression>, '#.##')</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>
As the HTML comments note, it works in most cases but not all.
I would like to use a single expression to format all prices same as the input, rather than applying "when-otherwise" constructs everywhere which would additionally require a boolean parameter passed to the XSLT to determine the display format. The present state of the XSLT is that all numbers are rounded off regardless of the input, using format-number(<expression>, '0')
.
How do I accomplish this?
Edit: After Dimitre's comment, I decided to create a sample XML (and the XSLT above) so the experts can try it out easily.
Sample XML (This one contains decimals):
<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>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果我理解正确,您希望:
最接近您想要的的是这个表达式:
由于 XPath 1.0 中没有易于使用的条件表达式 (XPath 2.0 有
if/then/else
),折衷方案是这样:但是,这个“失败”(截掉小数)当
$cost
、$extraCharges
和$discount
加起来为整数时。没有吸引力的替代方法是使用贝克尔方法(以 Oliver Becker of < a href="http://www.informatik.hu-berlin.de/" rel="noreferrer">HU Berlin):
从技术上讲,这是一句简单的话。 两个
sub-string()
部分基于$condition
是互斥的,因此concat()
只会返回一个值或另一个值。实际上(特别是对于您的情况),这将是一个难以管理的混乱,缓慢且完全隐藏了意图:
由于您声明所有输入值都包含小数,或者都不包含小数,因此上面的表达式仅检查
$cost
包含小数点。 我猜检查$extraCharges
和$discount
也是正确的。If I understand you correctly, you want:
The closest you have gotten to what you want is this expression:
Since there is no easy to use conditional expression in XPath 1.0 (XPath 2.0 has
if/then/else
), a compromise would be this:However, this "fails" (cutting off the decimals) when
$cost
,$extraCharges
and$discount
add up to a round number.The unattractive alternative is to use Becker's method (named after Oliver Becker of HU Berlin):
Technically, that's a one-liner. The two
sub-string()
parts are mutually exclusive based on$condition
, soconcat()
will only return the one value or the other.Practically (especially for your case), it's going to be an unmanageable mess, slow and entirely concealing the intention:
Since you stated that either all input values contained decimals, or none of them, the above expression only checks if
$cost
contains the decimal dot. It would be correct to check$extraCharges
and$discount
as well, I guess.