Skip to main content

XForms extensions to XPath

Going beyond the standard

Elliotte Rusty Harold (elharo@metalab.unc.edu), Adjunct Professor, Polytechnic University
Photo of Elliot Rusty Harold
Elliotte Rusty Harold is originally from New Orleans, to which he returns periodically in search of a decent bowl of gumbo. However, he resides in the Prospect Heights neighborhood of Brooklyn with his wife Beth and cats Charm (named after the quark) and Marjorie (named after his mother-in-law). He's an adjunct professor of computer science at Polytechnic University, where he teaches Java and object-oriented programming. His Cafe au Lait Web site has become one of the most popular independent Java sites on the Internet, and his spin-off site, Cafe con Leche, has become one of the most popular XML sites. His next book, Refactoring HTML, will be published by Addison Wesley later this year. He's currently working on the XOM API for processing XML and the Jaxen XPath engine.

Summary:  XForms uses XML Path Language (XPath) as its basic function and evaluation language. This is the same XPath used in Extensible Stylesheet Language Transformations (XSLT). In addition to familiar functions like count and substring, XForms introduces a number of useful extension functions to XPath for numeric, date, and XForms-specific operations including if, avg, min, max, now, days-from-date, month, and instance.

Date:  07 Aug 2007
Level:  Introductory
Activity:  2709 views

XForms adopts the familiar XPath expression language for two purposes. First, it uses it as a query language for addressing and identifying fields within the form and the user-submitted data. In particular, it uses XPath expressions to bind input controls to particular parts of the form's data model. Second, it uses XPath as its basic calculation language for output, bind, and setvalue elements. The focus of this article is mostly on the second usage, though some of these functions are useful for binding as well.

XPath was designed to be extensible both by users and implementers. XPath 1.0 defines a basic set of functions such as count and translate that are suitable for a broad range of purposes. Languages such as XSLT, XQuery, XML Pointer Language (XPointer), XML Signature, and XForms that adopt the XPath expression language are expected to expand on this basic set with additional functions. XForms takes advantage of this by introducing new functions useful in forms processing. In particular, XForms adds functions for math, dates, times, Booleans, and XForms-specific operations. You may never see these functions in an XSLT stylesheet or a Jaxen program, but you can use them today in any XForms 1.0 document.

XForms that calculate

I'll begin with a simple example. Listing 1 shows a basic XForm that displays today's date.

Listing 1. An XHTML document containing a simple XForm

                <html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:xforms="http://www.w3.org/2002/xforms">
   <head>
    <title>Date and Time</title>
    <xforms:model>
     <xforms:instance xmlns="">
      <Name/>
     </xforms:instance>
    </xforms:model>
   </head>
   <body>
    <h1>
      <xforms:output value="now()">
        <xforms:label>The time is now: </xforms:label>
      </xforms:output>
    </h1>
   </body>
</html>

Figure 1 shows this form running in Firefox with the Mozilla XForms plug-in.


Figure 1. The date is supplied by the now function
The time is now: 2007-06-10T08:54:21-05:00

The xforms:output element evaluates the XPath 1.0 expression contained in its value attribute. It inserts this result into the browser's view of the document. In this example, the XPath expression consists solely of the function now(), which returns the current date and time, formatted according to ISO 8601. That is, year-month-day:hours:minutes:seconds-timezone. This is the time format recommend by the XML Schema specification. Listing 1 is straightforward and obvious except for one thing: Where the heck did the now() function come from?

XSLT 1.0 and XPath 1.0 do not have a now() function. Indeed, including one would contradict XSLT's basic nature as a functional programming language. A stylesheet that uses a now() function produces different results depending on when it is run. However, XForms does not have the same goals and constraints as XSLT. XForms is neither functional nor Turing complete. Rather, it is declarative. Therefore, functions such as now() that don't fit into XSLT fit nicely with XForms. With that in mind, I'll explore the other new functions XForms 1.0 introduces.


Dates and times

2007-06-10T08:54:21-05:00 is complete, but it's not exactly pretty. XForms adds four more functions for converting dates into individual pieces such as years and months:

  • days-from-date()
  • seconds-from-dateTime()
  • seconds()
  • months()

The first two operate on date-times and can be used on the output of now() as well as on date values input by the user or supplied by the form itself. The second two operate on durations, also in the format specified by ISO 8601 and the World Wide Web Consortium (W3C) XML Schema Language.

days-from-date()

The days-from-date function converts a date in the form 2007-07-30T08:54:21-05:00 or 2007-07-30 into the number of days that have elapsed since January 1, 1970. You can use this to calculate the difference between two dates or the amount of time that has passed. For example, Listing 2 counts the days remaining until Christmas:

