PDA

View Full Version : real xpath selector



christophe.geiser
15 Jun 2011, 1:36 AM
Hi all,

For those working with XML, this xpath extension might be useful. It is working well with Chrome and FF, not tested with IE (if somebody could confirm that this is ok with IE though, would be appreciated).

use like :

XML.xpath('.//item[@test = "me"]')

or with namespace :

XML.xpath('.//xf:item[@test = "me"]', {xf: 'http://www.w3.org/2002/xforms'})

Along with those changes/overrides:


//====================
// overrideXmlReader
//====================
/*modify extract Data so as to allow an xpath selector*/
Ext.override(Ext.data.XmlReader,
{ extractData: function(root) {
var me = this;
var recordName = me.record,
selector = me.selector;

if (!recordName) {
Ext.Error.raise('Record is a required parameter');
}
if (recordName != root.nodeName) {
root = Ext.DomQuery.select(selector ? selector : ('.//' + recordName), root);
} else {
root = [root];
}
return this.callParent([root]);
}
});


Ext.DomQuery.select = document.xpath ?
function(path, root, type) {
root = root || document;
if (Ext.DomQuery.isXml(root)) {return root.xpath(path)}
return Ext.DomQuery.jsSelect.call(this, path, root, type);
}
: function(path, root, type) {return Ext.DomQuery.jsSelect.call(this, path, root, type)};

It allows to use real xpath expressions in DomQuery selectors (and handle nested xml data with the use of a xpath selector fitting the structure of the data, e.g. like: './*/item' if the data looks like :


<root>
<items>
<item id="1">
<label>first item</label>
<items>
<item id="2">
<label>second Item (nested)</label>
</item>
<item id="3">
<label>third Item (nested)</label>
</item>
</items>
</item>
</items>
</root>).

Cheers,
C.



Ext.define('Ext.ux.xpath',{
singleton : true,
ns : {":": "http://www.w3.org/1999/xhtml11","xsl": "http://www.w3.org/1999/XSL/Transform", "xsi": "http://www.w3.org/2001/XMLSchema-instance", "xsd": "http://www.w3.org/2001/XMLSchema", "xf": "http://www.w3.org/2002/xforms", "ev": "http://www.w3.org/2001/xml-events", "ext": "http://www.sencha.com"}
});
Ext.define('Ext.ux.xpath.Xpath', {
expression : '.',
ns : Ext.ux.xpath.ns,
constructor: function(config){
var me = this;
Ext.apply(me,config);
if(!me.expression) {me.expression = '.'};
if(me.expression.substring(0,2) == '.[') { me.expression = 'self::node()' +me.expression.substring(1)} // bug ?? in chrome not evaluation .[@attribute] or .[element]
if(!me.ctx) {me.ctx = document};
if(!me.doc) {me.doc= me instanceof Document ? me: document};
},
evalMapping : {
1: 'numberValue',
2: 'stringValue',
3: 'booleanValue'
},
evaluate : function() {
var temp,x,me=this;
try {
x=me.doc.evaluate(
me.expression,
me.ctx,
me.ns ? function(prefix) {return me.ns[prefix ? prefix : ":"] || null}
: me.doc.createNSResolver ? me.doc.createNSResolver(me.ctx) : null,
XPathResult.ANY_TYPE
,null);
} catch (e) {
Ext.Error.raise({
parseError: e,
xpath: me,
msg: "xpath expression is invalid: " + me.expression
});
}
switch (x.resultType)
{case XPathResult.NUMBER_TYPE:
case XPathResult.STRING_TYPE:
case XPathResult.BOOLEAN_TYPE:
me.value=x[me.evalMapping[x.resultType]];
break;

case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
me.value=[];
while (!!(temp=x.iterateNext())) me.value.push(temp);
break;

case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
me.value=[];
for (temp=0;temp<x.snapshotLength;++temp) me.value.push(x.snapshotItem(i));
break;

case XPathResult.ANY_UNORDERED_NODE_TYPE:
case XPathResult.FIRST_ORDERED_NODE_TYPE:
me.value=x.singleNodeValue;
break;}

return me.value
},
evaluateIE : function() {
var me = this;
me.setDocIE();

return me.value = me.ctx.selectNodes(me.expression);
},
setDocIE : function() {
var x='', me = this;
if(me.doc instanceof HTMLDocument) {
me.doc = new ActiveXObject("Msxml2.DOMDocument").appendChild(me.doc.cloneNode(true));
Ext.apply(me.doc, {async: false, validateOnParse : false })
}
me.doc.setProperty("SelectionLanguage", "XPath");
for (var ns in me.ns) {x += "xmlns" +(ns == ":"? "'": ":'") + this.ns[ns] + "' "};
me.doc.setProperty("SelectionNamespaces", x);
}
},function() {
var Xp = this;
if(Ext.isIE){
Element.prototype.xpath = function(expression,ns) {return new Xp( {expression: expression , ns: ns || Xp.prototype.ns, ctx:this, doc:this.ownerDocument || this.elementDocument}).evaluateIE()}
}
else {
Node.prototype.xpath = function(expression,ns) {return new Xp( {expression: expression , ns: ns || Xp.prototype.ns, ctx:this, doc:this.ownerDocument}).evaluate()}
}
});

