/* 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 objects. Write * objects to get CQL strings. Create a new parser with * the constructor. * * Inherits from: * - */ 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 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: * {} 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 - {} 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" }); })();