1 var EXPORTED_SYMBOLS = ["CQLParser"];
3 var DEFAULT_SERVER_CHOICE_FIELD = 'cql.serverChoice';
4 var DEFAULT_SERVER_CHOICE_RELATION = 'scr';
6 function indent(n, c) {
8 for (var i = 0; i < n; i++)
13 var CQLModifier = function () {
19 CQLModifier.prototype = {
20 toString: function () {
21 return this.name + this.relation + this.value;
24 toXCQL: function (n, c) {
25 var s = indent(n+1, c) + "<modifier>\n";
26 s = s + indent(n+2, c) + "<name>" + this.name + "</name>\n";
27 if (this.relation != null)
28 s = s + indent(n+2, c)
29 + "<relation>" + this.relation + "</relation>\n";
30 if (this.value != null)
31 s = s + indent(n+2, c)
32 + "<value>" + this.value +"</value>\n";
33 s = s + indent(n+1, c) + "</modifier>\n";
38 //we ignore modifier relation symbol, for value-less modifiers
40 var value = this.value.length > 0 ? this.value : "true";
41 var s = '"'+this.name+'": "'+value+'"';
47 var CQLSearchClause = function (field, fielduri, relation, relationuri,
48 modifiers, term, scf, scr) {
50 this.fielduri = fielduri;
51 this.relation = relation;
52 this.relationuri = relationuri;
53 this.modifiers = modifiers;
55 this.scf = scf || DEFAULT_SERVER_CHOICE_FIELD;
56 this.scr = scr || DEFAULT_SERVER_CHOICE_RELATION;
59 CQLSearchClause.prototype = {
60 toString: function () {
61 var field = this.field;
62 var relation = this.relation;
63 if (field == this.scf && relation == this.scr) {
64 //avoid redundant field/relation
68 return (field ? field + ' ' : '') +
69 (relation ? relation : '') +
70 (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') +
71 (relation || this.modifiers.length ? ' ' : '') +
72 '"' + this.term + '"';
75 toXCQL: function (n, c) {
76 var s = indent(n, c) + "<searchClause>\n";
77 if (this.fielduri.length > 0)
79 s = s + indent(n+1, c) + "<prefixes>\n" +
80 indent(n+2, c) + "<prefix>\n" +
81 indent(n+3, c) + "<identifier>" + this.fielduri +
83 indent(n+2, c) + "</prefix>\n" +
84 indent(n+1, c) + "</prefixes>\n";
86 s = s + indent(n+1, c) + "<index>" + this.field + "</index>\n";
87 s = s + indent(n+1, c) + "<relation>\n";
88 if (this.relationuri.length > 0) {
89 s = s + indent(n+2, c) +
90 "<identifier>" + this.relationuri + "</identifier>\n";
92 s = s + indent(n+2, c) + "<value>" + this.relation + "</value>\n";
93 if (this.modifiers.length > 0) {
94 s = s + indent(n+2, c) + "<modifiers>\n";
95 for (var i = 0; i < this.modifiers.length; i++)
96 s = s + this.modifiers[i].toXCQL(n+2, c);
97 s = s + indent(n+2, c) + "</modifiers>\n";
99 s = s + indent(n+1, c) + "</relation>\n";
100 s = s + indent(n+1, c) + "<term>" + this.term + "</term>\n";
101 s = s + indent(n, c) + "</searchClause>\n";
106 var s = '{"term": "'+this.term+'"';
107 if (this.field.length > 0 && this.field != this.scf)
108 s+= ', "field": "'+this.field+'"';
109 if (this.relation.length > 0 && this.relation != this.scr)
110 s+= ', "relation": "'+this._mapRelation(this.relation)+'"';
111 for (var i = 0; i < this.modifiers.length; i++) {
112 //since modifiers are mapped to keys, ignore the reserved ones
113 if (this.modifiers[i].name == "term"
114 ||this.modifiers[i].name == "field"
115 ||this.modifiers[i].name == "relation")
117 s += ', ' + this.modifiers[i].toFQ();
123 _mapRelation: function (rel) {
125 case "<" : return "lt";
126 case ">" : return "gt";
127 case "=" : return "eq";
128 case "<>" : return "ne";
129 case ">=" : return "ge";
130 case "<=" : return "le";
135 _remapRelation: function (rel) {
137 case "lt" : return "<";
138 case "gt" : return ">";
139 case "eq" : return "=";
140 case "ne" : return "<>";
141 case "ge" : return ">=";
142 case "le" : return "<=";
149 var CQLBoolean = function() {
151 this.modifiers = null;
156 CQLBoolean.prototype = {
157 toString: function () {
158 return (this.left.op ? '(' + this.left + ')' : this.left) + ' ' +
159 this.op.toUpperCase() +
160 (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') +
161 ' ' + (this.right.op ? '(' + this.right + ')' : this.right);;
163 toXCQL: function (n, c) {
164 var s = indent(n, c) + "<triple>\n";
165 s = s + indent(n+1, c) + "<boolean>\n" +
166 indent(n+2, c) + "<value>" + this.op + "</value>\n";
167 if (this.modifiers.length > 0) {
168 s = s + indent(n+2, c) + "<modifiers>\n";
169 for (var i = 0; i < this.modifiers.length; i++)
170 s = s + this.modifiers[i].toXCQL(n+2, c);
171 s = s + indent(n+2, c) + "</modifiers>\n";
173 s = s + indent(n+1, c) + "</boolean>\n";
174 s = s + indent(n+1, c) + "<leftOperand>\n" +
175 this.left.toXCQL(n+2, c) + indent(n+1, c) + "</leftOperand>\n";
177 s = s + indent(n+1, c) + "<rightOperand>\n" +
178 this.right.toXCQL(n+2, c) + indent(n+1, c) + "</rightOperand>\n";
179 s = s + indent(n, c) + "</triple>\n";
183 toFQ: function (n, c, nl) {
184 var s = '{"op": "'+this.op+'"';
185 //proximity modifiers
186 for (var i = 0; i < this.modifiers.length; i++)
187 s += ', ' + this.modifiers[i].toFQ();
188 s += ','+nl+indent(n, c)+' "s1": '+this.left.toFQ(n+1, c, nl);
189 s += ','+nl+indent(n, c)+' "s2": '+this.right.toFQ(n+1, c, nl);
190 var fill = n && c ? ' ' : '';
191 s += nl+indent(n-1, c)+fill+'}';
197 var CQLParser = function () {
204 this.prefixes = new Object();
210 CQLParser.prototype = {
211 parse: function (query, scf, scr) {
213 throw new Error("The query to be parsed cannot be empty");
214 this.scf = typeof scf != 'string'
215 ? DEFAULT_SERVER_CHOICE_FIELD : scf;
216 this.scr = typeof scr != 'string'
217 ? DEFAULT_SERVER_CHOICE_RELATION : scr;
219 this.ql = this.qs.length;
222 this.tree = this._parseQuery(this.scf, this.scr, new Array());
224 throw new Error("EOF expected");
226 parseFromFQ: function (query, scf, scr) {
228 throw new Error("The query to be parsed cannot be empty");
229 if (typeof query == 'string')
230 query = JSON.parse(query);
231 this.scf = typeof scf != 'string'
232 ? DEFAULT_SERVER_CHOICE_FIELD : scf;
233 this.scr = typeof scr != 'string'
234 ? DEFAULT_SERVER_CHOICE_RELATION : scr;
235 this.tree = this._parseFromFQ(query);
237 _parseFromFQ: function (fq) {
239 if (fq.hasOwnProperty('op')
240 && fq.hasOwnProperty('s1')
241 && fq.hasOwnProperty('s2')) {
242 var node = new CQLBoolean();
244 node.left = this._parseFromFQ(fq.s1);
245 node.right = this._parseFromFQ(fq.s2);
246 //include all other members as modifiers
248 for (var key in fq) {
249 if (key == 'op' || key == 's1' || key == 's2')
251 var mod = new CQLModifier();
255 node.modifiers.push(mod);
260 if (fq.hasOwnProperty('term')) {
261 var node = new CQLSearchClause();
265 node.field = fq.hasOwnProperty('field')
266 ? fq.field : this.scf;
267 node.relation = fq.hasOwnProperty('relation')
268 ? node._remapRelation(fq.relation) : this.scr;
269 //include all other members as modifiers
270 node.relationuri = '';
273 for (var key in fq) {
274 if (key == 'term' || key == 'field' || key == 'relation')
276 var mod = new CQLModifier();
280 node.modifiers.push(mod);
284 throw new Error('Unknow node type; '+JSON.stringify(fq));
286 toXCQL: function (c) {
287 c = typeof c == "undefined" ? ' ' : c;
288 return this.tree.toXCQL(0, c);
290 toFQ: function (c, nl) {
291 c = typeof c == "undefined" ? ' ' : c;
292 nl = typeof nl == "undefined" ? '\n' : c;
293 return this.tree.toFQ(0, c, nl);
295 toString: function () {
296 return this.tree.toString();
298 _parseQuery: function(field, relation, modifiers) {
299 var left = this._parseSearchClause(field, relation, modifiers);
300 while (this.look == "s" && (
301 this.lval == "and" ||
303 this.lval == "not" ||
304 this.lval == "prox")) {
305 var b = new CQLBoolean();
308 b.modifiers = this._parseModifiers();
310 b.right = this._parseSearchClause(field, relation, modifiers);
315 _parseModifiers: function() {
316 var ar = new Array();
317 while (this.look == "/") {
319 if (this.look != "s" && this.look != "q")
320 throw new Error("Invalid modifier.")
322 var name = this.lval;
324 if (this.look.length > 0
325 && this._strchr("<>=", this.look.charAt(0))) {
328 if (this.look != "s" && this.look != "q")
329 throw new Error("Invalid relation within the modifier.");
331 var m = new CQLModifier();
338 var m = new CQLModifier();
347 _parseSearchClause: function(field, relation, modifiers) {
348 if (this.look == "(") {
350 var b = this._parseQuery(field, relation, modifiers);
351 if (this.look == ")")
354 throw new Error("Missing closing parenthesis.");
357 } else if (this.look == "s" || this.look == "q") {
358 var first = this.val; // dont know if field or term yet
360 if (this.look == "q" ||
362 this.lval != "and" &&
364 this.lval != "not" &&
365 this.lval != "prox")) {
366 var rel = this.val; // string relation
368 return this._parseSearchClause(first, rel,
369 this._parseModifiers());
370 } else if (this.look.length > 0
371 && this._strchr("<>=", this.look.charAt(0))) {
372 var rel = this.look; // other relation <, = ,etc
374 return this._parseSearchClause(first, rel,
375 this._parseModifiers());
377 // it's a search term
378 var pos = field.indexOf('.');
381 pre = field.substring(0, pos);
383 var uri = this._lookupPrefix(pre);
385 field = field.substring(pos+1);
387 pos = relation.indexOf('.');
391 pre = relation.substring(0, pos);
393 var reluri = this._lookupPrefix(pre);
394 if (reluri.Length > 0)
395 relation = relation.Substring(pos+1);
397 var sc = new CQLSearchClause(field,
408 } else if (this.look == ">") {
410 if (this.look != "s" && this.look != "q")
411 throw new Error("Expecting string or a quoted expression.");
413 var first = this.lval;
415 if (this.look == "=")
418 if (this.look != "s" && this.look != "q")
419 throw new Error("Expecting string or a quoted expression.");
421 this._addPrefix(first, this.lval);
423 return this._parseQuery(field, relation, modifiers);
425 this._addPrefix("default", first);
426 return this._parseQuery(field, relation, modifiers);
429 throw new Error("Invalid search clause.");
435 while (this.qi < this.ql
436 && this._strchr(" \t\r\n", this.qs.charAt(this.qi)))
439 if (this.qi == this.ql) {
444 var c = this.qs.charAt(this.qi);
446 if (this._strchr("()/", c)) {
450 } else if (this._strchr("<>=", c)) {
453 //comparitors can repeat, could be if
454 while (this.qi < this.ql
455 && this._strchr("<>=", this.qs.charAt(this.qi))) {
456 this.look = this.look + this.qs.charAt(this.qi);
460 } else if (this._strchr("\"'", c)) {
462 //remember quote char
467 while (this.qi < this.ql) {
468 if (!escaped && this.qs.charAt(this.qi) == mark)
470 if (!escaped && this.qs.charAt(this.qi) == '\\')
474 this.val += this.qs.charAt(this.qi);
477 this.lval = this.val.toLowerCase();
478 if (this.qi < this.ql)
481 this.look = ""; //notify error
486 while (this.qi < this.ql
487 && !this._strchr("()/<>= \t\r\n", this.qs.charAt(this.qi))) {
488 this.val = this.val + this.qs.charAt(this.qi);
491 this.lval = this.val.toLowerCase();
494 _strchr: function (s, ch) {
495 return s.indexOf(ch) >= 0
497 _lookupPrefix: function(name) {
498 return this.prefixes[name] ? this.prefixes[name] : "";
500 _addPrefix: function(name, value) {
501 //overwrite existing items
502 this.prefixes[name] = value;