diff options
Diffstat (limited to 'js/dojo/dojox/json/query.js')
| -rw-r--r-- | js/dojo/dojox/json/query.js | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/js/dojo/dojox/json/query.js b/js/dojo/dojox/json/query.js new file mode 100644 index 0000000..0f4c914 --- /dev/null +++ b/js/dojo/dojox/json/query.js @@ -0,0 +1,262 @@ +//>>built +define("dojox/json/query", ["dojo/_base/kernel", "dojox", "dojo/_base/array"], function(dojo, dojox){ + + dojo.getObject("json", true, dojox); + + dojox.json._slice = function(obj,start,end,step){ + // handles slice operations: [3:6:2] + var len=obj.length,results = []; + end = end || len; + start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start); + end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end); + for(var i=start; i<end; i+=step){ + results.push(obj[i]); + } + return results; + } + dojox.json._find = function e(obj,name){ + // handles ..name, .*, [*], [val1,val2], [val] + // name can be a property to search for, undefined for full recursive, or an array for picking by index + var results = []; + function walk(obj){ + if(name){ + if(name===true && !(obj instanceof Array)){ + //recursive object search + results.push(obj); + }else if(obj[name]){ + // found the name, add to our results + results.push(obj[name]); + } + } + for(var i in obj){ + var val = obj[i]; + if(!name){ + // if we don't have a name we are just getting all the properties values (.* or [*]) + results.push(val); + }else if(val && typeof val == 'object'){ + + walk(val); + } + } + } + if(name instanceof Array){ + // this is called when multiple items are in the brackets: [3,4,5] + if(name.length==1){ + // this can happen as a result of the parser becoming confused about commas + // in the brackets like [@.func(4,2)]. Fixing the parser would require recursive + // analsys, very expensive, but this fixes the problem nicely. + return obj[name[0]]; + } + for(var i = 0; i < name.length; i++){ + results.push(obj[name[i]]); + } + }else{ + // otherwise we expanding + walk(obj); + } + return results; + } + + dojox.json._distinctFilter = function(array, callback){ + // does the filter with removal of duplicates in O(n) + var outArr = []; + var primitives = {}; + for(var i=0,l=array.length; i<l; ++i){ + var value = array[i]; + if(callback(value, i, array)){ + if((typeof value == 'object') && value){ + // with objects we prevent duplicates with a marker property + if(!value.__included){ + value.__included = true; + outArr.push(value); + } + }else if(!primitives[value + typeof value]){ + // with primitives we prevent duplicates by putting it in a map + primitives[value + typeof value] = true; + outArr.push(value); + } + } + } + for(i=0,l=outArr.length; i<l; ++i){ + // cleanup the marker properties + if(outArr[i]){ + delete outArr[i].__included; + } + } + return outArr; + } + return dojox.json.query = function(/*String*/query,/*Object?*/obj){ + // summary: + // Performs a JSONQuery on the provided object and returns the results. + // If no object is provided (just a query), it returns a "compiled" function that evaluates objects + // according to the provided query. + // query: + // Query string + // obj: + // Target of the JSONQuery + // + // description: + // JSONQuery provides a comprehensive set of data querying tools including filtering, + // recursive search, sorting, mapping, range selection, and powerful expressions with + // wildcard string comparisons and various operators. JSONQuery generally supersets + // JSONPath and provides syntax that matches and behaves like JavaScript where + // possible. + // + // JSONQuery evaluations begin with the provided object, which can referenced with + // $. From + // the starting object, various operators can be successively applied, each operating + // on the result of the last operation. + // + // Supported Operators: + // -------------------- + // * .property - This will return the provided property of the object, behaving exactly + // like JavaScript. + // * [expression] - This returns the property name/index defined by the evaluation of + // the provided expression, behaving exactly like JavaScript. + // * [?expression] - This will perform a filter operation on an array, returning all the + // items in an array that match the provided expression. This operator does not + // need to be in brackets, you can simply use ?expression, but since it does not + // have any containment, no operators can be used afterwards when used + // without brackets. + // * [^?expression] - This will perform a distinct filter operation on an array. This behaves + // as [?expression] except that it will remove any duplicate values/objects from the + // result set. + // * [/expression], [\expression], [/expression, /expression] - This performs a sort + // operation on an array, with sort based on the provide expression. Multiple comma delimited sort + // expressions can be provided for multiple sort orders (first being highest priority). / + // indicates ascending order and \ indicates descending order + // * [=expression] - This performs a map operation on an array, creating a new array + // with each item being the evaluation of the expression for each item in the source array. + // * [start:end:step] - This performs an array slice/range operation, returning the elements + // from the optional start index to the optional end index, stepping by the optional step number. + // * [expr,expr] - This a union operator, returning an array of all the property/index values from + // the evaluation of the comma delimited expressions. + // * .* or [*] - This returns the values of all the properties of the current object. + // * $ - This is the root object, If a JSONQuery expression does not being with a $, + // it will be auto-inserted at the beginning. + // * @ - This is the current object in filter, sort, and map expressions. This is generally + // not necessary, names are auto-converted to property references of the current object + // in expressions. + // * ..property - Performs a recursive search for the given property name, returning + // an array of all values with such a property name in the current object and any subobjects + // * expr = expr - Performs a comparison (like JS's ==). When comparing to + // a string, the comparison string may contain wildcards * (matches any number of + // characters) and ? (matches any single character). + // * expr ~ expr - Performs a string comparison with case insensitivity. + // * ..[?expression] - This will perform a deep search filter operation on all the objects and + // subobjects of the current data. Rather than only searching an array, this will search + // property values, arrays, and their children. + // * $1,$2,$3, etc. - These are references to extra parameters passed to the query + // function or the evaluator function. + // * +, -, /, *, &, |, %, (, ), <, >, <=, >=, != - These operators behave just as they do + // in JavaScript. + // + // + // + // | dojox.json.query(queryString,object) + // and + // | dojox.json.query(queryString)(object) + // always return identical results. The first one immediately evaluates, the second one returns a + // function that then evaluates the object. + // + // example: + // | dojox.json.query("foo",{foo:"bar"}) + // This will return "bar". + // + // example: + // | evaluator = dojox.json.query("?foo='bar'&rating>3"); + // This creates a function that finds all the objects in an array with a property + // foo that is equals to "bar" and with a rating property with a value greater + // than 3. + // | evaluator([{foo:"bar",rating:4},{foo:"baz",rating:2}]) + // This returns: + // | {foo:"bar",rating:4} + // + // example: + // | evaluator = dojox.json.query("$[?price<15.00][\rating][0:10]"); + // This finds objects in array with a price less than 15.00 and sorts then + // by rating, highest rated first, and returns the first ten items in from this + // filtered and sorted list. + var depth = 0; + var str = []; + query = query.replace(/"(\\.|[^"\\])*"|'(\\.|[^'\\])*'|[\[\]]/g,function(t){ + depth += t == '[' ? 1 : t == ']' ? -1 : 0; // keep track of bracket depth + return (t == ']' && depth > 0) ? '`]' : // we mark all the inner brackets as skippable + (t.charAt(0) == '"' || t.charAt(0) == "'") ? "`" + (str.push(t) - 1) :// and replace all the strings + t; + }); + var prefix = ''; + function call(name){ + // creates a function call and puts the expression so far in a parameter for a call + prefix = name + "(" + prefix; + } + function makeRegex(t,a,b,c,d,e,f,g){ + // creates a regular expression matcher for when wildcards and ignore case is used + return str[g].match(/[\*\?]/) || f == '~' ? + "/^" + str[g].substring(1,str[g].length-1).replace(/\\([btnfr\\"'])|([^\w\*\?])/g,"\\$1$2").replace(/([\*\?])/g,"[\\w\\W]$1") + (f == '~' ? '$/i' : '$/') + ".test(" + a + ")" : + t; + } + query.replace(/(\]|\)|push|pop|shift|splice|sort|reverse)\s*\(/,function(){ + throw new Error("Unsafe function call"); + }); + + query = query.replace(/([^<>=]=)([^=])/g,"$1=$2"). // change the equals to comparisons except operators ==, <=, >= + replace(/@|(\.\s*)?[a-zA-Z\$_]+(\s*:)?/g,function(t){ + return t.charAt(0) == '.' ? t : // leave .prop alone + t == '@' ? "$obj" :// the reference to the current object + (t.match(/:|^(\$|Math|true|false|null)$/) ? "" : "$obj.") + t; // plain names should be properties of root... unless they are a label in object initializer + }). + replace(/\.?\.?\[(`\]|[^\]])*\]|\?.*|\.\.([\w\$_]+)|\.\*/g,function(t,a,b){ + var oper = t.match(/^\.?\.?(\[\s*\^?\?|\^?\?|\[\s*==)(.*?)\]?$/); // [?expr] and ?expr and [=expr and =expr + if(oper){ + var prefix = ''; + if(t.match(/^\./)){ + // recursive object search + call("dojox.json._find"); + prefix = ",true)"; + } + call(oper[1].match(/\=/) ? "dojo.map" : oper[1].match(/\^/) ? "dojox.json._distinctFilter" : "dojo.filter"); + return prefix + ",function($obj){return " + oper[2] + "})"; + } + oper = t.match(/^\[\s*([\/\\].*)\]/); // [/sortexpr,\sortexpr] + if(oper){ + // make a copy of the array and then sort it using the sorting expression + return ".concat().sort(function(a,b){" + oper[1].replace(/\s*,?\s*([\/\\])\s*([^,\\\/]+)/g,function(t,a,b){ + return "var av= " + b.replace(/\$obj/,"a") + ",bv= " + b.replace(/\$obj/,"b") + // FIXME: Should check to make sure the $obj token isn't followed by characters + ";if(av>bv||bv==null){return " + (a== "/" ? 1 : -1) +";}\n" + + "if(bv>av||av==null){return " + (a== "/" ? -1 : 1) +";}\n"; + }) + "return 0;})"; + } + oper = t.match(/^\[(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)\]/); // slice [0:3] + if(oper){ + call("dojox.json._slice"); + return "," + (oper[1] || 0) + "," + (oper[2] || 0) + "," + (oper[3] || 1) + ")"; + } + if(t.match(/^\.\.|\.\*|\[\s*\*\s*\]|,/)){ // ..prop and [*] + call("dojox.json._find"); + return (t.charAt(1) == '.' ? + ",'" + b + "'" : // ..prop + t.match(/,/) ? + "," + t : // [prop1,prop2] + "") + ")"; // [*] + } + return t; + }). + replace(/(\$obj\s*((\.\s*[\w_$]+\s*)|(\[\s*`([0-9]+)\s*`\]))*)(==|~)\s*`([0-9]+)/g,makeRegex). // create regex matching + replace(/`([0-9]+)\s*(==|~)\s*(\$obj\s*((\.\s*[\w_$]+)|(\[\s*`([0-9]+)\s*`\]))*)/g,function(t,a,b,c,d,e,f,g){ // and do it for reverse = + return makeRegex(t,c,d,e,f,g,b,a); + }); + query = prefix + (query.charAt(0) == '$' ? "" : "$") + query.replace(/`([0-9]+|\])/g,function(t,a){ + //restore the strings + return a == ']' ? ']' : str[a]; + }); + // create a function within this scope (so it can use expand and slice) + + var executor = eval("1&&function($,$1,$2,$3,$4,$5,$6,$7,$8,$9){var $obj=$;return " + query + "}"); + for(var i = 0;i<arguments.length-1;i++){ + arguments[i] = arguments[i+1]; + } + return obj ? executor.apply(this,arguments) : executor; + }; + +});
\ No newline at end of file |
