2009-03-21 18 views
0

Ich habe eine Situation, wo meine XSLT-Dateien Preise mit Dezimalzahlen bedingt anzeigen sollten, abhängig davon, ob die XML-Eingabe Dezimalzahlen enthält oder nicht. So kann ich XML-Dateien mit zwei Arten von Werten empfangen - XML ​​wird alle Preise enthalten, die mit Dezimalen bis zu zwei Stellen formatiert sind (ich nenne dies "Dezimal-XML") oder die Preise werden auf die nächste Ganzzahl gerundet (ich nenne das) ein "Integer-XML").XSLT Dezimale Formatierung basierend auf Eingabe XML

Mein Problem ist, dass ich so wenig wie möglich in den XSLT-Dateien umgestalten und ihnen dennoch erlauben soll, die Transformation auf XHTML im selben Format wie die XML-Eingabe anzuwenden. Um dies zu erreichen, habe ich umgesetzt und schlug vor, drei Richtlinien an meinem Team:

  1. Alle entfernen format-number() Funktion aufruft, wenn der Wert in einer Variablen für Berechnungen oder gespeichert berechnet wird. Verwenden Sie stattdessen number(<value>). Für diese Regel gelten jedoch bestimmte Bedingungen (siehe unten). Wenn der Wert angezeigt werden soll, verwenden Sie das Format format-number(<value>, '#.##'). Dies sollte sicherstellen, dass Integer- oder Dezimalwerte so angezeigt werden, wie sie ursprünglich im XML vorhanden waren.
  2. Verwenden Sie für optionale Tags (z. B. "Rabatt") die Funktion format-number(<value>, '0.00'), auch wenn der Wert nur berechnet wird. Dies ist notwendig, da, wenn das Tag nicht vorhanden ist, der Versuch, einen Wert zu erhalten, ein NaN Ergebnis ergibt. Hier

ist ein anschauliches Beispiel für die 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> 

Als HTML-Kommentare Note, es funktioniert in den meisten Fällen aber nicht alle.

Ich würde gerne einen einzelnen Ausdruck verwenden, um alle Preise gleich wie die Eingabe zu formatieren, anstatt "wann-sonst" -Konstrukte überall anzuwenden, die zusätzlich einen booleschen Parameter benötigen, der an das XSLT übergeben wird, um das Anzeigeformat zu bestimmen. Der aktuelle Status von XSLT besteht darin, dass alle Zahlen unabhängig von der Eingabe mit format-number(<expression>, '0') gerundet werden.

Wie bewerkstellige ich das?


Edit: Nach Dimitre Kommentar, habe ich beschlossen, eine Probe XML (und die XSLT oben) zu schaffen, damit die Experten kann es leicht ausprobieren.

XML-Beispiel (Diese enthält Dezimalstellen):

<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

Sie haben vergessen, selbst das einfachste XML-Dokument anzugeben. Dies ist nicht hilfreich. –

+0

@Dimitre: Ich habe nicht vergessen, es zu geben, aber ich muss zugeben, dass ich nicht erkannte, dass eine XML-Quelle erforderlich wäre, weil dies nicht wirklich eine Frage der Transformation ist. Es ist eine allgemeine Frage zur dezimalen Formatierung. Trotzdem werde ich versuchen, eine zu erstellen, wenn das hilft. – Cerebrus

Antwort

6

Wenn ich Sie richtig verstehe, Sie wollen:

  • "Integer-XML" immer als eine abgerundete Zahl zu zeigen, nein Dezimalstellen
  • „dezimal-XML“ immer mit zwei Dezimalstellen zeigen
  • keine Vorlage zu verwenden, die die Formatierung der Fall ist, sondern ein XPath-Einzeiler

Die nächstgelegene Sie bekommen haben, was Sie wollen, ist dieser Ausdruck:

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

Da es keine einfache ist bedingter Ausdruck in XPath 1 zu verwenden.0 (XPath 2.0 has if/then/else), wäre ein Kompromiss sein:

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

Doch diese "versagt" (Abschneiden der Nachkommastellen), wenn $cost, $extraCharges und $discount auf eine runde Zahl addieren.

Die unattraktive Alternative ist Beckers Verfahren (benannt nach dem Oliver BeckerHU Berlin) zu verwenden:

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

Technisch, das ist ein Ein-Liner. Die beiden sub-string() Teile schließen sich gegenseitig basierend auf $condition aus, so dass concat() nur den einen oder den anderen Wert zurückgibt.

Praktisch (vor allem für Ihren Fall), es wird eine unüberschaubare Chaos, langsam und ganz die Absicht zu verbergen sein:

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

Da Sie festgestellt, dass entweder alle Eingabewerte Dezimalstellen enthalten, oder keiner von ihnen, Der obige Ausdruck überprüft nur, ob den Dezimalpunkt enthält. Es wäre richtig, auch $extraCharges und $discount zu überprüfen, denke ich.