/* JPath 1.0.4 - json equivalent to xpath Copyright (C) 2009 Bryan English Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Usage: var jpath = new JPath( myjsonobj ); var somevalue = jpath.$('book/title').json; //results in title //or var somevalue = jpath.query('book/title'); //results in title Supported XPath-like Syntax: /tagname //tagname tagname * wildcard [] predicates operators ( >=, ==, <= ) array selection .. * and, or nodename[0] nodename[last()] nodename[position()] nodename[last()-1] nodename[somenode > 3]/node nodename[count() > 3]/node Tested With: Firefox 2-3, IE 6-7 Update Log: 1.0.1 - Bugfix for zero-based element selection 1.0.2 - Bugfix for IE not handling eval() and returning a function 1.0.3 - Bugfix added support for underscore and dash in query() function Bugfix improper use of Array.concat which was flattening arrays Added support for single equal sign in query() function Added support for count() xpath function Added support for and, or boolean expression in predicate blocks Added support for global selector $$ and // Added support for wildcard (*) selector support 1.0.4 - Changed to MIT license */ function JPath( json, parent ) { this.json = json; this._parent = parent; } JPath.prototype = { /* Property: json Copy of current json segment to operate on */ json: null, /* Property: parent Parent json object, null if root. */ parent: null, /* Method: $ Performs a find query on the current jpath object. Parameters: str - mixed, find query to perform. Can consist of a nodename or nodename path or function object or integer. Return: jpath - Returns the resulting jpath object after performing find query. */ '$': function ( str ) { var result = null; var working = this; if ( this.json && str !== null ) { switch ( typeof(str) ) { case 'function': result = this.f(str).json; break; case 'number': result = this.json[str] || null; break; case 'string': var names = str.split('/'); //foreach slash delimited node name// for ( var i=0; i\\<\\!])\\=([^\\=])" : '$1==$2', "\\[" : "$(function(j){ with(j){return(", "\\]" : ");}}).", "\\(\\.":'(', "(\\.|\\])(?!\\$|\\p)":"$1json", "count\\(([^\\)]+)\\)":"count('$1')" }; //save quoted strings// var quotes = /(\'|\")([^\1]*)\1/; var saves = new Array(); while ( quotes.test(str) ) { saves.push( str.match(quotes)[2] ); str = str.replace(quotes,'%'+ (saves.length-1) +'%'); } for ( var e in re ) { str = str.replace( new RegExp(e,'ig'), re[e] ); } //alert('this.' + str.replace(/\%(\d+)\%/g,'saves[$1]') + ";"); return eval('this.' + str.replace(/\%(\d+)\%/g,'saves[$1]') + ";"); }, /* Method: f Performs the equivilant to an xpath predicate eval on the current nodeset. Parameters: f - function, an iterator function that is executed for every json node and is expected to return a boolean value which determines if that particular node is selected. Alternativly you can submit a string which will be inserted into a prepared function. Return: jpath - Returns the resulting jpath object after performing find query. */ f: function ( iterator ) { var a = new Array(); if ( typeof(iterator) == 'string' ) { eval('iterator = function(j){with(j){return('+ iterator +');}}'); } for ( var p in this.json ) { var j = new JPath(this.json[p], this); j.index = p; if ( iterator( j ) ) { a.push( this.json[p] ); } } return new JPath( a, this ); }, /* Method: parent Returns the parent jpath object or itself if its the root node Return: jpath - Returns the parent jpath object or itself if its the root node */ parent: function() { return ( (this._parent) ? this._parent : this ); }, /* Method: position Returns the index position of the current node. Only valid within a function or predicate Return: int - array index position of this json object. */ position: function() { return this.index; }, /* Method: last Returns true if this is the last node in the nodeset. Only valid within a function or predicate Return: booean - Returns true if this is the last node in the nodeset */ last: function() { return (this.index == (this._parent.json.length-1)); }, /* Method: count Returns the count of child nodes in the current node Parameters: string - optional node name to count, defaults to all Return: booean - Returns number of child nodes found */ count: function(n) { var found = this.$( n || '*').json; return ( found ? ( found instanceof Array ? found.length : 1 ) : 0 ); }, /* Method: root Returns the root jpath object. Return: jpath - The top level, root jpath object. */ root: function () { return ( this._parent ? this._parent.root() : this ); } };