diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Format/CQL.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Format/CQL.js | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Format/CQL.js b/misc/openlayers/lib/OpenLayers/Format/CQL.js new file mode 100644 index 0000000..8430a8b --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Format/CQL.js @@ -0,0 +1,452 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/WKT.js + * @requires OpenLayers/Filter/Comparison.js + * @requires OpenLayers/Filter/Logical.js + * @requires OpenLayers/Filter/Spatial.js + */ + +/** + * Class: OpenLayers.Format.CQL + * Read CQL strings to get <OpenLayers.Filter> objects. Write + * <OpenLayers.Filter> objects to get CQL strings. Create a new parser with + * the <OpenLayers.Format.CQL> constructor. + * + * Inherits from: + * - <OpenLayers.Format> + */ +OpenLayers.Format.CQL = (function() { + + var tokens = [ + "PROPERTY", "COMPARISON", "VALUE", "LOGICAL" + ], + + patterns = { + PROPERTY: /^[_a-zA-Z]\w*/, + COMPARISON: /^(=|<>|<=|<|>=|>|LIKE)/i, + IS_NULL: /^IS NULL/i, + COMMA: /^,/, + LOGICAL: /^(AND|OR)/i, + VALUE: /^('([^']|'')*'|\d+(\.\d*)?|\.\d+)/, + LPAREN: /^\(/, + RPAREN: /^\)/, + SPATIAL: /^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i, + NOT: /^NOT/i, + BETWEEN: /^BETWEEN/i, + GEOMETRY: function(text) { + var type = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(text); + if (type) { + var len = text.length; + var idx = text.indexOf("(", type[0].length); + if (idx > -1) { + var depth = 1; + while (idx < len && depth > 0) { + idx++; + switch(text.charAt(idx)) { + case '(': + depth++; + break; + case ')': + depth--; + break; + default: + // in default case, do nothing + } + } + } + return [text.substr(0, idx+1)]; + } + }, + END: /^$/ + }, + + follows = { + LPAREN: ['GEOMETRY', 'SPATIAL', 'PROPERTY', 'VALUE', 'LPAREN'], + RPAREN: ['NOT', 'LOGICAL', 'END', 'RPAREN'], + PROPERTY: ['COMPARISON', 'BETWEEN', 'COMMA', 'IS_NULL'], + BETWEEN: ['VALUE'], + IS_NULL: ['END'], + COMPARISON: ['VALUE'], + COMMA: ['GEOMETRY', 'VALUE', 'PROPERTY'], + VALUE: ['LOGICAL', 'COMMA', 'RPAREN', 'END'], + SPATIAL: ['LPAREN'], + LOGICAL: ['NOT', 'VALUE', 'SPATIAL', 'PROPERTY', 'LPAREN'], + NOT: ['PROPERTY', 'LPAREN'], + GEOMETRY: ['COMMA', 'RPAREN'] + }, + + operators = { + '=': OpenLayers.Filter.Comparison.EQUAL_TO, + '<>': OpenLayers.Filter.Comparison.NOT_EQUAL_TO, + '<': OpenLayers.Filter.Comparison.LESS_THAN, + '<=': OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO, + '>': OpenLayers.Filter.Comparison.GREATER_THAN, + '>=': OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO, + 'LIKE': OpenLayers.Filter.Comparison.LIKE, + 'BETWEEN': OpenLayers.Filter.Comparison.BETWEEN, + 'IS NULL': OpenLayers.Filter.Comparison.IS_NULL + }, + + operatorReverse = {}, + + logicals = { + 'AND': OpenLayers.Filter.Logical.AND, + 'OR': OpenLayers.Filter.Logical.OR + }, + + logicalReverse = {}, + + precedence = { + 'RPAREN': 3, + 'LOGICAL': 2, + 'COMPARISON': 1 + }; + + var i; + for (i in operators) { + if (operators.hasOwnProperty(i)) { + operatorReverse[operators[i]] = i; + } + } + + for (i in logicals) { + if (logicals.hasOwnProperty(i)) { + logicalReverse[logicals[i]] = i; + } + } + + function tryToken(text, pattern) { + if (pattern instanceof RegExp) { + return pattern.exec(text); + } else { + return pattern(text); + } + } + + function nextToken(text, tokens) { + var i, token, len = tokens.length; + for (i=0; i<len; i++) { + token = tokens[i]; + var pat = patterns[token]; + var matches = tryToken(text, pat); + if (matches) { + var match = matches[0]; + var remainder = text.substr(match.length).replace(/^\s*/, ""); + return { + type: token, + text: match, + remainder: remainder + }; + } + } + + var msg = "ERROR: In parsing: [" + text + "], expected one of: "; + for (i=0; i<len; i++) { + token = tokens[i]; + msg += "\n " + token + ": " + patterns[token]; + } + + throw new Error(msg); + } + + function tokenize(text) { + var results = []; + var token, expect = ["NOT", "GEOMETRY", "SPATIAL", "PROPERTY", "LPAREN"]; + + do { + token = nextToken(text, expect); + text = token.remainder; + expect = follows[token.type]; + if (token.type != "END" && !expect) { + throw new Error("No follows list for " + token.type); + } + results.push(token); + } while (token.type != "END"); + + return results; + } + + function buildAst(tokens) { + var operatorStack = [], + postfix = []; + + while (tokens.length) { + var tok = tokens.shift(); + switch (tok.type) { + case "PROPERTY": + case "GEOMETRY": + case "VALUE": + postfix.push(tok); + break; + case "COMPARISON": + case "BETWEEN": + case "IS_NULL": + case "LOGICAL": + var p = precedence[tok.type]; + + while (operatorStack.length > 0 && + (precedence[operatorStack[operatorStack.length - 1].type] <= p) + ) { + postfix.push(operatorStack.pop()); + } + + operatorStack.push(tok); + break; + case "SPATIAL": + case "NOT": + case "LPAREN": + operatorStack.push(tok); + break; + case "RPAREN": + while (operatorStack.length > 0 && + (operatorStack[operatorStack.length - 1].type != "LPAREN") + ) { + postfix.push(operatorStack.pop()); + } + operatorStack.pop(); // toss out the LPAREN + + if (operatorStack.length > 0 && + operatorStack[operatorStack.length-1].type == "SPATIAL") { + postfix.push(operatorStack.pop()); + } + case "COMMA": + case "END": + break; + default: + throw new Error("Unknown token type " + tok.type); + } + } + + while (operatorStack.length > 0) { + postfix.push(operatorStack.pop()); + } + + function buildTree() { + var tok = postfix.pop(); + switch (tok.type) { + case "LOGICAL": + var rhs = buildTree(), + lhs = buildTree(); + return new OpenLayers.Filter.Logical({ + filters: [lhs, rhs], + type: logicals[tok.text.toUpperCase()] + }); + case "NOT": + var operand = buildTree(); + return new OpenLayers.Filter.Logical({ + filters: [operand], + type: OpenLayers.Filter.Logical.NOT + }); + case "BETWEEN": + var min, max, property; + postfix.pop(); // unneeded AND token here + max = buildTree(); + min = buildTree(); + property = buildTree(); + return new OpenLayers.Filter.Comparison({ + property: property, + lowerBoundary: min, + upperBoundary: max, + type: OpenLayers.Filter.Comparison.BETWEEN + }); + case "COMPARISON": + var value = buildTree(), + property = buildTree(); + return new OpenLayers.Filter.Comparison({ + property: property, + value: value, + type: operators[tok.text.toUpperCase()] + }); + case "IS_NULL": + var property = buildTree(); + return new OpenLayers.Filter.Comparison({ + property: property, + type: operators[tok.text.toUpperCase()] + }); + case "VALUE": + var match = tok.text.match(/^'(.*)'$/); + if (match) { + return match[1].replace(/''/g, "'"); + } else { + return Number(tok.text); + } + case "SPATIAL": + switch(tok.text.toUpperCase()) { + case "BBOX": + var maxy = buildTree(), + maxx = buildTree(), + miny = buildTree(), + minx = buildTree(), + prop = buildTree(); + + return new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.BBOX, + property: prop, + value: OpenLayers.Bounds.fromArray( + [minx, miny, maxx, maxy] + ) + }); + case "INTERSECTS": + var value = buildTree(), + property = buildTree(); + return new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.INTERSECTS, + property: property, + value: value + }); + case "WITHIN": + var value = buildTree(), + property = buildTree(); + return new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.WITHIN, + property: property, + value: value + }); + case "CONTAINS": + var value = buildTree(), + property = buildTree(); + return new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.CONTAINS, + property: property, + value: value + }); + case "DWITHIN": + var distance = buildTree(), + value = buildTree(), + property = buildTree(); + return new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.DWITHIN, + value: value, + property: property, + distance: Number(distance) + }); + } + case "GEOMETRY": + return OpenLayers.Geometry.fromWKT(tok.text); + default: + return tok.text; + } + } + + var result = buildTree(); + if (postfix.length > 0) { + var msg = "Remaining tokens after building AST: \n"; + for (var i = postfix.length - 1; i >= 0; i--) { + msg += postfix[i].type + ": " + postfix[i].text + "\n"; + } + throw new Error(msg); + } + + return result; + } + + return OpenLayers.Class(OpenLayers.Format, { + /** + * APIMethod: read + * Generate a filter from a CQL string. + + * Parameters: + * text - {String} The CQL text. + * + * Returns: + * {<OpenLayers.Filter>} A filter based on the CQL text. + */ + read: function(text) { + var result = buildAst(tokenize(text)); + if (this.keepData) { + this.data = result; + } + return result; + }, + + /** + * APIMethod: write + * Convert a filter into a CQL string. + + * Parameters: + * filter - {<OpenLayers.Filter>} The filter. + * + * Returns: + * {String} A CQL string based on the filter. + */ + write: function(filter) { + if (filter instanceof OpenLayers.Geometry) { + return filter.toString(); + } + switch (filter.CLASS_NAME) { + case "OpenLayers.Filter.Spatial": + switch(filter.type) { + case OpenLayers.Filter.Spatial.BBOX: + return "BBOX(" + + filter.property + "," + + filter.value.toBBOX() + + ")"; + case OpenLayers.Filter.Spatial.DWITHIN: + return "DWITHIN(" + + filter.property + ", " + + this.write(filter.value) + ", " + + filter.distance + ")"; + case OpenLayers.Filter.Spatial.WITHIN: + return "WITHIN(" + + filter.property + ", " + + this.write(filter.value) + ")"; + case OpenLayers.Filter.Spatial.INTERSECTS: + return "INTERSECTS(" + + filter.property + ", " + + this.write(filter.value) + ")"; + case OpenLayers.Filter.Spatial.CONTAINS: + return "CONTAINS(" + + filter.property + ", " + + this.write(filter.value) + ")"; + default: + throw new Error("Unknown spatial filter type: " + filter.type); + } + case "OpenLayers.Filter.Logical": + if (filter.type == OpenLayers.Filter.Logical.NOT) { + // TODO: deal with precedence of logical operators to + // avoid extra parentheses (not urgent) + return "NOT (" + this.write(filter.filters[0]) + ")"; + } else { + var res = "("; + var first = true; + for (var i = 0; i < filter.filters.length; i++) { + if (first) { + first = false; + } else { + res += ") " + logicalReverse[filter.type] + " ("; + } + res += this.write(filter.filters[i]); + } + return res + ")"; + } + case "OpenLayers.Filter.Comparison": + if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) { + return filter.property + " BETWEEN " + + this.write(filter.lowerBoundary) + " AND " + + this.write(filter.upperBoundary); + } else { + return (filter.value !== null) ? filter.property + + " " + operatorReverse[filter.type] + " " + + this.write(filter.value) : filter.property + + " " + operatorReverse[filter.type]; + } + case undefined: + if (typeof filter === "string") { + return "'" + filter.replace(/'/g, "''") + "'"; + } else if (typeof filter === "number") { + return String(filter); + } + default: + throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter); + } + }, + + CLASS_NAME: "OpenLayers.Format.CQL" + + }); +})(); + |