Using XPath expressions

XForms is built on a number of different standards, and perhaps the most important is XPath. This standard is used to indicate which pieces of data should be manipulated by the form, and because the language is extremely powerful, expressions can get quite complex. For this reason XForms also supports a number of ways to abbreviate these expressions, and it's the shorthand techniques what have been used in the examples so far.

The evaluation context

XForms uses XPath expressions in many places, and it is important as an author to understand how these expressions are evaluated. Perhaps the most important concept is that of the evaluation context.

When an XPath expression is evaluated, the processor needs to know both the model and the instance against which to evaluate the expression. Since all expressions are evaluated in some context then it should always be possible to work out the meaning of even short expressions such as "/".

To illustrate the context rules, we'll begin with the simplest situation, when the form being processed has only one model and one instance:

  <xf:model id="mdl">
    <xf:instance id="inst">
      <a xmlns="">
        <b />
      </a>
    </xf:instance>
  </xf:model>

Explicit references

We'll look first at all the ways we could bind to element b. The first approach is to make everything--both the model and the instance--explicit:

  <xf:input model="mdl" ref="instance('inst')/b">
    <xf:label>B:</xf:label>
  </xf:input>

In a small form we would be unlikely to use this explicit syntax since there are so many shortcuts available to us. But this syntax does have the advantage that it will always refer to element b, regardless of where in our form it is placed. For example, if we wanted to show the value of b in the hint of some other control, we would need to use this explicit syntax if the other control was in another model:

  <xf:input model="mdl2" ref="instance('inst2')/d">
    <xf:label>D:</xf:label>
    <xf:hint>
      Please enter a
      '<xf:output model="mdl" ref="instance('inst')/b" />'
    </xf:hint>
  </xf:input>

The 'First Instance' rule

Perhaps the simplest abbreviation we can make on our earlier mark-up is to make use of the 'First Instance' rule, which says that if no instance is specified, the first instance in the specified model should be used. Our previous example can be altered to remove the use of instance():

  <xf:input model="mdl" ref="b">
    <xf:label>B:</xf:label>
  </xf:input>

We can safely rely on this rule in our example so far, since we only have one instance, but once our forms get to a reasonable size it is probably worth specifying the instance explicitly. That way, if form controls are moved around or a new instance is added to a model, the form will continue to work correctly.

To illustrate the kinds of problems that can arise, in the following example a new instance has been added, but since it has been placed first in the model, the previous reference to b is no longer bound correctly since it relied on the 'first instance' rule, and 'inst3' is now the first instance:

  <xf:model id="mdl">
    <xf:instance id="inst3">
      <c xmlns="">
        <d />
      </c>
    </xf:instance>

    <xf:instance id="inst">
      <a xmlns="">
        <b />
      </a>
    </xf:instance>
  </xf:model>

  <xf:input model="mdl" ref="b">
    <xf:label>B:</xf:label>
  </xf:input>

We can fix this by referring to the instance explicitly, and if we had made our initial references explicit then we would not have had a problem in the first place.

The 'First Model' rule

In the same way that if there is no instance specified the first one is implied, so too if no model is specified then the first model will be used. With this in mind, we can further abbreviate our expression:

  <xf:model id="mdl">
    <xf:instance id="inst">
      <a xmlns="">
        <b />
      </a>
    </xf:instance>
  </xf:model>

  <xf:input ref="/a/b">
    <xf:label>B:</xf:label>
  </xf:input>

or:

  <xf:input ref="b">
    <xf:label>B:</xf:label>
  </xf:input>

This last control only works because the evaluation context is actually the root element of the instance document (a) and not the document itself. In other words, the expression "*" (which means return all children of the context node) would return b and not a.

Using group to set context for a number of controls

When overriding the 'first model' or 'first instance' rules, as discussed above, it is not always necessary to place the model or instance that you want directly onto a control; if there is more than one control then it is certainly more efficient to use a group:

  <xf:model id="mdl">
    <xf:instance id="inst3">
      <c xmlns="">
        <d />
        <e />
      </c>
    </xf:instance>

    <xf:instance id="inst">
      <a xmlns="">
        <b />
      </a>
    </xf:instance>
  </xf:model>

  <xf:model id="mdl2">
    <xf:instance id="inst2">
      <c xmlns="">
        <d />
      </c>
    </xf:instance>
  </xf:model>

  <xf:group model="mdl">
    <xf:input ref="d">
      <xf:label>D:</xf:label>
    </xf:input>
    <xf:input ref="e">
      <xf:label>E:</xf:label>
    </xf:input>
  </xf:group>

