X-Git-Url: http://git.indexdata.com/?p=cql-js-moved-to-github.git;a=blobdiff_plain;f=cql.js;h=37035d3b9611ef4b345324ad2fec0585510ebdc3;hp=ebd5e766615c4af2e540faec1dc634161314bf84;hb=HEAD;hpb=0b0b8b4fae0f58ab263e03c1cffe2dd819a6d891 diff --git a/cql.js b/cql.js index ebd5e76..37035d3 100644 --- a/cql.js +++ b/cql.js @@ -1,10 +1,14 @@ -function indent(n) { +var EXPORTED_SYMBOLS = ["CQLParser"]; + +var DEFAULT_SERVER_CHOICE_FIELD = 'cql.serverChoice'; +var DEFAULT_SERVER_CHOICE_RELATION = 'scr'; + +function indent(n, c) { var s = ""; for (var i = 0; i < n; i++) - s = s + " "; + s = s + c; return s; } - // CQLModifier var CQLModifier = function () { this.name = null; @@ -13,60 +17,133 @@ var CQLModifier = function () { } CQLModifier.prototype = { - toXCQL: function (n) { - var s = indent(n+1) + "\n"; - s = s + indent(n+2) + "" + this.name + "\n"; + toString: function () { + return this.name + this.relation + this.value; + }, + + toXCQL: function (n, c) { + var s = indent(n+1, c) + "\n"; + s = s + indent(n+2, c) + "" + this.name + "\n"; if (this.relation != null) - s = s + indent(n+2) + s = s + indent(n+2, c) + "" + this.relation + "\n"; if (this.value != null) - s = s + indent(n+2) + s = s + indent(n+2, c) + "" + this.value +"\n"; - s = s + indent(n+1) + "\n"; + s = s + indent(n+1, c) + "\n"; + return s; + }, + + toFQ: function () { + //we ignore modifier relation symbol, for value-less modifiers + //we assume 'true' + var value = this.value.length > 0 ? this.value : "true"; + var s = '"'+this.name+'": "'+value+'"'; return s; } } + // CQLSearchClause var CQLSearchClause = function (field, fielduri, relation, relationuri, - modifiers, term) { + modifiers, term, scf, scr) { this.field = field; this.fielduri = fielduri; this.relation = relation; this.relationuri = relationuri; this.modifiers = modifiers; this.term = term; + this.scf = scf || DEFAULT_SERVER_CHOICE_FIELD; + this.scr = scr || DEFAULT_SERVER_CHOICE_RELATION; } CQLSearchClause.prototype = { - toXCQL: function (n) { - var s = indent(n) + "\n"; + toString: function () { + var field = this.field; + var relation = this.relation; + if (field == this.scf && relation == this.scr) { + //avoid redundant field/relation + field = null; + relation = null; + } + return (field ? field + ' ' : '') + + (relation ? relation : '') + + (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') + + (relation || this.modifiers.length ? ' ' : '') + + '"' + this.term + '"'; + }, + + toXCQL: function (n, c) { + var s = indent(n, c) + "\n"; if (this.fielduri.length > 0) { - s = s + indent(n+1) + "\n" + - indent(n+2) + "\n" + - indent(n+3) + "" + this.fielduri + + s = s + indent(n+1, c) + "\n" + + indent(n+2, c) + "\n" + + indent(n+3, c) + "" + this.fielduri + "\n" + - indent(n+2) + "\n" + - indent(n+1) + "\n"; + indent(n+2, c) + "\n" + + indent(n+1, c) + "\n"; } - s = s + indent(n+1) + "" + this.field + "\n"; - s = s + indent(n+1) + "\n"; + s = s + indent(n+1, c) + "" + this.field + "\n"; + s = s + indent(n+1, c) + "\n"; if (this.relationuri.length > 0) { - s = s + indent(n+2) + + s = s + indent(n+2, c) + "" + this.relationuri + "\n"; } - s = s + indent(n+2) + "" + this.relation + "\n"; + s = s + indent(n+2, c) + "" + this.relation + "\n"; if (this.modifiers.length > 0) { - s = s + indent(n+2) + "\n"; + s = s + indent(n+2, c) + "\n"; for (var i = 0; i < this.modifiers.length; i++) - s = s + this.modifiers[i].toXCQL(n+2); - s = s + indent(n+2) + "\n"; + s = s + this.modifiers[i].toXCQL(n+2, c); + s = s + indent(n+2, c) + "\n"; + } + s = s + indent(n+1, c) + "\n"; + s = s + indent(n+1, c) + "" + this.term + "\n"; + s = s + indent(n, c) + "\n"; + return s; + }, + + toFQ: function () { + var s = '{"term": "'+this.term+'"'; + if (this.field.length > 0 && this.field != this.scf) + s+= ', "field": "'+this.field+'"'; + if (this.relation.length > 0 && this.relation != this.scr) + s+= ', "relation": "'+this._mapRelation(this.relation)+'"'; + for (var i = 0; i < this.modifiers.length; i++) { + //since modifiers are mapped to keys, ignore the reserved ones + if (this.modifiers[i].name == "term" + ||this.modifiers[i].name == "field" + ||this.modifiers[i].name == "relation") + continue; + s += ', ' + this.modifiers[i].toFQ(); } - s = s + indent(n+1) + "\n"; - s = s + indent(n+1) + "" + this.term + "\n"; - s = s + indent(n) + "\n"; + s += '}'; return s; + }, + + _mapRelation: function (rel) { + switch(rel) { + case "<" : return "lt"; + case ">" : return "gt"; + case "=" : return "eq"; + case "<>" : return "ne"; + case ">=" : return "ge"; + case "<=" : return "le"; + default: return rel; + } + }, + + _remapRelation: function (rel) { + switch(rel) { + case "lt" : return "<"; + case "gt" : return ">"; + case "eq" : return "="; + case "ne" : return "<>"; + case "ge" : return ">="; + case "le" : return "<="; + default: return rel; + } } + } // CQLBoolean var CQLBoolean = function() { @@ -77,25 +154,44 @@ var CQLBoolean = function() { } CQLBoolean.prototype = { - toXCQL: function (n) { - var s = indent(n) + "\n"; - s = s + indent(n+1) + "\n" + - indent(n+2) + "" + this.op + "\n"; + toString: function () { + return (this.left.op ? '(' + this.left + ')' : this.left) + ' ' + + this.op.toUpperCase() + + (this.modifiers.length > 0 ? '/' + this.modifiers.join('/') : '') + + ' ' + (this.right.op ? '(' + this.right + ')' : this.right);; + }, + toXCQL: function (n, c) { + var s = indent(n, c) + "\n"; + s = s + indent(n+1, c) + "\n" + + indent(n+2, c) + "" + this.op + "\n"; if (this.modifiers.length > 0) { - s = s + indent(n+2) + "\n"; + s = s + indent(n+2, c) + "\n"; for (var i = 0; i < this.modifiers.length; i++) - s = s + this.modifiers[i].toXCQL(n+2); - s = s + indent(n+2) + "\n"; + s = s + this.modifiers[i].toXCQL(n+2, c); + s = s + indent(n+2, c) + "\n"; } - s = s + indent(n+1) + "\n"; - s = s + indent(n+1) + "\n" + - this.left.toXCQL(n+2) + indent(n+1) + "\n"; + s = s + indent(n+1, c) + "\n"; + s = s + indent(n+1, c) + "\n" + + this.left.toXCQL(n+2, c) + indent(n+1, c) + "\n"; - s = s + indent(n+1) + "\n" + - this.right.toXCQL(n+2) + indent(n+1) + "\n"; - s = s + indent(n) + "\n"; + s = s + indent(n+1, c) + "\n" + + this.right.toXCQL(n+2, c) + indent(n+1, c) + "\n"; + s = s + indent(n, c) + "\n"; return s; + }, + + toFQ: function (n, c, nl) { + var s = '{"op": "'+this.op+'"'; + //proximity modifiers + for (var i = 0; i < this.modifiers.length; i++) + s += ', ' + this.modifiers[i].toFQ(); + s += ','+nl+indent(n, c)+' "s1": '+this.left.toFQ(n+1, c, nl); + s += ','+nl+indent(n, c)+' "s2": '+this.right.toFQ(n+1, c, nl); + var fill = n && c ? ' ' : ''; + s += nl+indent(n-1, c)+fill+'}'; + return s; } + } // CQLParser var CQLParser = function () { @@ -107,23 +203,97 @@ var CQLParser = function () { this.val = null; this.prefixes = new Object(); this.tree = null; + this.scf = null; + this.scr = null; } CQLParser.prototype = { - parse: function (query) { + parse: function (query, scf, scr) { if (!query) throw new Error("The query to be parsed cannot be empty"); - + this.scf = typeof scf != 'string' + ? DEFAULT_SERVER_CHOICE_FIELD : scf; + this.scr = typeof scr != 'string' + ? DEFAULT_SERVER_CHOICE_RELATION : scr; this.qs = query; this.ql = this.qs.length; this.qi = 0; this._move(); - this.tree = this._parseQuery("cql.serverChoice", "scr", new Array()); + this.tree = this._parseQuery(this.scf, this.scr, new Array()); if (this.look != "") throw new Error("EOF expected"); }, - toXCQL: function () { - return this.tree.toXCQL(); + parseFromFQ: function (query, scf, scr) { + if (!query) + throw new Error("The query to be parsed cannot be empty"); + if (typeof query == 'string') + query = JSON.parse(query); + this.scf = typeof scf != 'string' + ? DEFAULT_SERVER_CHOICE_FIELD : scf; + this.scr = typeof scr != 'string' + ? DEFAULT_SERVER_CHOICE_RELATION : scr; + this.tree = this._parseFromFQ(query); + }, + _parseFromFQ: function (fq) { + //op-node + if (fq.hasOwnProperty('op') + && fq.hasOwnProperty('s1') + && fq.hasOwnProperty('s2')) { + var node = new CQLBoolean(); + node.op = fq.op; + node.left = this._parseFromFQ(fq.s1); + node.right = this._parseFromFQ(fq.s2); + //include all other members as modifiers + node.modifiers = []; + for (var key in fq) { + if (key == 'op' || key == 's1' || key == 's2') + continue; + var mod = new CQLModifier(); + mod.name = key; + mod.relation = '='; + mod.value = fq[key]; + node.modifiers.push(mod); + } + return node; + } + //search-clause node + if (fq.hasOwnProperty('term')) { + var node = new CQLSearchClause(); + node.term = fq.term; + node.scf = this.scf; + node.scr = this.scr; + node.field = fq.hasOwnProperty('field') + ? fq.field : this.scf; + node.relation = fq.hasOwnProperty('relation') + ? node._remapRelation(fq.relation) : this.scr; + //include all other members as modifiers + node.relationuri = ''; + node.fielduri = ''; + node.modifiers = []; + for (var key in fq) { + if (key == 'term' || key == 'field' || key == 'relation') + continue; + var mod = new CQLModifier(); + mod.name = key; + mod.relation = '='; + mod.value = fq[key]; + node.modifiers.push(mod); + } + return node; + } + throw new Error('Unknow node type; '+JSON.stringify(fq)); + }, + toXCQL: function (c) { + c = typeof c == "undefined" ? ' ' : c; + return this.tree.toXCQL(0, c); + }, + toFQ: function (c, nl) { + c = typeof c == "undefined" ? ' ' : c; + nl = typeof nl == "undefined" ? '\n' : c; + return this.tree.toFQ(0, c, nl); + }, + toString: function () { + return this.tree.toString(); }, _parseQuery: function(field, relation, modifiers) { var left = this._parseSearchClause(field, relation, modifiers); @@ -229,7 +399,9 @@ CQLParser.prototype = { relation, reluri, modifiers, - first); + first, + this.scf, + this.scr); return sc; } // prefixes @@ -259,41 +431,55 @@ CQLParser.prototype = { }, _move: function () { + //eat whitespace while (this.qi < this.ql && this._strchr(" \t\r\n", this.qs.charAt(this.qi))) this.qi++; + //eof if (this.qi == this.ql) { this.look = ""; return; } + //current char var c = this.qs.charAt(this.qi); + //separators if (this._strchr("()/", c)) { this.look = c; this.qi++; + //comparitor } else if (this._strchr("<>=", c)) { this.look = c; this.qi++; + //comparitors can repeat, could be if while (this.qi < this.ql && this._strchr("<>=", this.qs.charAt(this.qi))) { this.look = this.look + this.qs.charAt(this.qi); this.qi++; } + //quoted string } else if (this._strchr("\"'", c)) { this.look = "q"; + //remember quote char var mark = c; this.qi++; this.val = ""; - while (this.qi < this.ql - && this.qs.charAt(this.qi) != mark) { - if (this.qs.charAt(this.qi) == '\\' - && this.qi < this.ql-1) - this.qi++; - this.val = this.val + this.qs.charAt(this.qi); - this.qi++; + var escaped = false; + while (this.qi < this.ql) { + if (!escaped && this.qs.charAt(this.qi) == mark) + break; + if (!escaped && this.qs.charAt(this.qi) == '\\') + escaped = true; + else + escaped = false; + this.val += this.qs.charAt(this.qi); + this.qi++; } this.lval = this.val.toLowerCase(); if (this.qi < this.ql) this.qi++; + else //unterminated + this.look = ""; //notify error + //unquoted string } else { this.look = "s"; this.val = "";