Wednesday, August 14, 2013

Formulas for summing values, done right

Recently, we’ve been asked a number of questions relating to computing the sum of numbers in a form, whether with Form Builder or with plain XForms.

This seems pretty simple, because XPath, the expression language used by Orbeon Forms, has a sum() function. But there can be some difficulties:

  • Values can be missing, for example because the user hasn’t yet entered them.
  • Values can be incorrect, for example if the user has entered “gaga” instead of a valid number.

This can be solved in a couple of ways:

  1. If there is any missing or incorrect value, then produce a blank result.
  2. Or, ignore the missing or incorrect values and always produce a result. Is no value is present or correct, then output 0.

First, we assume that all the values have a decimal data type. With Form Builder, you simply specify the “Decimal” data type for the control.

Then say that you have 3 fields to sum called a, b, and c.

The “Calculate” expression to write for the first approach is simply:

sum(($a, $b, $c))

The sum() function takes a sequence of nodes or values (which is why you have to double the parentheses). If the expression fails due to missing or incorrect values, then the result of the calculation is set to a blank value.

The “Calculate” expression for the second approach looks like this:

sum(($a, $b, $c)[string() castable as xs:decimal], 0.0)

We are still using the same sequence of three values, but we are filtering them with a condition (called a predicate). The predicate only allows values which are castable as (or convertible to) a decimal value, so only those values are passed to the sum() function.

The function supports a second argument which is the value to return in case no value satisfies the predicate. This makes sure that we return a decimal value, as we are using a literal decimal 0.0.[1]

In this second scenario, the calculation always succeeds, even if values are missing or incorrect.

Here is a plain XForms example.

On as similar topic, see also Better formulas with XPath type annotations.

  1. A slightly tricky bit is the use of the string() function. You would think that you could write:  ↩

    sum(($a, $b, $c)[. castable as xs:decimal], 0.0)

    And this works in general. However, accessing the context item with . attempts to retrieve the typed value, as with the XPath data() function. If the value is a decimal, then there’s no problem. But if the value is not a decimal, a runtime error will happen. Using the string() function prevents that problem by returning the string value of the data. If the data is “gaga”, then that’s returned, and used to check whether it is castable as a decimal value.

1 comment: