source: Dev/trunk/src/client/dojox/json/query.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 11.4 KB
Line 
1define(["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});
Note: See TracBrowser for help on using the repository browser.