1 | define(["dojo/_base/kernel", "dojox", "dojo/_base/array"], function(dojo, dojox){ |
---|
2 | |
---|
3 | dojo.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 | // |
---|
97 | // description: |
---|
98 | // JSONQuery provides a comprehensive set of data querying tools including filtering, |
---|
99 | // recursive search, sorting, mapping, range selection, and powerful expressions with |
---|
100 | // wildcard string comparisons and various operators. JSONQuery generally supersets |
---|
101 | // JSONPath and provides syntax that matches and behaves like JavaScript where |
---|
102 | // possible. |
---|
103 | // |
---|
104 | // JSONQuery evaluations begin with the provided object, which can referenced with |
---|
105 | // $. From |
---|
106 | // the starting object, various operators can be successively applied, each operating |
---|
107 | // on the result of the last operation. |
---|
108 | // |
---|
109 | // Supported Operators: |
---|
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 | // |
---|
154 | // |
---|
155 | // | dojox.json.query(queryString,object) |
---|
156 | // and |
---|
157 | // | dojox.json.query(queryString)(object) |
---|
158 | // always return identical results. The first one immediately evaluates, the second one returns a |
---|
159 | // function that then evaluates the object. |
---|
160 | // |
---|
161 | // example: |
---|
162 | // | dojox.json.query("foo",{foo:"bar"}) |
---|
163 | // This will return "bar". |
---|
164 | // |
---|
165 | // example: |
---|
166 | // | evaluator = dojox.json.query("?foo='bar'&rating>3"); |
---|
167 | // This creates a function that finds all the objects in an array with a property |
---|
168 | // foo that is equals to "bar" and with a rating property with a value greater |
---|
169 | // than 3. |
---|
170 | // | evaluator([{foo:"bar",rating:4},{foo:"baz",rating:2}]) |
---|
171 | // This returns: |
---|
172 | // | {foo:"bar",rating:4} |
---|
173 | // |
---|
174 | // example: |
---|
175 | // | evaluator = dojox.json.query("$[?price<15.00][\rating][0:10]"); |
---|
176 | // This finds objects in array with a price less than 15.00 and sorts then |
---|
177 | // by rating, highest rated first, and returns the first ten items in from this |
---|
178 | // filtered and sorted list. |
---|
179 | var depth = 0; |
---|
180 | var str = []; |
---|
181 | query = query.replace(/"(\\.|[^"\\])*"|'(\\.|[^'\\])*'|[\[\]]/g,function(t){ |
---|
182 | depth += t == '[' ? 1 : t == ']' ? -1 : 0; // keep track of bracket depth |
---|
183 | return (t == ']' && depth > 0) ? '`]' : // we mark all the inner brackets as skippable |
---|
184 | (t.charAt(0) == '"' || t.charAt(0) == "'") ? "`" + (str.push(t) - 1) :// and replace all the strings |
---|
185 | t; |
---|
186 | }); |
---|
187 | var prefix = ''; |
---|
188 | function call(name){ |
---|
189 | // creates a function call and puts the expression so far in a parameter for a call |
---|
190 | prefix = name + "(" + prefix; |
---|
191 | } |
---|
192 | function makeRegex(t,a,b,c,d,e,f,g){ |
---|
193 | // creates a regular expression matcher for when wildcards and ignore case is used |
---|
194 | return str[g].match(/[\*\?]/) || f == '~' ? |
---|
195 | "/^" + str[g].substring(1,str[g].length-1).replace(/\\([btnfr\\"'])|([^\w\*\?])/g,"\\$1$2").replace(/([\*\?])/g,"[\\w\\W]$1") + (f == '~' ? '$/i' : '$/') + ".test(" + a + ")" : |
---|
196 | t; |
---|
197 | } |
---|
198 | query.replace(/(\]|\)|push|pop|shift|splice|sort|reverse)\s*\(/,function(){ |
---|
199 | throw new Error("Unsafe function call"); |
---|
200 | }); |
---|
201 | |
---|
202 | query = query.replace(/([^<>=]=)([^=])/g,"$1=$2"). // change the equals to comparisons except operators ==, <=, >= |
---|
203 | replace(/@|(\.\s*)?[a-zA-Z\$_]+(\s*:)?/g,function(t){ |
---|
204 | return t.charAt(0) == '.' ? t : // leave .prop alone |
---|
205 | t == '@' ? "$obj" :// the reference to the current object |
---|
206 | (t.match(/:|^(\$|Math|true|false|null)$/) ? "" : "$obj.") + t; // plain names should be properties of root... unless they are a label in object initializer |
---|
207 | }). |
---|
208 | replace(/\.?\.?\[(`\]|[^\]])*\]|\?.*|\.\.([\w\$_]+)|\.\*/g,function(t,a,b){ |
---|
209 | var oper = t.match(/^\.?\.?(\[\s*\^?\?|\^?\?|\[\s*==)(.*?)\]?$/); // [?expr] and ?expr and [=expr and =expr |
---|
210 | if(oper){ |
---|
211 | var prefix = ''; |
---|
212 | if(t.match(/^\./)){ |
---|
213 | // recursive object search |
---|
214 | call("dojox.json._find"); |
---|
215 | prefix = ",true)"; |
---|
216 | } |
---|
217 | call(oper[1].match(/\=/) ? "dojo.map" : oper[1].match(/\^/) ? "dojox.json._distinctFilter" : "dojo.filter"); |
---|
218 | return prefix + ",function($obj){return " + oper[2] + "})"; |
---|
219 | } |
---|
220 | oper = t.match(/^\[\s*([\/\\].*)\]/); // [/sortexpr,\sortexpr] |
---|
221 | if(oper){ |
---|
222 | // make a copy of the array and then sort it using the sorting expression |
---|
223 | return ".concat().sort(function(a,b){" + oper[1].replace(/\s*,?\s*([\/\\])\s*([^,\\\/]+)/g,function(t,a,b){ |
---|
224 | 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 |
---|
225 | ";if(av>bv||bv==null){return " + (a== "/" ? 1 : -1) +";}\n" + |
---|
226 | "if(bv>av||av==null){return " + (a== "/" ? -1 : 1) +";}\n"; |
---|
227 | }) + "return 0;})"; |
---|
228 | } |
---|
229 | oper = t.match(/^\[(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)\]/); // slice [0:3] |
---|
230 | if(oper){ |
---|
231 | call("dojox.json._slice"); |
---|
232 | return "," + (oper[1] || 0) + "," + (oper[2] || 0) + "," + (oper[3] || 1) + ")"; |
---|
233 | } |
---|
234 | if(t.match(/^\.\.|\.\*|\[\s*\*\s*\]|,/)){ // ..prop and [*] |
---|
235 | call("dojox.json._find"); |
---|
236 | return (t.charAt(1) == '.' ? |
---|
237 | ",'" + b + "'" : // ..prop |
---|
238 | t.match(/,/) ? |
---|
239 | "," + t : // [prop1,prop2] |
---|
240 | "") + ")"; // [*] |
---|
241 | } |
---|
242 | return t; |
---|
243 | }). |
---|
244 | replace(/(\$obj\s*((\.\s*[\w_$]+\s*)|(\[\s*`([0-9]+)\s*`\]))*)(==|~)\s*`([0-9]+)/g,makeRegex). // create regex matching |
---|
245 | 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 = |
---|
246 | return makeRegex(t,c,d,e,f,g,b,a); |
---|
247 | }); |
---|
248 | query = prefix + (query.charAt(0) == '$' ? "" : "$") + query.replace(/`([0-9]+|\])/g,function(t,a){ |
---|
249 | //restore the strings |
---|
250 | return a == ']' ? ']' : str[a]; |
---|
251 | }); |
---|
252 | // create a function within this scope (so it can use expand and slice) |
---|
253 | |
---|
254 | var executor = eval("1&&function($,$1,$2,$3,$4,$5,$6,$7,$8,$9){var $obj=$;return " + query + "}"); |
---|
255 | for(var i = 0;i<arguments.length-1;i++){ |
---|
256 | arguments[i] = arguments[i+1]; |
---|
257 | } |
---|
258 | return obj ? executor.apply(this,arguments) : executor; |
---|
259 | }; |
---|
260 | |
---|
261 | }); |
---|