CQL.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Format/WKT.js
  7. * @requires OpenLayers/Filter/Comparison.js
  8. * @requires OpenLayers/Filter/Logical.js
  9. * @requires OpenLayers/Filter/Spatial.js
  10. */
  11. /**
  12. * Class: OpenLayers.Format.CQL
  13. * Read CQL strings to get <OpenLayers.Filter> objects. Write
  14. * <OpenLayers.Filter> objects to get CQL strings. Create a new parser with
  15. * the <OpenLayers.Format.CQL> constructor.
  16. *
  17. * Inherits from:
  18. * - <OpenLayers.Format>
  19. */
  20. OpenLayers.Format.CQL = (function() {
  21. var tokens = [
  22. "PROPERTY", "COMPARISON", "VALUE", "LOGICAL"
  23. ],
  24. patterns = {
  25. PROPERTY: /^[_a-zA-Z]\w*/,
  26. COMPARISON: /^(=|<>|<=|<|>=|>|LIKE)/i,
  27. IS_NULL: /^IS NULL/i,
  28. COMMA: /^,/,
  29. LOGICAL: /^(AND|OR)/i,
  30. VALUE: /^('([^']|'')*'|\d+(\.\d*)?|\.\d+)/,
  31. LPAREN: /^\(/,
  32. RPAREN: /^\)/,
  33. SPATIAL: /^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,
  34. NOT: /^NOT/i,
  35. BETWEEN: /^BETWEEN/i,
  36. GEOMETRY: function(text) {
  37. var type = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(text);
  38. if (type) {
  39. var len = text.length;
  40. var idx = text.indexOf("(", type[0].length);
  41. if (idx > -1) {
  42. var depth = 1;
  43. while (idx < len && depth > 0) {
  44. idx++;
  45. switch(text.charAt(idx)) {
  46. case '(':
  47. depth++;
  48. break;
  49. case ')':
  50. depth--;
  51. break;
  52. default:
  53. // in default case, do nothing
  54. }
  55. }
  56. }
  57. return [text.substr(0, idx+1)];
  58. }
  59. },
  60. END: /^$/
  61. },
  62. follows = {
  63. LPAREN: ['GEOMETRY', 'SPATIAL', 'PROPERTY', 'VALUE', 'LPAREN'],
  64. RPAREN: ['NOT', 'LOGICAL', 'END', 'RPAREN'],
  65. PROPERTY: ['COMPARISON', 'BETWEEN', 'COMMA', 'IS_NULL'],
  66. BETWEEN: ['VALUE'],
  67. IS_NULL: ['END'],
  68. COMPARISON: ['VALUE'],
  69. COMMA: ['GEOMETRY', 'VALUE', 'PROPERTY'],
  70. VALUE: ['LOGICAL', 'COMMA', 'RPAREN', 'END'],
  71. SPATIAL: ['LPAREN'],
  72. LOGICAL: ['NOT', 'VALUE', 'SPATIAL', 'PROPERTY', 'LPAREN'],
  73. NOT: ['PROPERTY', 'LPAREN'],
  74. GEOMETRY: ['COMMA', 'RPAREN']
  75. },
  76. operators = {
  77. '=': OpenLayers.Filter.Comparison.EQUAL_TO,
  78. '<>': OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
  79. '<': OpenLayers.Filter.Comparison.LESS_THAN,
  80. '<=': OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
  81. '>': OpenLayers.Filter.Comparison.GREATER_THAN,
  82. '>=': OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
  83. 'LIKE': OpenLayers.Filter.Comparison.LIKE,
  84. 'BETWEEN': OpenLayers.Filter.Comparison.BETWEEN,
  85. 'IS NULL': OpenLayers.Filter.Comparison.IS_NULL
  86. },
  87. operatorReverse = {},
  88. logicals = {
  89. 'AND': OpenLayers.Filter.Logical.AND,
  90. 'OR': OpenLayers.Filter.Logical.OR
  91. },
  92. logicalReverse = {},
  93. precedence = {
  94. 'RPAREN': 3,
  95. 'LOGICAL': 2,
  96. 'COMPARISON': 1
  97. };
  98. var i;
  99. for (i in operators) {
  100. if (operators.hasOwnProperty(i)) {
  101. operatorReverse[operators[i]] = i;
  102. }
  103. }
  104. for (i in logicals) {
  105. if (logicals.hasOwnProperty(i)) {
  106. logicalReverse[logicals[i]] = i;
  107. }
  108. }
  109. function tryToken(text, pattern) {
  110. if (pattern instanceof RegExp) {
  111. return pattern.exec(text);
  112. } else {
  113. return pattern(text);
  114. }
  115. }
  116. function nextToken(text, tokens) {
  117. var i, token, len = tokens.length;
  118. for (i=0; i<len; i++) {
  119. token = tokens[i];
  120. var pat = patterns[token];
  121. var matches = tryToken(text, pat);
  122. if (matches) {
  123. var match = matches[0];
  124. var remainder = text.substr(match.length).replace(/^\s*/, "");
  125. return {
  126. type: token,
  127. text: match,
  128. remainder: remainder
  129. };
  130. }
  131. }
  132. var msg = "ERROR: In parsing: [" + text + "], expected one of: ";
  133. for (i=0; i<len; i++) {
  134. token = tokens[i];
  135. msg += "\n " + token + ": " + patterns[token];
  136. }
  137. throw new Error(msg);
  138. }
  139. function tokenize(text) {
  140. var results = [];
  141. var token, expect = ["NOT", "GEOMETRY", "SPATIAL", "PROPERTY", "LPAREN"];
  142. do {
  143. token = nextToken(text, expect);
  144. text = token.remainder;
  145. expect = follows[token.type];
  146. if (token.type != "END" && !expect) {
  147. throw new Error("No follows list for " + token.type);
  148. }
  149. results.push(token);
  150. } while (token.type != "END");
  151. return results;
  152. }
  153. function buildAst(tokens) {
  154. var operatorStack = [],
  155. postfix = [];
  156. while (tokens.length) {
  157. var tok = tokens.shift();
  158. switch (tok.type) {
  159. case "PROPERTY":
  160. case "GEOMETRY":
  161. case "VALUE":
  162. postfix.push(tok);
  163. break;
  164. case "COMPARISON":
  165. case "BETWEEN":
  166. case "IS_NULL":
  167. case "LOGICAL":
  168. var p = precedence[tok.type];
  169. while (operatorStack.length > 0 &&
  170. (precedence[operatorStack[operatorStack.length - 1].type] <= p)
  171. ) {
  172. postfix.push(operatorStack.pop());
  173. }
  174. operatorStack.push(tok);
  175. break;
  176. case "SPATIAL":
  177. case "NOT":
  178. case "LPAREN":
  179. operatorStack.push(tok);
  180. break;
  181. case "RPAREN":
  182. while (operatorStack.length > 0 &&
  183. (operatorStack[operatorStack.length - 1].type != "LPAREN")
  184. ) {
  185. postfix.push(operatorStack.pop());
  186. }
  187. operatorStack.pop(); // toss out the LPAREN
  188. if (operatorStack.length > 0 &&
  189. operatorStack[operatorStack.length-1].type == "SPATIAL") {
  190. postfix.push(operatorStack.pop());
  191. }
  192. case "COMMA":
  193. case "END":
  194. break;
  195. default:
  196. throw new Error("Unknown token type " + tok.type);
  197. }
  198. }
  199. while (operatorStack.length > 0) {
  200. postfix.push(operatorStack.pop());
  201. }
  202. function buildTree() {
  203. var tok = postfix.pop();
  204. switch (tok.type) {
  205. case "LOGICAL":
  206. var rhs = buildTree(),
  207. lhs = buildTree();
  208. return new OpenLayers.Filter.Logical({
  209. filters: [lhs, rhs],
  210. type: logicals[tok.text.toUpperCase()]
  211. });
  212. case "NOT":
  213. var operand = buildTree();
  214. return new OpenLayers.Filter.Logical({
  215. filters: [operand],
  216. type: OpenLayers.Filter.Logical.NOT
  217. });
  218. case "BETWEEN":
  219. var min, max, property;
  220. postfix.pop(); // unneeded AND token here
  221. max = buildTree();
  222. min = buildTree();
  223. property = buildTree();
  224. return new OpenLayers.Filter.Comparison({
  225. property: property,
  226. lowerBoundary: min,
  227. upperBoundary: max,
  228. type: OpenLayers.Filter.Comparison.BETWEEN
  229. });
  230. case "COMPARISON":
  231. var value = buildTree(),
  232. property = buildTree();
  233. return new OpenLayers.Filter.Comparison({
  234. property: property,
  235. value: value,
  236. type: operators[tok.text.toUpperCase()]
  237. });
  238. case "IS_NULL":
  239. var property = buildTree();
  240. return new OpenLayers.Filter.Comparison({
  241. property: property,
  242. type: operators[tok.text.toUpperCase()]
  243. });
  244. case "VALUE":
  245. var match = tok.text.match(/^'(.*)'$/);
  246. if (match) {
  247. return match[1].replace(/''/g, "'");
  248. } else {
  249. return Number(tok.text);
  250. }
  251. case "SPATIAL":
  252. switch(tok.text.toUpperCase()) {
  253. case "BBOX":
  254. var maxy = buildTree(),
  255. maxx = buildTree(),
  256. miny = buildTree(),
  257. minx = buildTree(),
  258. prop = buildTree();
  259. return new OpenLayers.Filter.Spatial({
  260. type: OpenLayers.Filter.Spatial.BBOX,
  261. property: prop,
  262. value: OpenLayers.Bounds.fromArray(
  263. [minx, miny, maxx, maxy]
  264. )
  265. });
  266. case "INTERSECTS":
  267. var value = buildTree(),
  268. property = buildTree();
  269. return new OpenLayers.Filter.Spatial({
  270. type: OpenLayers.Filter.Spatial.INTERSECTS,
  271. property: property,
  272. value: value
  273. });
  274. case "WITHIN":
  275. var value = buildTree(),
  276. property = buildTree();
  277. return new OpenLayers.Filter.Spatial({
  278. type: OpenLayers.Filter.Spatial.WITHIN,
  279. property: property,
  280. value: value
  281. });
  282. case "CONTAINS":
  283. var value = buildTree(),
  284. property = buildTree();
  285. return new OpenLayers.Filter.Spatial({
  286. type: OpenLayers.Filter.Spatial.CONTAINS,
  287. property: property,
  288. value: value
  289. });
  290. case "DWITHIN":
  291. var distance = buildTree(),
  292. value = buildTree(),
  293. property = buildTree();
  294. return new OpenLayers.Filter.Spatial({
  295. type: OpenLayers.Filter.Spatial.DWITHIN,
  296. value: value,
  297. property: property,
  298. distance: Number(distance)
  299. });
  300. }
  301. case "GEOMETRY":
  302. return OpenLayers.Geometry.fromWKT(tok.text);
  303. default:
  304. return tok.text;
  305. }
  306. }
  307. var result = buildTree();
  308. if (postfix.length > 0) {
  309. var msg = "Remaining tokens after building AST: \n";
  310. for (var i = postfix.length - 1; i >= 0; i--) {
  311. msg += postfix[i].type + ": " + postfix[i].text + "\n";
  312. }
  313. throw new Error(msg);
  314. }
  315. return result;
  316. }
  317. return OpenLayers.Class(OpenLayers.Format, {
  318. /**
  319. * APIMethod: read
  320. * Generate a filter from a CQL string.
  321. * Parameters:
  322. * text - {String} The CQL text.
  323. *
  324. * Returns:
  325. * {<OpenLayers.Filter>} A filter based on the CQL text.
  326. */
  327. read: function(text) {
  328. var result = buildAst(tokenize(text));
  329. if (this.keepData) {
  330. this.data = result;
  331. }
  332. return result;
  333. },
  334. /**
  335. * APIMethod: write
  336. * Convert a filter into a CQL string.
  337. * Parameters:
  338. * filter - {<OpenLayers.Filter>} The filter.
  339. *
  340. * Returns:
  341. * {String} A CQL string based on the filter.
  342. */
  343. write: function(filter) {
  344. if (filter instanceof OpenLayers.Geometry) {
  345. return filter.toString();
  346. }
  347. switch (filter.CLASS_NAME) {
  348. case "OpenLayers.Filter.Spatial":
  349. switch(filter.type) {
  350. case OpenLayers.Filter.Spatial.BBOX:
  351. return "BBOX(" +
  352. filter.property + "," +
  353. filter.value.toBBOX() +
  354. ")";
  355. case OpenLayers.Filter.Spatial.DWITHIN:
  356. return "DWITHIN(" +
  357. filter.property + ", " +
  358. this.write(filter.value) + ", " +
  359. filter.distance + ")";
  360. case OpenLayers.Filter.Spatial.WITHIN:
  361. return "WITHIN(" +
  362. filter.property + ", " +
  363. this.write(filter.value) + ")";
  364. case OpenLayers.Filter.Spatial.INTERSECTS:
  365. return "INTERSECTS(" +
  366. filter.property + ", " +
  367. this.write(filter.value) + ")";
  368. case OpenLayers.Filter.Spatial.CONTAINS:
  369. return "CONTAINS(" +
  370. filter.property + ", " +
  371. this.write(filter.value) + ")";
  372. default:
  373. throw new Error("Unknown spatial filter type: " + filter.type);
  374. }
  375. case "OpenLayers.Filter.Logical":
  376. if (filter.type == OpenLayers.Filter.Logical.NOT) {
  377. // TODO: deal with precedence of logical operators to
  378. // avoid extra parentheses (not urgent)
  379. return "NOT (" + this.write(filter.filters[0]) + ")";
  380. } else {
  381. var res = "(";
  382. var first = true;
  383. for (var i = 0; i < filter.filters.length; i++) {
  384. if (first) {
  385. first = false;
  386. } else {
  387. res += ") " + logicalReverse[filter.type] + " (";
  388. }
  389. res += this.write(filter.filters[i]);
  390. }
  391. return res + ")";
  392. }
  393. case "OpenLayers.Filter.Comparison":
  394. if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) {
  395. return filter.property + " BETWEEN " +
  396. this.write(filter.lowerBoundary) + " AND " +
  397. this.write(filter.upperBoundary);
  398. } else {
  399. return (filter.value !== null) ? filter.property +
  400. " " + operatorReverse[filter.type] + " " +
  401. this.write(filter.value) : filter.property +
  402. " " + operatorReverse[filter.type];
  403. }
  404. case undefined:
  405. if (typeof filter === "string") {
  406. return "'" + filter.replace(/'/g, "''") + "'";
  407. } else if (typeof filter === "number") {
  408. return String(filter);
  409. }
  410. default:
  411. throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter);
  412. }
  413. },
  414. CLASS_NAME: "OpenLayers.Format.CQL"
  415. });
  416. })();