Laszlo and XPath

* - *
|

Laszlo implements a subset of the XPath W3C Standard XML Path Language. It's pretty sensible, and, I think, fairly intuitive.

For instance, given a dataset that looks like this:

    <dataset name="dset">
        <root>
            <anode/>
            <anode v="0"/>
            <anode v="1"/>
            <anode v="0"/>
            <anode v="1"/>
            <anode v="0"/>
            <anode v="1"/>
        </root>
    </dataset>

I can make selections on it. I can select the first node this way:

    dset:/root/anode[1]

The symbol "*" means "any node", so I can write

    dset:/root/*[1]

and acheive the same effect (XPath offsets are 1 based.) The token '[1]' is a predicate, which narrows the set selected by the path that preceedes it. I can use other tokens in predicates as well. For instance, to select all of the nodes that have an attribute 'v', I can write:

    dset:/root/*[ @v ]

And I can combine predicates, so that this:

    dset:/root/*[ 6- ][ @v = "1" ]

Selects the last anode.

One thing that's interesting about XPath expressions is that they can return either null (for no match,) a single element, or a list. The general consensus in implementations appears to be that these possibilities should be modeled with overloaded return types -- null, a single datanode, or a list of datanodes respectively. This can be extremely inconvenient, since there isn't a great way to tell the type of an object at runtime. In general, you have to write code like this:

    var r = dp.xpathQuery( "dset:/*/*" );
    if ( r instanceof Array ){
        //multiple matches
        ...
    } else {
        //single or no match
    }

Alternatively, you could do this:

    var r = dp.xpathQuery( "dset:/*/*" );
    if ( ! ( r instanceof Array ) ){
        r = [ r ];
    }

    //array processing
    ....

Of course, if that's how you handle it, then xpathQuery should probably have just returned an array in the first place.

One final note about all this stuff. XML in general obviously is very order-sensitive, and the original order of an XML document often matters a lot. However, the position() operator in XPath returns the position of the element within a selection. For instance, you might expect this query:

    dset:/root/*[ @v='0' ]/position()

to return the array [ 2, 4, 6]. Instead it returns the array [ 1, 2, 3]. This is often useful behavior when processing additional predicates, such as:

    dset:/root/*[ @v='0' ][ position() = last()]
    //(this is currently unimplemented in Laszlo)

but as far as I know, there's no way to get the original position of a node in the document. Perhaps this is because that information can at times be ambigious. For instance, in the selection '*/*', the nodes might have the absolute positions [1, 2, 1, 2, 3, 1, 2] in the case where there are multiple top-level nodes and multiple subnodes. Obviously, if there were such an absolute position operator, a simple number would not really be enough to unamibiguously represent a node's position within the original data. Either the programmer would have to keep track of that, or the absolute position operator would have a more complex return type. In the above example, something like this perhaps: [ [ 1,1], [1,2], [2,1], [2,2], [2,3], [3,1], [3,2] ]

XPath isn't bad, but I think I would always prefer a more script oriented way of working with data. BEA is working on some interesting stuff in this area -- they call it Native XML Scripting.

Anyway, here's a little laszlo program that let's you type an XPath query against the dataset at the top of this page and see what the results are:

VIEW/EDIT PROGRAM

Comments

How can I find the last value of child like your "anode" without knowing how many elements I've got. My structure is:

value1
value2
value3
value4
valueN

I don't how many value there are and I don't if there is an order. How can I select the last "valuen" ??

Paolo,
That's a great question. As I noted above, the [ position() = last() ] predicate is unimplemented in Laszlo. The easiest way to do this right now would probably be to use Laszlo's DOM-style API, but here's a datapointer which uses a set of attributes to create the proper xpath by querying the dataset. You could obviously edit basexpath to suit your application. Expect to see the predicate implemented in a future release, though.

<datapointer ondata="Debug.write( this.serialize() )">
<attribute name="basexpath" value="dset:/root/anode" type="string"/>
<attribute name="lastxpath" value="${basexpath + '/last()'}" />
<attribute name="lastnum" value="${xpathQuery( lastxpath )}" />
<attribute name="xpath" value="${basexpath + '['+ lastnum + ']' }" />
</datapointer>

|
* * *