PDA

View Full Version : Ext.ux.XPathReader, an Ext.data.DataReader with true XPath 1.0 support



Roland.Bouman
2 Jan 2011, 6:25 PM
Hi all,

I just wrote a Data Reader for XML documents that supports true XPath syntax. This as opposed to the CSS-based selectors implemented by the standard Ext.data.XmlReader (which has some basic XPath extensions). I wrote it to extract data from XML formats which I could not parse with the standard DomQuery selectors.

Here's a few examples:

#1: getting data from a node that is an ancestor of the nodes identified by the "record" selector:



<container>
<group>
<name>Group1</name>
<item><name>Item1</name></item>
<item><name>Item2</name></item>
...
</group>

<group>
<name>Group2</name>
<item><name>Item1</name></item>
<item><name>Item2</name></item>
...
</group>
</container>


Desired resultset:

[
{group: "Group1", item: "Item1"},
{group: "Group1", item: "Item2"},
{group: "Group2", item: "Item1"},
{group: "Group2", item: "Item2"}
]

As far as I can see, there is no CSS selector to refer to a parent node (or any ancestor node for that matter). For this particular case I could work around it by writing :parent and :grandParent pseudo selectors, but to me this didn't feel right, because as I understand it pseudo selectors should conceptually filter nodes out of the current nodelist, whereas my goal is to extract data from outside the current nodelist.

With the XPathReader, these things are reasonably simple:



var reader1 = Ext.ux.XPathReader({
record: "//item",
fields: [
{name: "group", mapping: "../name"}, /* find the name of the item's group (.. = parent) */
{name: "item"} /* mapping defaults to name if omitted */
]
});


#2 Grab the tagname as data
(Yes I know it sounds wrong. But I have to deal with this.)


<container>
<a>bla</a>
<b>blabla</b>
...
</container>

Desired resultset:

[
{name: "a", text: "bla"},
{name: "b", text: "blabla"}
]

I tried to solve this one also by writing a tagName pseudo selector, but this didn't succeed, presumably because I want to return a string (tagname) in this case, not select a node from a list.

With the XPathReader, this is how to do it:



var reader2 = Ext.ux.XPathReader({
record: "/container/*",
fields: [
{name: "name", mapping: "local-name()"}, /* grab the tagname */
{name: "text", mapping: "."} /* grab the tag value */
]
});


These represent just the few use cases I need to work against right now. It's possible that I failed because I lack skills in CSS selector syntax - if that is so I'd appreciate to see a solution based on the standard XmlReader.

I'd like to share this with other ExtJs users, and I hope Sencha Inc will accept it as a user-extension and ship it in some future release (Sencha inc, if you're reading this, please let me know if you're interested and if so, what you need from me in order to include it). I've attached the source as an attachment to this post.

For those who are interested to know how it works, and what XPath syntax it supports: It relies on XSLT support present in all major browsers I know of (IE6+, Webkit, Opera, Gecko). I override the extractData(), buildExtractors(), and createAccessor() methods of the standard Ext.data.XmlReader. In buildExtractors, I generate XSLT based on the fields and record config. I then use some cross-browser compatible script to instantiate an XSLT processor, and I load the XSLT stylesheet into it.

To actually read records, the xslt processor runs the xslt stylesheet over the source document, and generates a XML document of the form:


<d sucess="...successproperty.." message="...messageproperty..." total="...totalproperty...">
<r name1="value1" name2="value2" ... nameN="valueN"/>
...
<r name1="value1" name2="value2" ... nameN="valueN"/>
</d>

This generated document is then traversed using ordinary DOM iteration, passing the values on to the default

All browsers I've tested have fairly good support for XSLT and the XPath 1.0 on which it relies so you should be able to write pretty much all XPath described in http://www.w3.org/TR/xpath/.

I hope it's useful. Please let me know if you have any issues with it, and if you have any suggestions to improve it.

Cheers, Roland.

JimR
4 Jan 2011, 11:48 AM
Thanks! I think we've largely switched to JSON for internal reasons, but I'm sure this will come in handy!

Roland.Bouman
4 Jan 2011, 2:59 PM
JimR, sure! If you do happen to need it at some point, I'll welcome any feedback.

Steph
6 Oct 2011, 5:55 AM
Thanks for your work,

i have to use complex XML with treeloader and gridPanel (and some DD). I think this will be helpful for me !
i will try it right now.

cgalpin
15 Nov 2011, 7:29 AM
I'd really like to avoid using an extension for this, but can someone confirm that what I want to do is simple not possible with the built in XMLReader? My xml looks something like



<a>
<b>
<type>x</type>
<data>123</data>
</b>
<b>
<type>y</type>
<data>123</data>
</b>
</a>
<a>
...
</a>
...

And I am trying to use a/b[type='y']/data as my mapping but it doesn't work. Is there an alternate syntax that I could use without resorting to an extension?

tia,
charles

pierre.voisin
1 May 2013, 10:23 AM
I believe you should write it like:

http://www.xpathtester.com/obj/f0dad654-a7e2-47c1-937c-bcaf715ac32f