2009-03-21 9 views
0

Je suis dans une situation où mes fichiers XSLT doivent afficher les prix avec des décimales conditionnellement, selon que l'entrée XML contient des décimales ou non. Donc, je peux recevoir des fichiers XML avec deux types de valeurs - XML ​​contiendra tous les prix formatés avec des décimales jusqu'à deux endroits (j'appelle celui-ci "Decimal-XML") ou les prix seront arrondis à l'entier le plus proche (j'appelle cela un "Integer-XML"). Mon problème est que j'ai besoin de refactoriser le moins possible dans les fichiers XSLT et pourtant leur permettre d'appliquer la transformation à XHTML dans le même format que l'entrée XML. Pour ce faire, je mis en œuvre et a proposé trois directives à mon équipe:XSLT Formatage décimal basé sur l'entrée XML

  1. Retirez toutes les fonctions format-number() appelle lorsque la valeur est calculée pour les calculs ou stockés dans une variable. Utilisez number(<value>) à la place. Cependant, certaines conditions s'appliquent à cette règle (voir ci-dessous).
  2. Lorsque la valeur doit être affichée, utilisez le format format-number(<value>, '#.##'). Cela devrait garantir que les valeurs entières ou décimales seront affichées comme initialement présentes dans le fichier XML.
  3. Pour les balises facultatives (telles que "Discount"), utilisez la fonction format-number(<value>, '0.00') même si la valeur est uniquement calculée. Ceci est nécessaire car si la balise est absente, essayer d'obtenir une valeur donnera un résultat NaN.

Voici un exemple illustratif du 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> 

Comme les commentaires HTML note, il fonctionne dans la plupart des cas, mais pas tous. Je voudrais utiliser une seule expression pour mettre en forme tous les prix identiques à l'entrée, plutôt que d'appliquer partout des constructions "when-else" qui nécessiteraient en outre un paramètre booléen transmis au XSLT pour déterminer le format d'affichage. L'état actuel du XSLT est que tous les nombres sont arrondis quelle que soit l'entrée, en utilisant format-number(<expression>, '0').

Comment accomplir ceci?


Edit: Après le commentaire de Dimitre, j'ai décidé de créer un exemple XML (et XSLT ci-dessus) de sorte que les experts peuvent essayer facilement.

XML Exemple (Celui-ci contient des décimales):

<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> 
+0

Vous avez oublié de fournir même le document XML le plus simple possible. Ce n'est pas utile. –

+0

@Dimitre: Je n'ai pas oublié de le fournir, mais je dois avouer que je n'avais pas réalisé qu'une source XML serait requise car ce n'est pas vraiment une question de transformation. C'est une question générique sur le formatage décimal. Pourtant, je vais essayer d'en créer un si cela peut aider. – Cerebrus

Répondre

6

Si je comprends bien, vous voulez:

  • "Entier XML" toujours apparaître comme un nombre arrondi, non décimales
  • « XML décimal » toujours apparaissent avec deux décimales
  • ne pas utiliser un modèle qui fait la mise en forme, mais un XPath un liner

Le plus proche que vous avez obtenu ce que vous voulez est cette expression:

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

Comme il n'y a pas facile à utiliser l'expression conditionnelle dans XPath 1.0 (XPath 2.0 has if/then/else), un compromis serait:

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

Cependant, cette "échoue" (coupant les décimales) lorsque $cost, $extraCharges et $discount ajouter jusqu'à un nombre rond.

La solution peu intéressante est d'utiliser la méthode de Becker (du nom de Oliver BeckerHU Berlin):

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

Techniquement, qui a une seule ligne. Les deux parties sub-string() s'excluent mutuellement en fonction de $condition, donc concat() ne renverra qu'une seule valeur ou l'autre.

Pratiquement (en particulier pour votre cas), il va être un désordre ingérable, lent et tout à dissimuler l'intention:

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, '#.##')) 
) 
) 

Puisque vous dites que ce soit toutes les valeurs d'entrée contenaient des décimales, ou aucun d'entre eux, l'expression ci-dessus ne vérifie que si $cost contient le point décimal. Il serait bon de vérifier également $extraCharges et $discount, je suppose.