source: Dev/branches/rest-dojo-ui/client/dojox/json/query.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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