Rather than having @model on each of the two controls, things are more succinct by setting a new evaluation context for all the controls via a group.

Conclusion

A number of strategies are available in XForms to abbreviate an XPath expression. Which one we use for a particular form will depend on how our form will evolve.

XPath functions

This section describes the functions that can be used in XPath expressions.

Core XForms functions

The XForms Core Function Library includes the entire XPath Core Function Library, including operations on node-sets, strings, numbers, and booleans. In addition XForms adds the functions described in the following sections.

days-from-date()

number days-from-date(string)

This function returns a whole number of days, according to the following rules:

  1. If the string parameter represents a legal lexical xsd:date or xsd:dateTime, the return value is equal to the number of days difference between the specified date or dateTime (normalized to UTC) and 1970-01-01.
  2. Hour, minute, and second components are ignored after normalisation.
  3. Any other input parameter causes a return value of NaN.

Examples:

days-from-date("2002-01-01")

returns 11688

days-from-date("1969-12-31")

returns -1

More examples

days-to-date()

string days-to-date(number)

This function returns a string containing a lexical xsd:date that corresponds to the number of days passed as the parameter according to the following rules:

  1. The number parameter is rounded to the nearest whole number.
  2. The result is interpreted as the difference between the desired date and 1970-01-01.
  3. An input parameter value of NaN results in output of the empty string.

Examples:

days-to-date(11688)

returns 2002-01-01

days-to-date(-1)

returns 1969-12-31

More examples

instance()

node-set instance(string)

An XForms Model can contain more that one instance. This function allows access to instance data, within the same XForms Model, but outside the instance data containing the context node.

The argument is converted to a string as if by a call to the string function. This string is treated as an IDREF, which is matched against instance elements in the containing document. If a match is located, and the matching instance data is associated with the same XForms Model as the current context node, this function returns a node-set containing just the root element node (also called the document element node) of the referenced instance data. In all other cases, an empty node-set is returned.

Example:

For instance data corresponding to this XML:

<xforms:instance xmlns="" id="orderform">
  <orderForm>
    <shipTo>
      <firstName>John</firstName>
    </shipTo>
  </orderForm>
</xforms:instance>

The following expression selects the firstName node. Note that the instance function returns an element node, effectively replacing the leftmost location step from the path:

ref="instance('orderform')/shipTo/firstName"

power()

number power(number, number)

Raises the first argument to the power of the second argument, returning the result. If the calculation does not result in a real number, then NaN is returned.

Examples:

power(-1, 0.5)

returns NaN.

if (prin>0 and dur>0 and rate>0, prin*rate/(1-power(1+rate, -dur)), 0)

returns a compounded interest payment value given a non-zero principal (prin), duration (dur) and periodic interest rate (rate).

More examples.

Functions added by formsPlayer

formsPlayer adds some additional functions.

globalInstance()

The standard XForms function instance() must only return instances from the model of the current evaluation context, and although trying to obtain an instance from another model won't result in an error, the return value will be null.

The rationale is to improve performance of XForms processors, since if an author is unable to create dependencies between models, then calculations for each model can be performed on each model individually as necessary, rather than for every model in a form.

Although authors are prevented from creating depencies between models, there will still be occassions when data needs to be moved from one model to another. For this reason formsPlayer adds the globalInstance() function, which can return a reference to any instance in the form. Note however, that if this function is used in bind statement no dependency is created; in other words, if the data in an instance in another model changes, the bind statement will not automatically be recalculated. If a recalculation is needed, then it is the author's responsibility.

A proposal has been made to add this feature to XForms.

Adding custom XPath functions

With formsPlayer it is possible to write your own XPath functions to manipulate instance data within the XForms data processing model. One example of doing this is discussed in the context of controlling a 3D viewer, which shows how to create inline functions (i.e., functions that are defined in the current source document).

It's also possible to create XPath functions which are methods on COM components.