diff --git a/pygeofilter/cql2.py b/pygeofilter/cql2.py index 53cf0ec..92ce542 100644 --- a/pygeofilter/cql2.py +++ b/pygeofilter/cql2.py @@ -56,6 +56,7 @@ "a_equals": ast.ArrayEquals, "a_contains": ast.ArrayContains, "a_containedby": ast.ArrayContainedBy, + "a_containedBy": ast.ArrayContainedBy, "a_overlaps": ast.ArrayOverlaps, } diff --git a/pygeofilter/parsers/cql2_text/grammar.lark b/pygeofilter/parsers/cql2_text/grammar.lark index 703cb43..0b0a571 100644 --- a/pygeofilter/parsers/cql2_text/grammar.lark +++ b/pygeofilter/parsers/cql2_text/grammar.lark @@ -61,6 +61,7 @@ | "EXCLUDE"i -> exclude | spatial_predicate | temporal_predicate + | array_predicate ?temporal_predicate: expression _binary_temporal_predicate_func expression -> binary_temporal_predicate @@ -95,6 +96,14 @@ | "S_EQUALS"i +?array_predicate: _array_predicate_func "(" expression "," expression ")" -> binary_array_predicate + +!_array_predicate_func: "A_EQUALS"i + | "A_CONTAINS"i + | "A_CONTAINEDBY"i + | "A_OVERLAPS"i + + ?expression: sum ?sum: product @@ -109,6 +118,8 @@ | attribute | literal | "-" atom -> neg + | "(" ")" -> empty_array + | "(" expression ("," expression)+ ")" -> array | "(" expression ")" func.2: attribute "(" expression ("," expression)* ")" -> function diff --git a/pygeofilter/parsers/cql2_text/parser.py b/pygeofilter/parsers/cql2_text/parser.py index cced284..83ab00a 100644 --- a/pygeofilter/parsers/cql2_text/parser.py +++ b/pygeofilter/parsers/cql2_text/parser.py @@ -31,7 +31,7 @@ from lark import Lark, logger, v_args from ... import ast, values -from ...cql2 import SPATIAL_PREDICATES_MAP, TEMPORAL_PREDICATES_MAP +from ...cql2 import ARRAY_PREDICATES_MAP, SPATIAL_PREDICATES_MAP, TEMPORAL_PREDICATES_MAP from ..iso8601 import ISO8601Transformer from ..wkt import WKTTransformer @@ -124,6 +124,15 @@ def during_or_after(self, node, period): def after(self, node, dt): return ast.TimeAfter(node, dt) + def binary_array_predicate(self, func, lhs, rhs): + return ARRAY_PREDICATES_MAP[func.lower()](lhs, rhs) + + def array(self, *items): + return list(items) + + def empty_array(self): + return [] + def binary_spatial_predicate(self, op, lhs, rhs): op = op.lower() return SPATIAL_PREDICATES_MAP[op](lhs, rhs) diff --git a/tests/parsers/cql2_text/test_parser.py b/tests/parsers/cql2_text/test_parser.py index 7d4c38e..386f3f0 100644 --- a/tests/parsers/cql2_text/test_parser.py +++ b/tests/parsers/cql2_text/test_parser.py @@ -477,3 +477,23 @@ def test_not_eq(): assert result == ast.Not( ast.Equal(ast.Attribute("attr"), 2) ) + + +def test_array_equals(): + result = parse("A_EQUALS(attr, (1, 2, 3))") + assert result == ast.ArrayEquals(ast.Attribute("attr"), [1, 2, 3]) + + +def test_array_contains(): + result = parse("A_CONTAINS(attr, (1, 2, 3))") + assert result == ast.ArrayContains(ast.Attribute("attr"), [1, 2, 3]) + + +def test_array_overlaps(): + result = parse("A_OVERLAPS(attr, (1, 2, 3))") + assert result == ast.ArrayOverlaps(ast.Attribute("attr"), [1, 2, 3]) + + +def test_array_empty(): + result = parse("A_EQUALS(attr, ())") + assert result == ast.ArrayEquals(ast.Attribute("attr"), [])