Listing 2. An XForm that counts the days remaining until Christmas

                <html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:xforms="http://www.w3.org/2002/xforms">
   <head>
    <title>Days until Christmas</title>
    <xforms:model>
     <xforms:instance xmlns="">
      <variables>
        <Now/>
        <Year/>
        <Christmas/>
        <ChristmasDay/>
        <Result/>
      </variables>
     </xforms:instance>

    <xforms:bind nodeset="Now" calculate="days-from-date(now())"/>
    <xforms:bind nodeset="Year" calculate="substring(now(), 1, 4)"/>
    <xforms:bind nodeset="Christmas" calculate="concat(../Year, '-12-25')"/>
    <xforms:bind nodeset="ChristmasDay" calculate="days-from-date(../Christmas)"/>
    <xforms:bind nodeset="Result" calculate="../ChristmasDay - ../Now"/>

    </xforms:model>
   </head>
   <body>
    <h1>
      <xforms:output value="Result">
        <xforms:label>Days until Christmas: </xforms:label>
      </xforms:output>
    </h1>
   </body>
</html>

Compressing the form

This could be written with one long complicated XPath expression like this one:

days-from-date(concat(substring(now(), 1, 4), '-12-25'))
 - days-from-date(now())

However, it seems much clearer with the temporary variables.

In this case, the instance holds five temporary variables: Christmas, ChristmasDay, Now, Year, and Result. These five variables are set by XPath expressions in bind elements.

  • Now is set by using days-from-date to get the number of days that have elapsed since January 1, 1970 until today.
  • Year is set by picking off the first four digits of today's date with a substring operation.
  • Christmas is set by appending "-12-25" to the Year retrieved in the previous step.
  • ChristmasDay is set by using days-from-date on Christmas.
  • The form subtracts Now from ChristmasDay to get the number of days remaining.

The final result is displayed by an output element.

This form does not yet handle dates after December 25. I'll introduce one more extension function to help with that shortly.

seconds-from-dateTime()

The seconds-from-dateTime function converts a date in the form 2007-07-30T08:54:21-05:00 into the number of seconds that have elapsed since midnight, January 1, 1970, Greenwich Mean Time. You can use this to calculate the difference between two times or the amount of time that has passed. For example, this output element counts the seconds remaining until Christmas:

<xforms:output value="seconds-from-dateTime(
  concat(substring(now(), 1, 4), '-12-25T00:00:00-05:00')) 
  - seconds-from-dateTime(now())">
   <xforms:label>Seconds until Christmas: </xforms:label>
</xforms:output>

seconds-from-dateTime returns NaN if it can't convert the string to a date.

seconds()

The seconds() function operates on durations rather than datetimes. It takes a string in the form P3DT10H30M1.5S (3 days, 10 hours, 0 minutes, and 1.5 seconds) and converts it into just seconds (though possibly fractional). This is useful for comparing durations and adding and subtracting them.

months()

The months() function also operates on durations. It takes a string in the form P2Y7M3DT10H30M1.5S (2 years, 7 months, 3 days, 10 hours, 0 minutes, and 1.5 seconds) and converts it into just months. Days, hours, minutes, and seconds are truncated.

There are no corresponding days(), hours(), or minutes() functions. If you want those, you have to ask for seconds; then do the math.


Numeric functions

XForms adds four numeric functions to XPath 1.0:

  • avg()
  • min()
  • max()
  • count-non-empty()

avg()

The avg() function calculates the arithmetic mean of a node-set. That is, it converts each of the nodes in the set to a number, adds them up, and divides the result by the number of nodes. If any of the nodes in its argument node-set can't be converted to a number, avg() returns NaN.

min() and max()

The min() and max() functions also take node-sets as arguments. Each member is converted to a number. The min() function returns the minimum value, and the max() function returns the maximum value. If any of the arguments can't be converted to a number, these functions return NaN.

For example, the XForms model in Listing 3 provides several temperature measurements. (The user would use a repeating control to fill them in.) Then bind elements calculate the minimum, maximum, and average of all the temperature measurements and assign them to other elements.

Listing 3. An XForms model that calculates high, low, and average temperatures from multiple measurements

                    <xforms:model>
     <xforms:instance xmlns="">
      <Data>
        <Temperature/>
        <LowTemperature/>
        <HighTemperature/>
        <AverageTemperature/>
      </Data>
     </xforms:instance>

     <xforms:bind nodeset="AverageTemperature" type="xs:decimal"
      calculate="avg(../Temperature)"/>
     <xforms:bind nodeset="LowTemperature" type="xs:decimal"
      calculate="min(../Temperature)"/>
     <xforms:bind nodeset="HighTemperature" type="xs:decimal"
      calculate="max(../Temperature)"/>
  </xforms:model>

count-non-empty()

