1 var EXPORTED_SYMBOLS = ["CQLParser"];
3 function indent(n, c) {
5 for (var i = 0; i < n; i++)
10 var CQLModifier = function () {
16 CQLModifier.prototype = {
17 toString: function () {
18 return this.name + this.relation + this.value;
21 toXCQL: function (n, c) {
22 var s = indent(n+1, c) + "<modifier>\n";
23 s = s + indent(n+2, c) + "<name>" + this.name + "</name>\n";
24 if (this.relation != null)
25 s = s + indent(n+2, c)
26 + "<relation>" + this.relation + "</relation>\n";
27 if (this.value != null)
28 s = s + indent(n+2, c)
29 + "<value>" + this.value +"</value>\n";
30 s = s + indent(n+1, c) + "</modifier>\n";
35 //we ignore modifier relation symbol, for value-less modifiers
37 var value = this.value.length > 0 ? this.value : "true";
38 var s = '"'+this.name+'": "'+value+'"';
44 var CQLSearchClause = function (field, fielduri, relation, relationuri,
47 this.fielduri = fielduri;
48 this.relation = relation;
49 this.relationuri = relationuri;
50 this.modifiers = modifiers;
54 CQLSearchClause.prototype = {
55 toString: function () {
56 var field = this.field;
57 var relation = this.relation;
58 if (field == 'cql.serverChoice' && relation == 'scr') {
59 //avoid redundant field/relation
63 return (field ? field + ' ' : '') +
64 (relation ? relation : '') +
65 (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') +
66 (relation || this.modifiers.length ? ' ' : '') +
67 '"' + this.term + '"';
70 toXCQL: function (n, c) {
71 var s = indent(n, c) + "<searchClause>\n";
72 if (this.fielduri.length > 0)
74 s = s + indent(n+1, c) + "<prefixes>\n" +
75 indent(n+2, c) + "<prefix>\n" +
76 indent(n+3, c) + "<identifier>" + this.fielduri +
78 indent(n+2, c) + "</prefix>\n" +
79 indent(n+1, c) + "</prefixes>\n";
81 s = s + indent(n+1, c) + "<index>" + this.field + "</index>\n";
82 s = s + indent(n+1, c) + "<relation>\n";
83 if (this.relationuri.length > 0) {
84 s = s + indent(n+2, c) +
85 "<identifier>" + this.relationuri + "</identifier>\n";
87 s = s + indent(n+2, c) + "<value>" + this.relation + "</value>\n";
88 if (this.modifiers.length > 0) {
89 s = s + indent(n+2, c) + "<modifiers>\n";
90 for (var i = 0; i < this.modifiers.length; i++)
91 s = s + this.modifiers[i].toXCQL(n+2, c);
92 s = s + indent(n+2, c) + "</modifiers>\n";
94 s = s + indent(n+1, c) + "</relation>\n";
95 s = s + indent(n+1, c) + "<term>" + this.term + "</term>\n";
96 s = s + indent(n, c) + "</searchClause>\n";
101 var s = '{"term": "'+this.term+'"';
102 if (this.field.length > 0 && this.field != 'cql.serverChoice')
103 s+= ', "field": "'+this.field+'"';
104 if (this.relation.length > 0 && this.relation != 'scr')
105 s+= ', "relation": "'+this._mapRelation(this.relation)+'"';
106 for (var i = 0; i < this.modifiers.length; i++) {
107 //since modifiers are mapped to keys, ignore the reserved ones
108 if (this.modifiers[i].name == "term"
109 ||this.modifiers[i].name == "field"
110 ||this.modifiers[i].name == "relation")
112 s += ', ' + this.modifiers[i].toFQ();
118 _mapRelation: function (rel) {
120 case "<" : return "lt";
121 case ">" : return "gt";
122 case "=" : return "eq";
123 case "<>" : return "ne";
124 case ">=" : return "ge";
125 case "<=" : return "le";
130 _remapRelation: function (rel) {
132 case "lt" : return "<";
133 case "gt" : return ">";
134 case "eq" : return "=";
135 case "ne" : return "<>";
136 case "ge" : return ">=";
137 case "le" : return "<=";
144 var CQLBoolean = function() {
146 this.modifiers = null;
151 CQLBoolean.prototype = {
152 toString: function () {
153 return (this.left.op ? '(' + this.left + ')' : this.left) + ' ' +
154 this.op.toUpperCase() +
155 (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') +
156 ' ' + (this.right.op ? '(' + this.right + ')' : this.right);;
158 toXCQL: function (n, c) {
159 var s = indent(n, c) + "<triple>\n";
160 s = s + indent(n+1, c) + "<boolean>\n" +
161 indent(n+2, c) + "<value>" + this.op + "</value>\n";
162 if (this.modifiers.length > 0) {
163 s = s + indent(n+2, c) + "<modifiers>\n";
164 for (var i = 0; i < this.modifiers.length; i++)
165 s = s + this.modifiers[i].toXCQL(n+2, c);
166 s = s + indent(n+2, c) + "</modifiers>\n";
168 s = s + indent(n+1, c) + "</boolean>\n";
169 s = s + indent(n+1, c) + "<leftOperand>\n" +
170 this.left.toXCQL(n+2, c) + indent(n+1, c) + "</leftOperand>\n";
172 s = s + indent(n+1, c) + "<rightOperand>\n" +
173 this.right.toXCQL(n+2, c) + indent(n+1, c) + "</rightOperand>\n";
174 s = s + indent(n, c) + "</triple>\n";
178 toFQ: function (n, c, nl) {
179 var s = '{"op": "'+this.op+'"';
180 //proximity modifiers
181 for (var i = 0; i < this.modifiers.length; i++)
182 s += ', ' + this.modifiers[i].toFQ();
183 s += ','+nl+indent(n, c)+' "s1": '+this.left.toFQ(n+1, c, nl);
184 s += ','+nl+indent(n, c)+' "s2": '+this.right.toFQ(n+1, c, nl);
185 var fill = n && c ? ' ' : '';
186 s += nl+indent(n-1, c)+fill+'}';
192 var CQLParser = function () {
199 this.prefixes = new Object();
203 CQLParser.prototype = {
204 parse: function (query) {
206 throw new Error("The query to be parsed cannot be empty");
209 this.ql = this.qs.length;
212 this.tree = this._parseQuery("cql.serverChoice", "scr", new Array());
214 throw new Error("EOF expected");
216 parseFromFQ: function (query) {
218 throw new Error("The query to be parsed cannot be empty");
219 if (typeof query == 'string')
220 query = JSON.parse(query);
221 this.tree = this._parseFromFQ(query);
223 _parseFromFQ: function (fq) {
225 if (fq.hasOwnProperty('op')
226 && fq.hasOwnProperty('s1')
227 && fq.hasOwnProperty('s2')) {
228 var node = new CQLBoolean();
230 node.left = this._parseFromFQ(fq.s1);
231 node.right = this._parseFromFQ(fq.s2);
232 //include all other members as modifiers
234 for (var key in fq) {
235 if (key == 'op' || key == 's1' || key == 's2')
237 var mod = new CQLModifier();
241 node.modifiers.push(mod);
246 if (fq.hasOwnProperty('term')) {
247 var node = new CQLSearchClause();
249 node.field = fq.hasOwnProperty('field')
250 ? fq.field : 'cql.serverChoice';
251 node.relation = fq.hasOwnProperty('relation')
252 ? node._remapRelation(fq.relation) :
253 //HACK: if the field is set, assume '=' rather than scr
254 (fq.hasOwnProperty('field') ? '=' : 'scr');
255 //include all other members as modifiers
256 node.relationuri = '';
259 for (var key in fq) {
260 if (key == 'term' || key == 'field' || key == 'relation')
262 var mod = new CQLModifier();
266 node.modifiers.push(mod);
270 throw new Error('Unknow node type; '+JSON.stringify(fq));
272 toXCQL: function (c) {
273 c = typeof c == "undefined" ? ' ' : c;
274 return this.tree.toXCQL(0, c);
276 toFQ: function (c, nl) {
277 c = typeof c == "undefined" ? ' ' : c;
278 nl = typeof nl == "undefined" ? '\n' : c;
279 return this.tree.toFQ(0, c, nl);
281 toString: function () {
282 return this.tree.toString();
284 _parseQuery: function(field, relation, modifiers) {
285 var left = this._parseSearchClause(field, relation, modifiers);
286 while (this.look == "s" && (
287 this.lval == "and" ||
289 this.lval == "not" ||
290 this.lval == "prox")) {
291 var b = new CQLBoolean();
294 b.modifiers = this._parseModifiers();
296 b.right = this._parseSearchClause(field, relation, modifiers);
301 _parseModifiers: function() {
302 var ar = new Array();
303 while (this.look == "/") {
305 if (this.look != "s" && this.look != "q")
306 throw new Error("Invalid modifier.")
308 var name = this.lval;
310 if (this.look.length > 0
311 && this._strchr("<>=", this.look.charAt(0))) {
314 if (this.look != "s" && this.look != "q")
315 throw new Error("Invalid relation within the modifier.");
317 var m = new CQLModifier();
324 var m = new CQLModifier();
333 _parseSearchClause: function(field, relation, modifiers) {
334 if (this.look == "(") {
336 var b = this._parseQuery(field, relation, modifiers);
337 if (this.look == ")")
340 throw new Error("Missing closing parenthesis.");
343 } else if (this.look == "s" || this.look == "q") {
344 var first = this.val; // dont know if field or term yet
346 if (this.look == "q" ||
348 this.lval != "and" &&
350 this.lval != "not" &&
351 this.lval != "prox")) {
352 var rel = this.val; // string relation
354 return this._parseSearchClause(first, rel,
355 this._parseModifiers());
356 } else if (this.look.length > 0
357 && this._strchr("<>=", this.look.charAt(0))) {
358 var rel = this.look; // other relation <, = ,etc
360 return this._parseSearchClause(first, rel,
361 this._parseModifiers());
363 // it's a search term
364 var pos = field.indexOf('.');
367 pre = field.substring(0, pos);
369 var uri = this._lookupPrefix(pre);
371 field = field.substring(pos+1);
373 pos = relation.indexOf('.');
377 pre = relation.substring(0, pos);
379 var reluri = this._lookupPrefix(pre);
380 if (reluri.Length > 0)
381 relation = relation.Substring(pos+1);
383 var sc = new CQLSearchClause(field,
392 } else if (this.look == ">") {
394 if (this.look != "s" && this.look != "q")
395 throw new Error("Expecting string or a quoted expression.");
397 var first = this.lval;
399 if (this.look == "=")
402 if (this.look != "s" && this.look != "q")
403 throw new Error("Expecting string or a quoted expression.");
405 this._addPrefix(first, this.lval);
407 return this._parseQuery(field, relation, modifiers);
409 this._addPrefix("default", first);
410 return this._parseQuery(field, relation, modifiers);
413 throw new Error("Invalid search clause.");
418 while (this.qi < this.ql
419 && this._strchr(" \t\r\n", this.qs.charAt(this.qi)))
421 if (this.qi == this.ql) {
425 var c = this.qs.charAt(this.qi);
426 if (this._strchr("()/", c)) {
429 } else if (this._strchr("<>=", c)) {
432 while (this.qi < this.ql
433 && this._strchr("<>=", this.qs.charAt(this.qi))) {
434 this.look = this.look + this.qs.charAt(this.qi);
437 } else if (this._strchr("\"'", c)) {
442 while (this.qi < this.ql
443 && this.qs.charAt(this.qi) != mark) {
444 if (this.qs.charAt(this.qi) == '\\'
445 && this.qi < this.ql-1)
447 this.val = this.val + this.qs.charAt(this.qi);
450 this.lval = this.val.toLowerCase();
451 if (this.qi < this.ql)
456 while (this.qi < this.ql
457 && !this._strchr("()/<>= \t\r\n", this.qs.charAt(this.qi))) {
458 this.val = this.val + this.qs.charAt(this.qi);
461 this.lval = this.val.toLowerCase();
464 _strchr: function (s, ch) {
465 return s.indexOf(ch) >= 0
467 _lookupPrefix: function(name) {
468 return this.prefixes[name] ? this.prefixes[name] : "";
470 _addPrefix: function(name, value) {
471 //overwrite existing items
472 this.prefixes[name] = value;