diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 71705c788a..6e0e29e669 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -62,7 +62,8 @@ class Selector: class_selector: "." WORD filter ? filter: "[" filter_key (comparison filter_value)? "]" filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple - filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL + filter_value: filter_regex | ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL + filter_regex: "r" ESCAPED_STRING keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)* keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)* keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)* @@ -205,22 +206,30 @@ class Selector: comparison = filter_rule.children[1].children[0].data if comparison == "not": comparison += filter_rule.children[1].children[1].data - token_type = filter_rule.children[2].children[0].type - if token_type == "ESCAPED_STRING": - value = str(filter_rule.children[2].children[0][1:-1]) + filter_value = filter_rule.children[2].children[0] + if isinstance(filter_value, lark.Tree): + is_regex = True + token_type = filter_value.data + else: + is_regex = False + token_type = filter_value.type + if token_type == "filter_regex": + value = str(filter_value.children[0][1:-1]) + elif token_type == "ESCAPED_STRING": + value = str(filter_value[1:-1]) elif token_type == "SIGNED_INT": - value = int(filter_rule.children[2].children[0]) + value = int(filter_value) elif token_type == "SIGNED_FLOAT": - value = float(filter_rule.children[2].children[0]) + value = float(filter_value) elif token_type == "BOOLEAN": - value = filter_rule.children[2].children[0].lower() == "true" + value = filter_value.lower() == "true" elif token_type == "NULL": value = None for element in elements: element_value = cls.get_element_value(element, filter_query["keys"], is_regex=filter_query["is_regex"]) if element_value is None and value is not None and "not" not in comparison: continue - if comparison and cls.filter_element(element, element_value, comparison, value): + if comparison and cls.filter_element(element, element_value, comparison, value, is_regex=is_regex): results.append(element) elif not comparison and element_value: results.append(element) @@ -314,12 +323,19 @@ class Selector: return value @classmethod - def filter_element(cls, element, element_value, comparison, value): + def filter_element(cls, element, element_value, comparison, value, is_regex=False): if comparison.startswith("not"): - return not cls.filter_element(element, element_value, comparison[3:], value) + return not cls.filter_element(element, element_value, comparison[3:], value, is_regex=is_regex) elif comparison == "equal" and isinstance(element_value, list): + if is_regex: + for element_v in element_value: + if re.match(value, element_v): + return True + return False return value in element_value elif comparison == "equal": + if is_regex: + return bool(re.match(value, element_value)) return element_value == value elif comparison == "contains" and isinstance(element_value, list): return bool([ev for ev in element_value if value in str(ev)]) diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 54780279e0..c3b9c2c110 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -99,6 +99,12 @@ class TestSelector(test.bootstrap.IFC4): assert subject.Selector.parse(self.file, '.IfcElement[Name="Foobar"]') == [element] assert subject.Selector.parse(self.file, '.IfcElement[Name="Foobaz"]') == [] + def test_selecting_by_regex(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element.Name = "Foobar" + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + assert subject.Selector.parse(self.file, '.IfcElement[Name=r"Foo.*"]') == [element] + def test_selecting_by_property_existence(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")