The XPath built-in count() function returns the number of nodes in a node-set. The XForms extension function count-non-empty() returns the number of non-empty nodes in a set, where "empty" is defined as having a string representation that is not empty.

For example, to calculate the average price of several items, first sum up all the individual prices with an expression like sum(../Price). Then divide the sum by count-non-empty(../Price).

      <xforms:output value="sum(../Price) div count-non-empty(../Price)">
        <xforms:label>Average price: </xforms:label>
      </xforms:output>

By contrast, if you divide by count(../Price), the average price might be too low if there are extra fields in the form that the user simply hasn't filled in.


Boolean functions

XForms adds two Boolean functions to XPath 1.0:

  • if()
  • boolean-from-string()

if()

XSLT doesn't need if() because it has xsl:if. However, XForms doesn't have a selection operator, so it needs to add the if() function to XPath. The if() function takes three arguments:

  • The test condition
  • The true result
  • The false result

That is,

if (condition, trueresult, falseresult)

The if() function is like the ternary ?: operator in the Java™ and C programming languages.

For example, Listing 4 shows a revised version of the days until Christmas model that takes into account the possibility that the form might run on Boxing Day (the day after Christmas). To do this, it calculates both possible results, then uses if() to choose between them depending on whether the first result is negative.

Listing 4. An XForm that counts the days remaining till this Christmas or next Christmas

                    <xforms:model>
     <xforms:instance xmlns="">
      <variables>
        <Now/>
        <Year/>
        <Christmas/>
        <ChristmasDay/>
        <NextChristmas/>
        <NextChristmasDay/>
        <Result/>
        <Result1/>
        <Result2/>
      </variables>
     </xforms:instance>

    <xforms:bind nodeset="Now" calculate="days-from-date(now())"/>
    <xforms:bind nodeset="Year" calculate="substring(now(), 1, 4)"/>
    <xforms:bind nodeset="Christmas" calculate="concat(../Year, '-12-25')"/>
    <xforms:bind nodeset="NextChristmas" calculate="concat(../Year + 1, '-12-25')"/>
    <xforms:bind nodeset="ChristmasDay" calculate="days-from-date(../Christmas)"/>
    <xforms:bind nodeset="NextChristmasDay" 
                    calculate="days-from-date(../NextChristmas)"/>
    <xforms:bind nodeset="Result1" calculate="../ChristmasDay - ../Now"/>
    <xforms:bind nodeset="Result2" calculate="../NextChristmasDay - ../Now"/>
    <xforms:bind nodeset="Result" 
      calculate="if(../ChristmasDay - ../Now &gt;= 0, 
                 ../Result1,
                 ../Result2) "/>

    </xforms:model>


boolean-from-string()

The boolean-from-string() function is semi-redundant with XPath's built-in boolean() function, but only half. The boolean-from-string() function follows the W3C schema definition of Boolean. The boolean() function follows the XPath definition of Boolean.

In W3C schemas, the strings "true" and "1" are true. The strings "false" and "0" are false. All other strings are false.

By contrast, in XPath, all non-empty strings are true, including "false" and "0". Furthermore, boolean() has specific rules for determining the truth value of numbers and node-sets. The boolean-from-string() function simply converts arguments of other types to strings and then tests their truth.


XForms functions

XForms defines three functions purely for its own use that make no sense elsewhere:

  • instance()
  • property()
  • index()

instance()

When an XForms model contains multiple instances, the instance() function selects the one to work with. For example, suppose the days-until-Christmas calculation is just a piece of the page's user interface, but you don't really want to send it back to the server. There's other data you'd send back to the server, as shown in Listing 5. Here the real data is in the instance with the ID orderInfo and the temporary variables are in the instance with the ID temporaryVariables.

Listing 5. An XForm that contains two instances

                 <xforms:model>
     <xforms:instance xmlns="" id="temporaryVariables">
      <variables>
        <Now/>
        <Year/>
        <Christmas/>
        <ChristmasDay/>
        <NextChristmas/>
        <NextChristmasDay/>
        <Result/>
        <Result1/>
        <Result2/>
      </variables>
     </xforms:instance>

     <xforms:instance xmlns="" id="orderInfo">
      <Order>
        <Item/>
        <Price/>
      </Order>
     </xforms:instance>

    <xforms:bind nodeset="Now" calculate="days-from-date(now())"/>
    <xforms:bind nodeset="Year" calculate="substring(now(), 1, 4)"/>
    <xforms:bind nodeset="Christmas" 
               calculate="concat(instance('temporaryVariables')/Year, '-12-25')"/>
    <xforms:bind nodeset="NextChristmas" 
               calculate="concat(instance('temporaryVariables')/Year + 1, '-12-25')"/>
    <xforms:bind nodeset="ChristmasDay" calculate="days-from-date(../Christmas)"/>
    <xforms:bind nodeset="NextChristmasDay" 
               calculate="days-from-date(instance('temporaryVariables')/NextChristmas)"/>
    <xforms:bind nodeset="Result1" 
               calculate="instance('temporaryVariables')/ChristmasDay - ../Now"/>
    <xforms:bind nodeset="Result2"
               calculate="instance('temporaryVariables')/NextChristmasDay 
                        - instance('temporaryVariables')/Now"/>
    <xforms:bind nodeset="Result" 
      calculate="if(instance('temporaryVariables')/ChristmasDay 
                  - instance('temporaryVariables')/Now &gt;= 0, 
                 instance('temporaryVariables')/Result1,
                 instance('temporaryVariables')/Result2) "/>

    </xforms:model>

