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