views:

804

answers:

3

I have an XML document where the number of decimal places a particular xs:decimal should be reported in is held in a sibling node. I'm currently struggling to find a simple way to output this via the format-number function.

I can build a picture string with some other functions, but this seems terribly long-winded for what should be (at least imo) a relatively straightforward and common task.

e.g. what I'm currently doing is something like this:

<xsl:value-of
 select="format-number(myNode/DecimalValue,
         concat('#0.', 
                string-join(for $i in 1 to myNode/DecimalPlaces return '0'))"
/>

Is there a better way?

+3  A: 

Very good question! That usually means, I don't know the answer but I hope someone else does as this is a pain for me too.

Anyway, i did some searching and I think the round-half-to-even function might do the trick (http://www.xqueryfunctions.com/xq/fn_round-half-to-even.html)

Your code would become:

<xsl:value-of 
  select="
    round-half-to-even(
      myNode/DecimalValue
    , myNode/DecimalPlaces
    )
  "
/>

Now off for a little tangent: For people that are using XSLT 1.1 or lower and XPath 1, you could use this:

<xsl:value-of 
  select="
    concat(
      substring-before(DecimalValue, '.')
    , '.'
    , substring(substring-after(DecimalValue, '.'), 1, DecimalPlaces -1)
    , round(
        concat(
          substring(substring-after(DecimalValue, '.'), DecimalPlaces, 1)
        ,   '.'
        ,   substring(substring-after(DecimalValue, '.'), DecimalPlaces+1)
        )
      )
    )
  "
/>

Of course, this code is worse than the original, but if anyone knows how to solve the original question for XPath 1 and has a better idea than this, I'd love to hear that. (More and more often, I wish the world would have skipped XML altogether and moved immediately to JSON)

Roland Bouman
Holy carp, that XSLT1.1/XPath 1 code is horrible! Thankfully I don't have to support those versions :) I think your answer may well be what I'm looking for, but I'll wait a little while longer before accepting it to see if anyone else has any other ideas. Thanks!
adhocgeek
Yeah. I am a heavy client side browser XSLT user. Mock me but it works quite well for what I need to do. But for some reason (I have a hunch why) all browsers are still stuck at XSLT 1.1 / XPath 1. So at some point you just have to suck it up, and torture some kittens to get the work done. And things like this emerge. Thankfully, there s a lot of good stuff to compensate for this kind of crap.
Roland Bouman
+2  A: 
<!-- use a generous amount of zeros in a top-level variable -->
<xsl:variable name="zeros" select="'000000000000000000000000000000000'" />

<!-- …time passes… -->
<xsl:value-of select="
  format-number(
     myNode/DecimalValue,
     concat('#0.', substring($zeros, 1, myNode/DecimalPlaces))
  )
" />

You can abstract it away into a template:

<!-- template mode is merely to prevent collisions with other templates -->
<xsl:template match="myNode" mode="FormatValue">
  <xsl:value-of select="
    format-number(
      DecimalValue, 
      concat('#0.', substring($zeros, 1, DecimalPlaces))
    )
  " />
</xsl:template>

<!-- call like this -->
<xsl:apply-templates select="myNode" mode="FormatValue" />

You can also make a named template and use the XSLT context node when calling it. Depends a bit on your input document and needs if this is feasible for you.

<xsl:template name="FormatValue">
  <!-- same as above -->
</xsl:template>

<!-- call like this -->
<xsl:for-each select="myNode">
  <xsl:call-template name="FormatValue" />
</xsl:for-each>
Tomalak
+1 looks perfect
Filburt
Yeah, that's another way to do it. I know I can't earn credit by saying I thought of that too, but I did, and I didn't post it because the OP asked for a *better* way to do it. I did not consider this better, because I saw no indication that the maximum precision was known in advance. Personally, I would certainly use a solution like this if i for some reason could not use `round-half-to-even()` and if I would know the maximum precision.That said, I very much agree with the OP that it is just bananas that something this simple is so hard in XPath/XSLT
Roland Bouman
One little optimization might be to get rid of **concat()** and put '#0.' into the constant and then substring **DecimalPlaces+3.**
Filburt
@Roland Bouman: Well, it depends. A precision of 20 or 30 decimal places is highly unlikely, if not impossible. Preparing 20 or 30 zeros for padding is certainly getting you on the safe side. Admittedly, hard-coding them is not *as clean*.
Tomalak
@Filburt: I've thought about that, too. I just wanted to avoid a magic number "+3" in favor of something that is instantly apparent.
Tomalak
@Roland Bouman: Also, for reasons you mention in your comment to the other answer, I prefer sticking with XSLT 1.0/XPath 1.0 wherever possible. 2.0 is just not common enough.
Tomalak
Interesting solution, certainly. I did think of something like this, but dismissed it because it doesn't feel particularly clean. Still, it's probably worth keeping in mind, thanks!
adhocgeek
@Tomalak, I agree. For all applications I had to deal with so far, this solution would be acceptable. Interesting to see you use browser XSLT as well. It never got much penetration, and I don't see many people with XSLT skills. For me it meant an enormous boost in productivity as compared to classic webapps, and I am very happy to see the new browser also have decent XSLT support (XSLT 1.1 that is). Anyway - thanks for the discussion, and kind regards.
Roland Bouman
A: 

Thank you Tomalak

your answer helped me a lot.

Sabu