Rather than assuming a particular root node, the bind elements now specify the one they want using instance('temporaryVariables'). Other bind elements can bind to the second instance with instance('orderInfo'). Indeed, you can even combine data from two or more instances in one XPath expression.

property()

The property() function returns the value of an XForms property. XForms only defines two properties:

version
The version of XForms supported: 1.0 for XForms 1.0, 1.1 for XForms 1.1, and so forth.
conformance-level
The profile of XForms in use, typically "full" for a complete implementation.

Specific XForms processors may define additional properties beyond these two standard ones. Usually these are namespace qualified.

index()

The index() function returns the current position of a repeat element. That is, you pass the index of the repeat as the argument, and the function returns the current item in that repeat. It's useful for operating on the item the user is currently working with.


Extension functions

The fun doesn't stop there. Individual vendors are allowed (and even encouraged) to expand on the basic function library with additional functions of their own. For example, MozzIE 1.8 adds the EXSLT math and date-and-time extension functions. The math functions include abs(), acos(), asin(), atan(), atan2(), constant(), cos(), exp(), highest(), log(), lowest(), sin(), sqrt(), and tan(). The date-and-time functions include add(), add-duration(), date(), date-time(), day-abbreviation(), day-in-month(), day-in-week(), day-in-year(), day-name(), day-of-week-in-month(), difference(), duration(), format-date(), hour-in-day(), leap-year(), minute-in-hour(), month-abbreviation(), month-in-year(), month-name(), parse-date(), second-in-minute(), sum(), time(), week-in-month(), week-in-year(), year(), and date-for().

However, you should be aware that an XForm that uses any of these will only work in MozzIE. It will not work in formsPlayer, Orbeon Forms, Chiba, or Firefox (at least not until they also implement these EXSLT functions). Therefore, you probably want to stick to the standard XForms 1.0 set for broad deployment. Still, these might be useful in an Intranet environment where you know everyone has the same form processor.

An XForm that contains non-standard XPath extension functions should declare them in a functions attribute on the model element. Listing 6 shows how to declare some EXSLT extension functions for trigonometry and dates.

Listing 6. An XForm that declares extension functions

                 <xforms:model
    xmlns:date="http://exslt.org/dates-and-times"
    xmlns:math="http://www.exslt.org/math"
    functions="math:cos math:sin date:add date:year">
     <xforms:instance xmlns="" id="temporaryVariables">
 ...
     </xforms:instance>
...

 </xforms:model>

When the functions attribute is present on the model, the XForms processor reads it before loading the form. If it sees an extension function it doesn't recognize, it halts processing by signaling an xforms-compute-exception event.


In conclusion

The X in XPath stands for extensible. XForms has extended XPath to provide extra power and unique features that are worth learning. While XForms supports the full range of XPath 1.0 you're used to from XSLT, it has quite a bit more. Some of these functions like days-from-date() and now() are just nice to have. Others like if() and instance() are critical components of any non-trivial XForm. More XForms use these functions than don't. Learn to take advantage of the XForms XPath extension functions and you too can be an XForms master.


Resources

Learn

Get products and technologies

  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.

Discuss

About the author

Photo of Elliot Rusty Harold

Elliotte Rusty Harold is originally from New Orleans, to which he returns periodically in search of a decent bowl of gumbo. However, he resides in the Prospect Heights neighborhood of Brooklyn with his wife Beth and cats Charm (named after the quark) and Marjorie (named after his mother-in-law). He's an adjunct professor of computer science at Polytechnic University, where he teaches Java and object-oriented programming. His Cafe au Lait Web site has become one of the most popular independent Java sites on the Internet, and his spin-off site, Cafe con Leche, has become one of the most popular XML sites. His next book, Refactoring HTML, will be published by Addison Wesley later this year. He's currently working on the XOM API for processing XML and the Jaxen XPath engine.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=245665
ArticleTitle=XForms extensions to XPath
publish-date=08072007
author1-email=elharo@metalab.unc.edu
author1-email-cc=dwxed@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers