ebd878e71de519ce95f0fe9e0ff312cd950754a4
[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     toFQ: function () {
29         //we ignore modifier relation symbol, for value-less modifiers
30         //we assume 'true'
31         var value = this.value.length > 0 ? this.value : "true";
32         var s = '"'+this.name+'": "'+value+'"';
33         return s;
34     }
35 }
36
37 // CQLSearchClause
38 var CQLSearchClause = function (field, fielduri, relation, relationuri, 
39                                 modifiers, term) {
40     this.field = field;
41     this.fielduri = fielduri;
42     this.relation = relation;
43     this.relationuri = relationuri;
44     this.modifiers = modifiers;
45     this.term = term;
46 }
47
48 CQLSearchClause.prototype = {
49     toXCQL: function (n) {
50         var s = indent(n) + "<searchClause>\n";
51         if (this.fielduri.length > 0)
52         {
53             s = s + indent(n+1) + "<prefixes>\n" +
54                 indent(n+2) + "<prefix>\n" +
55                 indent(n+3) + "<identifier>" + this.fielduri +
56                 "</identifier>\n" +
57                 indent(n+2) + "</prefix>\n" +
58                 indent(n+1) + "</prefixes>\n";
59         }
60         s = s + indent(n+1) + "<index>" + this.field + "</index>\n";
61         s = s + indent(n+1) + "<relation>\n";
62         if (this.relationuri.length > 0) {
63             s = s + indent(n+2) +
64                 "<identifier>" + this.relationuri + "</identifier>\n";
65         }
66         s = s + indent(n+2) + "<value>" + this.relation + "</value>\n";
67         if (this.modifiers.length > 0) {
68             s = s + indent(n+2) + "<modifiers>\n";
69             for (var i = 0; i < this.modifiers.length; i++)
70                 s = s + this.modifiers[i].toXCQL(n+2);
71             s = s + indent(n+2) + "</modifiers>\n";
72         }
73         s = s + indent(n+1) + "</relation>\n";
74         s = s + indent(n+1) + "<term>" + this.term + "</term>\n";
75         s = s + indent(n) + "</searchClause>\n";
76         return s;
77     },
78
79     toFQ: function () {
80         var s = '{ "term": "'+this.term+'"';
81         if (this.field.length > 0 && this.field != 'cql.serverChoice')
82           s+= ', "field": "'+this.field+'"';
83         if (this.relation.length > 0 && this.relation != 'scr')
84           s+= ', "relation": "'+this._mapRelation(this.relation)+'"';
85         for (var i = 0; i < this.modifiers.length; i++) {
86           //since modifiers are mapped to keys, ignore the reserved ones
87           if (this.modifiers[i].name == "term"
88             ||this.modifiers[i].name == "field"
89             ||this.modifiers[i].name == "relation")
90             continue;
91           s += ', ' + this.modifiers[i].toFQ();
92         }
93         s += ' }';
94         return s;
95     },
96
97     _mapRelation: function (rel) {
98       switch(rel) {
99         case "<" : return "lt";
100         case ">" : return "gt";
101         case "=" : return "eq";
102         case "<>" : return "ne";
103         case ">=" : return "ge";
104         case "<=" : return "le";
105         default: return rel;
106       }
107     }
108
109 }
110 // CQLBoolean
111 var CQLBoolean = function() {
112     this.op = null;
113     this.modifiers = null;
114     this.left = null;
115     this.right = null;
116 }
117
118 CQLBoolean.prototype = {
119     toXCQL: function (n) {
120         var s = indent(n) + "<triple>\n";
121         s = s + indent(n+1) + "<boolean>\n" +
122             indent(n+2) + "<value>" + this.op + "</value>\n";
123         if (this.modifiers.length > 0) {
124             s = s + indent(n+2) + "<modifiers>\n";
125             for (var i = 0; i < this.modifiers.length; i++)
126                 s = s + this.modifiers[i].toXCQL(n+2);
127             s = s + indent(n+2) + "</modifiers>\n";
128         }
129         s = s + indent(n+1) + "</boolean>\n";
130         s = s + indent(n+1) + "<leftOperand>\n" +
131             this.left.toXCQL(n+2) + indent(n+1) + "</leftOperand>\n";
132
133         s = s + indent(n+1) + "<rightOperand>\n" +
134             this.right.toXCQL(n+2) + indent(n+1) + "</rightOperand>\n";
135         s = s + indent(n) + "</triple>\n";
136         return s;
137     },
138
139     toFQ: function () {
140       var s = ' { "op": "'+this.op+'"';
141       //proximity modifiers
142       for (var i = 0; i < this.modifiers.length; i++)
143         s += ', ' + this.modifiers[i].toFQ();
144       s += ', "s1": '+this.left.toFQ();
145       s += ', "s2": '+this.right.toFQ();
146       s += ' }'
147       return s;
148     }
149
150 }
151 // CQLParser
152 var CQLParser = function () {
153     this.qi = null;
154     this.ql = null;
155     this.qs = null;
156     this.look = null;
157     this.lval = null;
158     this.val = null;
159     this.prefixes = new Object();
160     this.tree = null;
161 }
162
163 CQLParser.prototype = {
164     parse: function (query) {
165         if (!query)
166             throw new Error("The query to be parsed cannot be empty");
167         
168         this.qs = query;
169         this.ql = this.qs.length;
170         this.qi = 0;
171         this._move(); 
172         this.tree = this._parseQuery("cql.serverChoice", "scr", new Array());
173         if (this.look != "")
174             throw new Error("EOF expected");
175     },
176     toXCQL: function () {
177         return this.tree.toXCQL();
178     },
179     toFQ: function () {
180         return this.tree.toFQ();
181     },
182     _parseQuery: function(field, relation, modifiers) {
183         var left = this._parseSearchClause(field, relation, modifiers);
184         while (this.look == "s" && (
185                     this.lval == "and" ||
186                     this.lval == "or" ||
187                     this.lval == "not" ||
188                     this.lval == "prox")) {
189             var b = new CQLBoolean();
190             b.op = this.lval;
191             this._move();
192             b.modifiers = this._parseModifiers();
193             b.left = left;
194             b.right = this._parseSearchClause(field, relation, modifiers);
195             left = b;
196         }
197         return left;
198     },
199     _parseModifiers: function() {
200         var ar = new Array();
201         while (this.look == "/") {
202             this._move();
203             if (this.look != "s" && this.look != "q")
204                 throw new Error("Invalid modifier.")
205             
206             var name = this.lval;
207             this._move();
208             if (this.look.length > 0 
209                 && this._strchr("<>=", this.look.charAt(0))) {
210                 var rel = this.look;
211                 this._move();
212                 if (this.look != "s" && this.look != "q")
213                     throw new Error("Invalid relation within the modifier.");
214                 
215                 var m = new CQLModifier();
216                 m.name = name;
217                 m.relation = rel;
218                 m.value = this.val;
219                 ar.push(m);
220                 this._move();
221             } else {
222                 var m = new CQLModifier();
223                 m.name = name;
224                 m.relation = "";
225                 m.value = "";
226                 ar.push(m);
227             }
228         }
229         return ar;
230     },
231     _parseSearchClause: function(field, relation, modifiers) {
232         if (this.look == "(") {
233             this._move();
234             var b = this._parseQuery(field, relation, modifiers);
235             if (this.look == ")")
236                 this._move();
237             else
238                 throw new Error("Missing closing parenthesis.");
239
240             return b;
241         } else if (this.look == "s" || this.look == "q") {
242             var first = this.val;   // dont know if field or term yet
243             this._move();
244             if (this.look == "q" ||
245                     (this.look == "s" &&
246                      this.lval != "and" &&
247                      this.lval != "or" &&
248                      this.lval != "not" &&
249                      this.lval != "prox")) {
250                 var rel = this.val;    // string relation
251                 this._move();
252                 return this._parseSearchClause(first, rel,
253                                                this._parseModifiers());
254             } else if (this.look.length > 0 
255                        && this._strchr("<>=", this.look.charAt(0))) {
256                 var rel = this.look;   // other relation <, = ,etc
257                 this._move();
258                 return this._parseSearchClause(first, rel, 
259                                                this._parseModifiers());
260             } else {
261                 // it's a search term
262                 var pos = field.indexOf('.');
263                 var pre = "";
264                 if (pos != -1)
265                     pre = field.substring(0, pos);
266                 
267                 var uri = this._lookupPrefix(pre);
268                 if (uri.length > 0)
269                     field = field.substring(pos+1);
270                 
271                 pos = relation.indexOf('.');
272                 if (pos == -1)
273                     pre = "cql";
274                 else
275                     pre = relation.substring(0, pos);
276
277                 var reluri = this._lookupPrefix(pre);
278                 if (reluri.Length > 0)
279                     relation = relation.Substring(pos+1);
280
281                 var sc = new CQLSearchClause(field,
282                         uri,
283                         relation,
284                         reluri,
285                         modifiers,
286                         first);
287                 return sc;
288             }
289         // prefixes
290         } else if (this.look == ">") {
291             this._move();
292             if (this.look != "s" && this.look != "q")
293                 throw new Error("Expecting string or a quoted expression.");
294             
295             var first = this.lval;
296             this._move();
297             if (this.look == "=")
298             {
299                 this._move();
300                 if (this.look != "s" && this.look != "q")
301                     throw new Error("Expecting string or a quoted expression.");
302                 
303                 this._addPrefix(first, this.lval);
304                 this._move();
305                 return this._parseQuery(field, relation, modifiers);
306             } else {
307                 this._addPrefix("default", first);
308                 return this._parseQuery(field, relation, modifiers);
309             }
310         } else {
311             throw new Error("Invalid search clause.");
312         }
313
314     },
315     _move: function () {
316         while (this.qi < this.ql 
317                && this._strchr(" \t\r\n", this.qs.charAt(this.qi)))
318             this.qi++;
319         if (this.qi == this.ql) {
320             this.look = "";
321             return;
322         }
323         var c = this.qs.charAt(this.qi);
324         if (this._strchr("()/", c)) {
325             this.look = c;
326             this.qi++;
327         } else if (this._strchr("<>=", c)) {
328             this.look = c;
329             this.qi++;
330             while (this.qi < this.ql 
331                    && this._strchr("<>=", this.qs.charAt(this.qi))) {
332                 this.look = this.look + this.qs.charAt(this.qi);
333                 this.qi++;
334             }
335         } else if (this._strchr("\"'", c)) {
336             this.look = "q";
337             var mark = c;
338             this.qi++;
339             this.val = "";
340             while (this.qi < this.ql 
341                    && this.qs.charAt(this.qi) != mark) {
342                 if (this.qs.charAt(this.qi) == '\\' 
343                     && this.qi < this.ql-1)
344                     this.qi++;
345                 this.val = this.val + this.qs.charAt(this.qi);
346                 this.qi++;
347             }
348             this.lval = this.val.toLowerCase();
349             if (this.qi < this.ql)
350                 this.qi++;
351         } else {
352             this.look = "s";
353             this.val = "";
354             while (this.qi < this.ql 
355                    && !this._strchr("()/<>= \t\r\n", this.qs.charAt(this.qi))) {
356                 this.val = this.val + this.qs.charAt(this.qi);
357                 this.qi++;
358             }
359             this.lval = this.val.toLowerCase();
360         }
361     },
362     _strchr: function (s, ch) {
363         return s.indexOf(ch) >= 0
364     },
365     _lookupPrefix: function(name) {
366         return this.prefixes[name] ? this.prefixes[name] : "";
367     },
368     _addPrefix: function(name, value) {
369         //overwrite existing items
370         this.prefixes[name] = value;
371     }
372 }