7

Say I have the following XML

    <A>100</A>
    <B>200</B>
    <C>300</C>

and the following XSLT

  <TEST>
    <xsl:value-of select="A + B + C"/>
  </TEST>

It produces the following output

<TEST>600</TEST>

however, when one of the nodes is blank

    <A></A>
    <B>200</B>
    <C>300</C>

I get the following.

<TEST>NaN</TEST>

I only want to add the nodes that are valid numbers. I could do this if xsl allowed me to dynamically replace a variable value by adding to the already existing variable, but even that would be messy. I assume there is an easy way that I'm missing?

I want XSLT 1.0 answers only please.

Thanks!

LarsH
  • 27,481
  • 8
  • 94
  • 152
james31rock
  • 2,615
  • 2
  • 20
  • 25
  • possible duplicate of [Using fn:sum in XSLT with node-set containing null-values](http://stackoverflow.com/questions/2279314/using-fnsum-in-xslt-with-node-set-containing-null-values) – Filburt Sep 04 '12 at 19:53
  • Not a duplicate because the other is the sum of one node name, this is summing different nodes together. Plus thats xslt 2.0. I asked for 1.0. – james31rock Sep 05 '12 at 13:04
  • Not an exact duplicate of course but contains a hint on how to test against empty nodes (not specific to xslt 2.0). – Filburt Sep 05 '12 at 14:15
  • I already knew how to use filters how to test against empty nodes. The issue I was trying to test three different nodes at once that was giving me the problem. Tried A[.!=''] + B[.!=''] + C[.!=''] and had the same issue. got to this before i saw a better answer sum(A[.!=''] | B[.!=''] | C[.!='']). problem is mine doesn't test if its a number, plus I using three filters inside of one. – james31rock Sep 05 '12 at 15:47

4 Answers4

12
  <TEST>
    <xsl:value-of select="sum((A | B | C)[number(.) = .])"/>
  </TEST>

That is, sum the subset of the elements A,B,C consisting of those whose contents can be used as numbers.

Note that number('') yields NaN, and (NaN = NaN) is false, so this will not include elements without text content.

We test for numbers as discussed at https://stackoverflow.com/a/3854389/423105

Community
  • 1
  • 1
LarsH
  • 27,481
  • 8
  • 94
  • 152
  • Note that this test will not work in XPath 2.0, where you "Cannot compare xs:double to xs:string". – LarsH Sep 04 '12 at 21:30
  • 3
    It will work in XPath 2.0 if there is no schema, or if the schema says that A, B, and C are numeric. You *can* compare xs:double to xs:untypedAtomic. – Michael Kay Sep 04 '12 at 22:20
  • 1
    @MichaelKay: Hmm. The above error was what I received with XPath 2.0 on an XML document that had no schema associated. This was in the XPath 2.0 evaluator in Oxygen XML Editor. I wonder if there was some hidden or default schema that I wasn't aware of? – LarsH Sep 05 '12 at 01:39
  • 1
    @LarsH. Thanks I was looking for something simple like that. I had a solution, but it seemed overkill and messy. – james31rock Sep 05 '12 at 13:05
  • 1
    Sorry, I was wrong. [number(.)=.] means [number(.)=xs:double(data(.))], and unlike number(), xs:double() doesn't convert '' to NaN, it throws an error. – Michael Kay Sep 05 '12 at 14:46
  • 1
    @james31rock: glad to help. Also consider Dimitre's answer, which is a little longer but more robust against errors (especially in XPath 2.0). – LarsH Sep 05 '12 at 15:11
  • Looks like the best answer on SOF for adding two numbers without thinking whitespace or empty nodes. – Sanjeev Singh Nov 16 '20 at 20:39
5

A little variation of LarsH's answer:

sum((A|B|C)[number(.) = number(.)])

the expression in the predicate doesn't cause any type error in XPath 2.0, because both arguments of = are of the same type -- xs:double.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
3

There is also:

For XSLT 1.0:

sum((A|B|C)[string(number())!='NaN'])

For XSLT 2.0:

sum((A,B,C)[string(number())!='NaN'])

For interest, here is another one. It works for both XSLT 1.0 and 2.0

sum((A|B|C)[number()>-99999999])

In the above, replace -99999999 with one less than the lower bound of the possible values. This works because NaN > any-thing always returns false. This is probably the most efficient solution yet, but it may be seen as a little ugly.

For example, if you know for a fact that none of A, B or C will be negative values, you could put:

sum((A|B|C)[number()>0])

...which looks a little cleaner and is still quiet readable.

Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
2
<xsl:value-of select="sum(/root/*[self::A or self::B or self::C][.!=''])"/>

This will add values from A, B, and C under the "Root" element so long as the value isn't blank.

Parker
  • 1,082
  • 3
  • 13
  • 27