Friday, January 18, 2013

Better formulas with XPath type annotations

With XForms, you use XPath expressions to specify complex validation constraints and calculations. Take the following XML snippet:
You can declaratively calculate the total like this:
<xf:bind ref="total" calculate="../units * ../price"/>
../units and ../price are paths that refer to the XML elements with those names. This is much like a good old spreadsheet formula, except you are not working on cells but on XML data.
Because there is a multiplication with *, each side is atomized by the XPath evaluator. This means that the expression engine looks at the XML elements and extracts a value from them. This makes sense, because you can’t multiply two elements, you can only multiply two numbers!
But there is a twist, because multiplication is defined on different kinds of numbers: in XPath, you can in particular multiply integers, floating-point numbers, and decimals. In this example, what does the XPath evaluator do? Well, by default, it looks at each number as a double-precision floating-point number (double).
And this is not always what you want. For example, when dealing with currencies, you really want decimal numbers in order to avoid funny rounding errors. In this case, you must use casts, which make the expression more complicated:
<xf:bind ref="total"
  calculate="xs:decimal(../units) * xs:decimal(../price)"/>
Now XForms also allows you to assign types to XML data:
<xf:bind ref="units, price, total" type="xs:decimal"/>
This annotates all three elements with the xs:decimal type, with the meaning that the value is required to conform to that type. It’s designed so that if the user, via an input field, enters an incorrect value, or fails to enter a value, that value will be marked as invalid. [1]
But with XForms prior to version 2, XPath expressions were not aware of these annotations. This is understandable, because formally XForms only specified support for XPath 1.0, which had a limited set of data types (string, number, and boolean), and which didn’t specify how annotations on a data model should work.
The good news is that XForms 2 officially supports XPath 2 [2], and this gives the XPath evaluator an opportunity to use type annotations properly. So, with the examples given above, the calculate expression, when looking at the values of the units and price elements, can notice that they have an xs:decimal type, and handle them properly as decimal values. The expression remains as lightweight as it was without annotations:
<xf:bind ref="total" calculate="../units * ../price"/>
You might wonder what happens if the value is not of the specified type. Say you have:
The answer is that when the XPath evaluator tries to access the typed value of price, the calculation is interrupted and its result is set to an empty string. It makes sense not to attempt to complete the calculation in this case, following the “garbage in, garbage out” philosophy.
Orbeon Forms has experimentally supported exposing type annotations to XPath for years, but recently we have ironed them out and enabled them by default for new forms created with Form Builder. And we are now working to officially make this part of XForms 2!
For more details, see the Orbeon Forms documentation.

  1. Besides validation, types can also, depending on the XForms engine, affect the visual representation of a form control associated with that piece of data. A classical example is a date picker for an xs:date value.  ↩
  2. Orbeon Forms on the other hand has always supported XPath 2 as an extension of XForms 1.1.  ↩

No comments:

Post a Comment