0

入力 XML に小数が含まれているかどうかに応じて、XSLT ファイルが条件付きで小数とともに価格を表示する必要がある状況があります。したがって、2 種類の値を持つ XML ファイルを受け取ることができます。XML には、2 桁までの小数点以下でフォーマットされたすべての価格が含まれます (これを「Decimal-XML」と呼びます)。または、価格は最も近い整数に丸められます (これを「10 進 XML」と呼びます)。 1 つの「Integer-XML」)。

私の問題は、XSLT ファイルのリファクタリングを最小限に抑えながら、XML 入力と同じ形式で XHTML への変換を適用できるようにする必要があることです。これを達成するために、私は次の 3 つのガイドラインを実装し、チームに提案しました。

  1. format-number()値が計算のために計算されているとき、または変数に格納されているときに、すべての関数呼び出しを削除します。number(<value>)代わりに使用してください。ただし、このルールには特定の条件が適用されます (以下を参照)。
  2. 値を表示する場合は、format-number(<value>, '#.##')フォーマットを使用してください。これにより、整数値または 10 進数値が XML に元々存在していたとおりに表示されるようになります。
  3. オプションのタグ (「Discount」など) の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 コメントにあるように、ほとんどの場合に機能しますが、すべてではありません。

表示形式を決定するために XSLT に渡されるブール値パラメーターを追加で必要とする "when-otherwise" 構造をあらゆる場所に適用するのではなく、単一の式を使用してすべての価格を入力と同じようにフォーマットしたいと考えています。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」は常に小数点以下 2 桁で表示されます
  • 書式設定を行うテンプレートを使用するのではなく、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すると丸数になります。

魅力的でない代替手段は、ベッカーの方法 ( HU ベルリンのオリバー・ベッカーにちなんで名付けられた) を使用することです。

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

技術的には、それはワンライナーです。2 つの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 に答える