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>