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:
Code:
<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:
Code:
[
{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:
Code:
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.)
Code:
<container>
<a>bla</a>
<b>blabla</b>
...
</container>
Desired resultset:
Code:
[
{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:
Code:
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:
Code:
<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.