Removed redundant whitespace :).
[cql-js-moved-to-github.git] / cql.js
1 function indent(n) {
2     var s = "";
3     for (var i = 0; i < n; i++)
4         s = s + " ";
5     return s;
6 }
7 // CQLModifier
8 var CQLModifier = function () {
9     this.name = null;
10     this.relation = null;
11     this.value = null;
12 }
13
14 CQLModifier.prototype = {
15     toXCQL: function (n) {
16         var s = indent(n+1) + "<modifier>\n";
17         s = s + indent(n+2) + "<name>" + this.name + "</name>\n";
18         if (this.relation != null)
19             s = s + indent(n+2) 
20                 + "<relation>" + this.relation + "</relation>\n";
21         if (this.value != null)
22             s = s + indent(n+2) 
23                 + "<value>" + this.value +"</value>\n";
24         s = s + indent(n+1) + "</modifier>\n";
25         return s;
26     }
27 }
28 // CQLSearchClause
29 var CQLSearchClause = function (field, fielduri, relation, relationuri, 
30                                 modifiers, term) {
31     this.field = field;
32     this.fielduri = fielduri;
33     this.relation = relation;
34     this.relationuri = relationuri;
35     this.modifiers = modifiers;
36     this.term = term;
37 }
38
39 CQLSearchClause.prototype = {
40     toXCQL: function (n) {
41         var s = indent(n) + "<searchClause>\n";
42         if (this.fielduri.length > 0)
43         {
44             s = s + indent(n+1) + "<prefixes>\n" +
45                 indent(n+2) + "<prefix>\n" +
46                 indent(n+3) + "<identifier>" + this.fielduri +
47                 "</identifier>\n" +
48                 indent(n+2) + "</prefix>\n" +
49                 indent(n+1) + "</prefixes>\n";
50         }
51         s = s + indent(n+1) + "<index>" + this.field + "</index>\n";
52         s = s + indent(n+1) + "<relation>\n";
53         if (this.relationuri.length > 0) {
54             s = s + indent(n+2) +
55                 "<identifier>" + this.relationuri + "</identifier>\n";
56         }
57         s = s + indent(n+2) + "<value>" + this.relation + "</value>\n";
58         if (this.modifiers.length > 0) {
59             s = s + indent(n+2) + "<modifiers>\n";
60             for (var i = 0; i < this.modifiers.length; i++)
61                 s = s + this.modifiers[i].toXCQL(n+2);
62             s = s + indent(n+2) + "</modifiers>\n";
63         }
64         s = s + indent(n+1) + "</relation>\n";
65         s = s + indent(n+1) + "<term>" + this.term + "</term>\n";
66         s = s + indent(n) + "</searchClause>\n";
67         return s;
68     }
69 }
70 // CQLBoolean
71 var CQLBoolean = function() {
72     this.op = null;
73     this.modifiers = null;
74     this.left = null;
75     this.right = null;
76 }
77
78 CQLBoolean.prototype = {
79     toXCQL: function (n) {
80         var s = indent(n) + "<triple>\n";
81         s = s + indent(n+1) + "<boolean>\n" +
82             indent(n+2) + "<value>" + this.op + "</value>\n";
83         if (this.modifiers.length > 0) {
84             s = s + indent(n+2) + "<modifiers>\n";
85             for (var i = 0; i < this.modifiers.length; i++)
86                 s = s + this.modifiers[i].toXCQL(n+2);
87             s = s + indent(n+2) + "</modifiers>\n";
88         }
89         s = s + indent(n+1) + "</boolean>\n";
90         s = s + indent(n+1) + "<leftOperand>\n" +
91             this.left.toXCQL(n+2) + indent(n+1) + "</leftOperand>\n";
92
93         s = s + indent(n+1) + "<rightOperand>\n" +
94             this.right.toXCQL(n+2) + indent(n+1) + "</rightOperand>\n";
95         s = s + indent(n) + "</triple>\n";
96         return s;
97     }
98 }
99 // CQLParser
100 var CQLParser = function () {
101     this.qi = null;
102     this.ql = null;
103     this.qs = null;
104     this.look = null;
105     this.lval = null;
106     this.val = null;
107     this.prefixes = new Object();
108     this.tree = null;
109 }
110
111 CQLParser.prototype = {
112     parse: function (query) {
113         if (!query)
114             throw new Error("The query to be parsed cannot be empty");
115         
116         this.qs = query;
117         this.ql = this.qs.length;
118         this.qi = 0;
119         this._move(); 
120         this.tree = this._parseQuery("cql.serverChoice", "scr", new Array());
121         if (this.look != "")
122             throw new Error("EOF expected");
123     },
124     toXCQL: function () {
125         return this.tree.toXCQL();
126     },
127     _parseQuery: function(field, relation, modifiers) {
128         var left = this._parseSearchClause(field, relation, modifiers);
129         while (this.look == "s" && (
130                     this.lval == "and" ||
131                     this.lval == "or" ||
132                     this.lval == "not" ||
133                     this.lval == "prox")) {
134             var b = new CQLBoolean();
135             b.op = this.lval;
136             this._move();
137             b.modifiers = this._parseModifiers();
138             b.left = left;
139             b.right = this._parseSearchClause(field, relation, modifiers);
140             left = b;
141         }
142         return left;
143     },
144     _parseModifiers: function() {
145         var ar = new Array();
146         while (this.look == "/") {
147             this._move();
148             if (this.look != "s" && this.look != "q")
149                 throw new Error("Invalid modifier.")
150             
151             var name = this.lval;
152             this._move();
153             if (this.look.length > 0 
154                 && this._strchr("<>=", this.look.charAt(0))) {
155                 var rel = this.look;
156                 this._move();
157                 if (this.look != "s" && this.look != "q")
158                     throw new Error("Invalid relation within the modifier.");
159                 
160                 var m = new CQLModifier();
161                 m.name = name;
162                 m.relation = rel;
163                 m.value = this.val;
164                 ar.push(m);
165                 this._move();
166             } else {
167                 var m = new CQLModifier();
168                 m.name = name;
169                 m.relation = "";
170                 m.value = "";
171                 ar.push(m);
172             }
173         }
174         return ar;
175     },
176     _parseSearchClause: function(field, relation, modifiers) {
177         if (this.look == "(") {
178             this._move();
179             var b = this._parseQuery(field, relation, modifiers);
180             if (this.look == ")")
181                 this._move();
182             else
183                 throw new Error("Missing closing parenthesis.");
184
185             return b;
186         } else if (this.look == "s" || this.look == "q") {
187             var first = this.val;   // dont know if field or term yet
188             this._move();
189             if (this.look == "q" ||
190                     (this.look == "s" &&
191                      this.lval != "and" &&
192                      this.lval != "or" &&
193                      this.lval != "not" &&
194                      this.lval != "prox")) {
195                 var rel = this.val;    // string relation
196                 this._move();
197                 return this._parseSearchClause(first, rel,
198                                                this._parseModifiers());
199             } else if (this.look.length > 0 
200                        && this._strchr("<>=", this.look.charAt(0))) {
201                 var rel = this.look;   // other relation <, = ,etc
202                 this._move();
203                 return this._parseSearchClause(first, rel, 
204                                                this._parseModifiers());
205             } else {
206                 // it's a search term
207                 var pos = field.indexOf('.');
208                 var pre = "";
209                 if (pos != -1)
210                     pre = field.substring(0, pos);
211                 
212                 var uri = this._lookupPrefix(pre);
213                 if (uri.length > 0)
214                     field = field.substring(pos+1);
215                 
216                 pos = relation.indexOf('.');
217                 if (pos == -1)
218                     pre = "cql";
219                 else
220                     pre = relation.substring(0, pos);
221
222                 var reluri = this._lookupPrefix(pre);
223                 if (reluri.Length > 0)
224                     relation = relation.Substring(pos+1);
225
226                 var sc = new CQLSearchClause(field,
227                         uri,
228                         relation,
229                         reluri,
230                         modifiers,
231                         first);
232                 return sc;
233             }
234         // prefixes
235         } else if (this.look == ">") {
236             this._move();
237             if (this.look != "s" && this.look != "q")
238                 throw new Error("Expecting string or a quoted expression.");
239             
240             var first = this.lval;
241             this._move();
242             if (this.look == "=")
243             {
244                 this._move();
245                 if (this.look != "s" && this.look != "q")
246                     throw new Error("Expecting string or a quoted expression.");
247                 
248                 this._addPrefix(first, this.lval);
249                 this._move();
250                 return this._parseQuery(field, relation, modifiers);
251             } else {
252                 this._addPrefix("default", first);
253                 return this._parseQuery(field, relation, modifiers);
254             }
255         } else {
256             throw new Error("Invalid search clause.");
257         }
258
259     },
260     _move: function () {
261         while (this.qi < this.ql 
262                && this._strchr(" \t\r\n", this.qs.charAt(this.qi)))
263             this.qi++;
264         if (this.qi == this.ql) {
265             this.look = "";
266             return;
267         }
268         var c = this.qs.charAt(this.qi);
269         if (this._strchr("()/", c)) {
270             this.look = c;
271             this.qi++;
272         } else if (this._strchr("<>=", c)) {
273             this.look = c;
274             this.qi++;
275             while (this.qi < this.ql 
276                    && this._strchr("<>=", this.qs.charAt(this.qi))) {
277                 this.look = this.look + this.qs.charAt(this.qi);
278                 this.qi++;
279             }
280         } else if (this._strchr("\"'", c)) {
281             this.look = "q";
282             var mark = c;
283             this.qi++;
284             this.val = "";
285             while (this.qi < this.ql 
286                    && this.qs.charAt(this.qi) != mark) {
287                 if (this.qs.charAt(this.qi) == '\\' 
288                     && this.qi < this.ql-1)
289                     this.qi++;
290                 this.val = this.val + this.qs.charAt(this.qi);
291                 this.qi++;
292             }
293             this.lval = this.val.toLowerCase();
294             if (this.qi < this.ql)
295                 this.qi++;
296         } else {
297             this.look = "s";
298             this.val = "";
299             while (this.qi < this.ql 
300                    && !this._strchr("()/<>= \t\r\n", this.qs.charAt(this.qi))) {
301                 this.val = this.val + this.qs.charAt(this.qi);
302                 this.qi++;
303             }
304             this.lval = this.val.toLowerCase();
305         }
306     },
307     _strchr: function (s, ch) {
308         return s.indexOf(ch) >= 0
309     },
310     _lookupPrefix: function(name) {
311         return this.prefixes[name] ? this.prefixes[name] : "";
312     },
313     _addPrefix: function(name, value) {
314         //overwrite existing items
315         this.prefixes[name] = value;
316     }
317 }