[483] | 1 | define(["dojo/_base/kernel", "dojo/_base/lang", "dojox", "dojo/_base/array"], function(dojo, lang, dojox){ |
---|
| 2 | |
---|
| 3 | lang.getObject("json", true, dojox); |
---|
| 4 | |
---|
| 5 | dojox.json._slice = function(obj,start,end,step){ |
---|
| 6 | // handles slice operations: [3:6:2] |
---|
| 7 | var len=obj.length,results = []; |
---|
| 8 | end = end || len; |
---|
| 9 | start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start); |
---|
| 10 | end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end); |
---|
| 11 | for(var i=start; i<end; i+=step){ |
---|
| 12 | results.push(obj[i]); |
---|
| 13 | } |
---|
| 14 | return results; |
---|
| 15 | }; |
---|
| 16 | dojox.json._find = function e(obj,name){ |
---|
| 17 | // handles ..name, .*, [*], [val1,val2], [val] |
---|
| 18 | // name can be a property to search for, undefined for full recursive, or an array for picking by index |
---|
| 19 | var results = []; |
---|
| 20 | function walk(obj){ |
---|
| 21 | if(name){ |
---|
| 22 | if(name===true && !(obj instanceof Array)){ |
---|
| 23 | //recursive object search |
---|
| 24 | results.push(obj); |
---|
| 25 | }else if(obj[name]){ |
---|
| 26 | // found the name, add to our results |
---|
| 27 | results.push(obj[name]); |
---|
| 28 | } |
---|
| 29 | } |
---|
| 30 | for(var i in obj){ |
---|
| 31 | var val = obj[i]; |
---|
| 32 | if(!name){ |
---|
| 33 | // if we don't have a name we are just getting all the properties values (.* or [*]) |
---|
| 34 | results.push(val); |
---|
| 35 | }else if(val && typeof val == 'object'){ |
---|
| 36 | |
---|
| 37 | walk(val); |
---|
| 38 | } |
---|
| 39 | } |
---|
| 40 | } |
---|
| 41 | if(name instanceof Array){ |
---|
| 42 | // this is called when multiple items are in the brackets: [3,4,5] |
---|
| 43 | if(name.length==1){ |
---|
| 44 | // this can happen as a result of the parser becoming confused about commas |
---|
| 45 | // in the brackets like [@.func(4,2)]. Fixing the parser would require recursive |
---|
| 46 | // analsys, very expensive, but this fixes the problem nicely. |
---|
| 47 | return obj[name[0]]; |
---|
| 48 | } |
---|
| 49 | for(var i = 0; i < name.length; i++){ |
---|
| 50 | results.push(obj[name[i]]); |
---|
| 51 | } |
---|
| 52 | }else{ |
---|
| 53 | // otherwise we expanding |
---|
| 54 | walk(obj); |
---|
| 55 | } |
---|
| 56 | return results; |
---|
| 57 | }; |
---|
| 58 | |
---|
| 59 | dojox.json._distinctFilter = function(array, callback){ |
---|
| 60 | // does the filter with removal of duplicates in O(n) |
---|
| 61 | var outArr = []; |
---|
| 62 | var primitives = {}; |
---|
| 63 | for(var i=0,l=array.length; i<l; ++i){ |
---|
| 64 | var value = array[i]; |
---|
| 65 | if(callback(value, i, array)){ |
---|
| 66 | if((typeof value == 'object') && value){ |
---|
| 67 | // with objects we prevent duplicates with a marker property |
---|
| 68 | if(!value.__included){ |
---|
| 69 | value.__included = true; |
---|
| 70 | outArr.push(value); |
---|
| 71 | } |
---|
| 72 | }else if(!primitives[value + typeof value]){ |
---|
| 73 | // with primitives we prevent duplicates by putting it in a map |
---|
| 74 | primitives[value + typeof value] = true; |
---|
| 75 | outArr.push(value); |
---|
| 76 | } |
---|
| 77 | } |
---|
| 78 | } |
---|
| 79 | for(i=0,l=outArr.length; i<l; ++i){ |
---|
| 80 | // cleanup the marker properties |
---|
| 81 | if(outArr[i]){ |
---|
| 82 | delete outArr[i].__included; |
---|
| 83 | } |
---|
| 84 | } |
---|
| 85 | return outArr; |
---|
| 86 | }; |
---|
| 87 | return dojox.json.query = function(/*String*/query,/*Object?*/obj){ |
---|
| 88 | // summary: |
---|
| 89 | // Performs a JSONQuery on the provided object and returns the results. |
---|
| 90 | // If no object is provided (just a query), it returns a "compiled" function that evaluates objects |
---|
| 91 | // according to the provided query. |
---|
| 92 | // query: |
---|
| 93 | // Query string |
---|
| 94 | // obj: |
---|
| 95 | // Target of the JSONQuery |
---|
| 96 | // description: |
---|
| 97 | // JSONQuery provides a comprehensive set of data querying tools including filtering, |
---|
| 98 | // recursive search, sorting, mapping, range selection, and powerful expressions with |
---|
| 99 | // wildcard string comparisons and various operators. JSONQuery generally supersets |
---|
| 100 | // JSONPath and provides syntax that matches and behaves like JavaScript where |
---|
| 101 | // possible. |
---|
| 102 | // |
---|
| 103 | // JSONQuery evaluations begin with the provided object, which can referenced with |
---|
| 104 | // $. From |
---|
| 105 | // the starting object, various operators can be successively applied, each operating |
---|
| 106 | // on the result of the last operation. |
---|
| 107 | // |
---|
| 108 | // Supported Operators |
---|
| 109 | // -------------------- |
---|
| 110 | // |
---|
| 111 | // - .property - This will return the provided property of the object, behaving exactly |
---|
| 112 | // like JavaScript. |
---|
| 113 | // - [expression] - This returns the property name/index defined by the evaluation of |
---|
| 114 | // the provided expression, behaving exactly like JavaScript. |
---|
| 115 | // - [?expression] - This will perform a filter operation on an array, returning all the |
---|
| 116 | // items in an array that match the provided expression. This operator does not |
---|
| 117 | // need to be in brackets, you can simply use ?expression, but since it does not |
---|
| 118 | // have any containment, no operators can be used afterwards when used |
---|
| 119 | // without brackets. |
---|
| 120 | // - [^?expression] - This will perform a distinct filter operation on an array. This behaves |
---|
| 121 | // as [?expression] except that it will remove any duplicate values/objects from the |
---|
| 122 | // result set. |
---|
| 123 | // - [/expression], [\expression], [/expression, /expression] - This performs a sort |
---|
| 124 | // operation on an array, with sort based on the provide expression. Multiple comma delimited sort |
---|
| 125 | // expressions can be provided for multiple sort orders (first being highest priority). / |
---|
| 126 | // indicates ascending order and \ indicates descending order |
---|
| 127 | // - [=expression] - This performs a map operation on an array, creating a new array |
---|
| 128 | // with each item being the evaluation of the expression for each item in the source array. |
---|
| 129 | // - [start:end:step] - This performs an array slice/range operation, returning the elements |
---|
| 130 | // from the optional start index to the optional end index, stepping by the optional step number. |
---|
| 131 | // - [expr,expr] - This a union operator, returning an array of all the property/index values from |
---|
| 132 | // the evaluation of the comma delimited expressions. |
---|
| 133 | // - .* or [*] - This returns the values of all the properties of the current object. |
---|
| 134 | // - $ - This is the root object, If a JSONQuery expression does not being with a $, |
---|
| 135 | // it will be auto-inserted at the beginning. |
---|
| 136 | // - @ - This is the current object in filter, sort, and map expressions. This is generally |
---|
| 137 | // not necessary, names are auto-converted to property references of the current object |
---|
| 138 | // in expressions. |
---|
| 139 | // - ..property - Performs a recursive search for the given property name, returning |
---|
| 140 | // an array of all values with such a property name in the current object and any subobjects |
---|
| 141 | // - expr = expr - Performs a comparison (like JS's ==). When comparing to |
---|
| 142 | // a string, the comparison string may contain wildcards * (matches any number of |
---|
| 143 | // characters) and ? (matches any single character). |
---|
| 144 | // - expr ~ expr - Performs a string comparison with case insensitivity. |
---|
| 145 | // - ..[?expression] - This will perform a deep search filter operation on all the objects and |
---|
| 146 | // subobjects of the current data. Rather than only searching an array, this will search |
---|
| 147 | // property values, arrays, and their children. |
---|
| 148 | // - $1,$2,$3, etc. - These are references to extra parameters passed to the query |
---|
| 149 | // function or the evaluator function. |
---|
| 150 | // - +, -, /, *, &, |, %, (, ), <, >, <=, >=, != - These operators behave just as they do |
---|
| 151 | // in JavaScript. |
---|
| 152 | // |
---|
| 153 | // | dojox.json.query(queryString,object) |
---|
| 154 | // and |
---|
| 155 | // | dojox.json.query(queryString)(object) |
---|
| 156 | // always return identical results. The first one immediately evaluates, the second one returns a |
---|
| 157 | // function that then evaluates the object. |
---|
| 158 | // |
---|
| 159 | // example: |
---|
| 160 | // | dojox.json.query("foo",{foo:"bar"}) |
---|
| 161 | // This will return "bar". |
---|
| 162 | // |
---|
| 163 | // example: |
---|
| 164 | // | evaluator = dojox.json.query("?foo='bar'&rating>3"); |
---|
| 165 | // This creates a function that finds all the objects in an array with a property |
---|
| 166 | // foo that is equals to "bar" and with a rating property with a value greater |
---|
| 167 | // than 3. |
---|
| 168 | // | evaluator([{foo:"bar",rating:4},{foo:"baz",rating:2}]) |
---|
| 169 | // This returns: |
---|
| 170 | // | {foo:"bar",rating:4} |
---|
| 171 | // |
---|
| 172 | // example: |
---|
| 173 | // | evaluator = dojox.json.query("$[?price<15.00][\rating][0:10]"); |
---|
| 174 | // This finds objects in array with a price less than 15.00 and sorts then |
---|
| 175 | // by rating, highest rated first, and returns the first ten items in from this |
---|
| 176 | // filtered and sorted list. |
---|
| 177 | var depth = 0; |
---|
| 178 | var str = []; |
---|
| 179 | query = query.replace(/"(\\.|[^"\\])*"|'(\\.|[^'\\])*'|[\[\]]/g,function(t){ |
---|
| 180 | depth += t == '[' ? 1 : t == ']' ? -1 : 0; // keep track of bracket depth |
---|
| 181 | return (t == ']' && depth > 0) ? '`]' : // we mark all the inner brackets as skippable |
---|
| 182 | (t.charAt(0) == '"' || t.charAt(0) == "'") ? "`" + (str.push(t) - 1) :// and replace all the strings |
---|
| 183 | t; |
---|
| 184 | }); |
---|
| 185 | var prefix = ''; |
---|
| 186 | function call(name){ |
---|
| 187 | // creates a function call and puts the expression so far in a parameter for a call |
---|
| 188 | prefix = name + "(" + prefix; |
---|
| 189 | } |
---|
| 190 | function makeRegex(t,a,b,c,d,e,f,g){ |
---|
| 191 | // creates a regular expression matcher for when wildcards and ignore case is used |
---|
| 192 | return str[g].match(/[\*\?]/) || f == '~' ? |
---|
| 193 | "/^" + str[g].substring(1,str[g].length-1).replace(/\\([btnfr\\"'])|([^\w\*\?])/g,"\\$1$2").replace(/([\*\?])/g,"[\\w\\W]$1") + (f == '~' ? '$/i' : '$/') + ".test(" + a + ")" : |
---|
| 194 | t; |
---|
| 195 | } |
---|
| 196 | query.replace(/(\]|\)|push|pop|shift|splice|sort|reverse)\s*\(/,function(){ |
---|
| 197 | throw new Error("Unsafe function call"); |
---|
| 198 | }); |
---|
| 199 | |
---|
| 200 | query = query.replace(/([^<>=]=)([^=])/g,"$1=$2"). // change the equals to comparisons except operators ==, <=, >= |
---|
| 201 | replace(/@|(\.\s*)?[a-zA-Z\$_]+(\s*:)?/g,function(t){ |
---|
| 202 | return t.charAt(0) == '.' ? t : // leave .prop alone |
---|
| 203 | t == '@' ? "$obj" :// the reference to the current object |
---|
| 204 | (t.match(/:|^(\$|Math|true|false|null)$/) ? "" : "$obj.") + t; // plain names should be properties of root... unless they are a label in object initializer |
---|
| 205 | }). |
---|
| 206 | replace(/\.?\.?\[(`\]|[^\]])*\]|\?.*|\.\.([\w\$_]+)|\.\*/g,function(t,a,b){ |
---|
| 207 | var oper = t.match(/^\.?\.?(\[\s*\^?\?|\^?\?|\[\s*==)(.*?)\]?$/); // [?expr] and ?expr and [=expr and =expr |
---|
| 208 | if(oper){ |
---|
| 209 | var prefix = ''; |
---|
| 210 | if(t.match(/^\./)){ |
---|
| 211 | // recursive object search |
---|
| 212 | call("dojox.json._find"); |
---|
| 213 | prefix = ",true)"; |
---|
| 214 | } |
---|
| 215 | call(oper[1].match(/\=/) ? "dojo.map" : oper[1].match(/\^/) ? "dojox.json._distinctFilter" : "dojo.filter"); |
---|
| 216 | return prefix + ",function($obj){return " + oper[2] + "})"; |
---|
| 217 | } |
---|
| 218 | oper = t.match(/^\[\s*([\/\\].*)\]/); // [/sortexpr,\sortexpr] |
---|
| 219 | if(oper){ |
---|
| 220 | // make a copy of the array and then sort it using the sorting expression |
---|
| 221 | return ".concat().sort(function(a,b){" + oper[1].replace(/\s*,?\s*([\/\\])\s*([^,\\\/]+)/g,function(t,a,b){ |
---|
| 222 | 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 |
---|
| 223 | ";if(av>bv||bv==null){return " + (a== "/" ? 1 : -1) +";}\n" + |
---|
| 224 | "if(bv>av||av==null){return " + (a== "/" ? -1 : 1) +";}\n"; |
---|
| 225 | }) + "return 0;})"; |
---|
| 226 | } |
---|
| 227 | oper = t.match(/^\[(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)\]/); // slice [0:3] |
---|
| 228 | if(oper){ |
---|
| 229 | call("dojox.json._slice"); |
---|
| 230 | return "," + (oper[1] || 0) + "," + (oper[2] || 0) + "," + (oper[3] || 1) + ")"; |
---|
| 231 | } |
---|
| 232 | if(t.match(/^\.\.|\.\*|\[\s*\*\s*\]|,/)){ // ..prop and [*] |
---|
| 233 | call("dojox.json._find"); |
---|
| 234 | return (t.charAt(1) == '.' ? |
---|
| 235 | ",'" + b + "'" : // ..prop |
---|
| 236 | t.match(/,/) ? |
---|
| 237 | "," + t : // [prop1,prop2] |
---|
| 238 | "") + ")"; // [*] |
---|
| 239 | } |
---|
| 240 | return t; |
---|
| 241 | }). |
---|
| 242 | replace(/(\$obj\s*((\.\s*[\w_$]+\s*)|(\[\s*`([0-9]+)\s*`\]))*)(==|~)\s*`([0-9]+)/g,makeRegex). // create regex matching |
---|
| 243 | 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 = |
---|
| 244 | return makeRegex(t,c,d,e,f,g,b,a); |
---|
| 245 | }); |
---|
| 246 | query = prefix + (query.charAt(0) == '$' ? "" : "$") + query.replace(/`([0-9]+|\])/g,function(t,a){ |
---|
| 247 | //restore the strings |
---|
| 248 | return a == ']' ? ']' : str[a]; |
---|
| 249 | }); |
---|
| 250 | // create a function within this scope (so it can use expand and slice) |
---|
| 251 | |
---|
| 252 | var executor = eval("1&&function($,$1,$2,$3,$4,$5,$6,$7,$8,$9){var $obj=$;return " + query + "}"); |
---|
| 253 | for(var i = 0;i<arguments.length-1;i++){ |
---|
| 254 | arguments[i] = arguments[i+1]; |
---|
| 255 | } |
---|
| 256 | return obj ? executor.apply(this,arguments) : executor; |
---|
| 257 | }; |
---|
| 258 | |
---|
| 259 | }); |
---|