KampfCaspar
15 Jun 2011, 3:54 AM
While not mainly interested in the UI document, I DO need selectors a lot on loaded xml documents... I wrote the following little wrapper class for xml documents with:

Ext.DomQuery.select* analogs
generic XPath method returning an iterator
XPath(Node|Number|String|Bool) -> returns ONE value or default value
XPath(Node|Number|String|Bool)s -> returns either array or object of values, object keys are extracted by a second xpath


NB: I assume document.evalute (DOM 3 XPath) is available. If you're stuck with IE, have a look at JavaScript-XPath (http://coderepos.org/share/wiki/JavaScript-XPath).


Ext.define( 'HPO.XML.Document', {
mixins: {
observable: 'Ext.util.Observable'
},

requires: [
'Ext.DomQuery',
'Ext.Ajax'
],

config: {
prefixes: null,
document: null,
request: null,
defaultType: XPathResult.ANY_TYPE
},

document_node: null,
resolver_func: null,
active_request: null,

constructor: function( config ) {
this.addEvents([
'beforeload',
'load',
'change',
'exception'
]);

config = config || {};
if ( config.documentElement ) {
config = { document: config };
}
else if ( Ext.isString(config) || config.url ) {
config = { request: config };
}

this.initConfig( config );
this.mixins.observable.constructor.call(this, config);

return this;
},

setPrefixes: function( ns ) {
var me = this;

this.prefixes = null;
if ( Ext.isFunction(ns) ) {
this.resolverfunc = ns;
}
else if ( Ext.isObject(ns) ) {
this.prefixes = ns;
this.resolverfunc = function( prefix ) {
return me.prefixes[prefix] || null;
};
}
else {
this.resolverfunc = null;
}

return this;
},

setRequest: function( request ) {
this.request = request;
if ( request ) {
this.load( request );
}
return this;
},

setDocument: function( doc ) {
var old = this.document;
this.document = doc;
this.document_node = doc ? doc.documentElement : null;
if ( doc !== null || old !== null ) {
this.fireEvent( 'change', this );
}
return this;
},

correctRequest: function( request ) {
if ( Ext.isString(request) ) {
request = { url: request };
}

return request;
},

load: function( request ) {
request = this.correctRequest( request );

Ext.apply( request, {
scope: this,
success: function( response, options ) {
this.active_request = null;
this.setDocument( response.responseXML );
this.fireEvent( 'load', this, response, options );
},
failure: function( response, options ) {
this.active_request = null;
this.document_node = null;
this.fireEvent( 'exception', this, response, options );
}
});

if ( this.fireEvent( 'beforeload', request, this ) !== false ) {
this.active_request = Ext.Ajax.request( request );
}
},

isLoading: function() {
return this.active_request ? Ext.Ajax.isLoading( this.active_request ) : false;
},

hasContent: function() {
return (this.document_node != null);
},

getRoot: function() {
return this.document_node;
},

createResolver: function( node ) {
return this.resolverfunc ? this.resolverfunc : this.document.createNSResolver( node ? node : this.document_node );
},

resolveContext: function( context ) {
return context ? context : this.document_node;
},

CSS: function( selector, context ) {
return Ext.DomQuery.select( selector, this.resolveContext( context ) );
},

CSSNode: function( selector, context ) {
return Ext.DomQuery.selectNode( selector, this.resolveContext( context ) );
},

CSSValue: function( selector, context, defvalue ) {
return Ext.DomQuery.selectValue( selector, this.resolveContext( context ), defvalue );
},

CSSNumber: function( selector, context, defvalue ) {
return Ext.DomQuery.selectNumber( selector, this.resolveContext( context ), defvalue );
},

_collect: function( iter, extract, idpath ) {
var res,t;
var xexpr, xres;

if ( idpath ) {
res = {};
t = iter.iterateNext();
if ( t ) {
xexpr = t.ownerDocument.createExpression( idpath, this.createResolver( t ) );
do {
xres = xexpr.evaluate( t, XPathResult.STRING_TYPE, xres );
res[xres.stringValue] = extract(t);
}
while ( t = iter.iterateNext() );
}
}
else {
res = [];
while ( t = iter.iterateNext() ) {
res.push( extract(t) );
}
}
return res;
},

XPath: function( selector, context, type ) {
var c = this.resolveContext( context );

return this.document.evaluate( selector, c, this.createResolver( c ), type || this.defaultType, null );
},

XPathNode: function( selector, context ) {
var res = this.XPath( selector, context, XPathResult.FIRST_ORDERED_NODE_TYPE );
return res.singleNodeValue;
},

XPathNodes: function( selector, context, idpath ) {
return this._collect(
this.XPath( selector, context, XPathResult.ORDERED_ITERATOR_TYPE ),
function(node) { return node; },
idpath
);
},

XPathString: function( selector, context, defvalue ) {
var res = this.XPath( selector, context, XPathResult.STRING_TYPE );
return Ext.isEmpty(res.stringValue, true) ? defvalue : res.stringValue;
},

XPathStrings: function( selector, context, idpath ) {
return this._collect(
this.XPath( selector, context, XPathResult.ORDERED_ITERATOR_TYPE ),
function(node) { return node.textContent; },
idpath
);
},

XPathNumber: function( selector, context, defvalue ) {
var res = this.XPath( selector, context, XPathResult.NUMBER_TYPE );
return Ext.isEmpty( res.numberValue ) ? defvalue : res.numberValue;
},

XPathNumbers: function( selector, context, idpath ) {
return this._collect(
this.XPath( selector, context, XPathResult.ORDERED_ITERATOR_TYPE ),
function(node) { return Number(node.textContent); },
idpath
);
},

XPathBool: function( selector, context, defvalue ) {
var res = this.XPath( selector, context, XPathResult.BOOLEAN_TYPE );
return Ext.isEmpty( res.booleanValue ) ? defvalue : res.booleanValue;
},

XPathBools: function( selector, context, idpath ) {
return this._collect(
this.XPath( selector, context, XPathResult.ORDERED_ITERATOR_TYPE ),
function(node) { return Boolean(node.textContent); },
idpath
);
}

});

christophe.geiser
15 Jun 2011, 4:35 AM
Pretty cool
Thanks for the IE xpath link
Cheers
C.

frankfenghua
9 May 2012, 10:55 AM
i'm new to ExtJS, coming from a flex env. it's very hard for me to render a nested xml tree in tree panel.
would you please post an working sample of how to use your xpath selector to render an nexted xml tree?