Fix #4195. Fix regressions from f840da3 and add support for "contains string" comparisons.

This commit is contained in:
Dion Moult
2024-01-13 15:58:57 +11:00
parent b5f63ec4f3
commit ca8e014647
2 changed files with 69 additions and 41 deletions
@@ -55,19 +55,20 @@ filter_elements_grammar = lark.Lark(
ifc_class: /Ifc\\w+/
value: special | quoted_string | regex_string | unquoted_string
unquoted_string: /[^,.=\\s]+/
unquoted_string: /[^,.=><*!\\s]+/
regex_string: "/" /[^\\/]+/ "/"
quoted_string: ESCAPED_STRING
special: null | true | false
comparison: not? equals | morethanequalto | lessthanequalto | morethan | lessthan
comparison: not? equals | morethanequalto | lessthanequalto | morethan | lessthan | not? contains
not: "!"
equals: "="
morethanequalto: ">="
lessthanequalto: "<="
morethan: ">"
lessthan: "<"
contains: "*="
null: "NULL"
true: "TRUE"
false: "FALSE"
@@ -557,18 +558,24 @@ class FacetTransformer(lark.Transformer):
return tree
def comparison(self, args):
if args[0].data == "equals":
return "="
elif args[0].data == "morethanequalto":
return ">="
elif args[0].data == "lessthanequalto":
return "<="
elif args[0].data == "morethan":
return ">"
elif args[0].data == "lessthan":
return "<"
if args[0].data == "not":
comparison = args[1].data
is_not = "!"
else:
return "!="
comparison = args[0].data
is_not = ""
return (
is_not
+ {
"equals": "=",
"morethanequalto": ">=",
"lessthanequalto": "<=",
"morethan": ">",
"lessthan": "<",
"contains": "*=",
}[comparison]
)
def keys(self, args):
return self.value(args)
@@ -595,35 +602,42 @@ class FacetTransformer(lark.Transformer):
return False
def compare(self, element_value, comparison, value):
if element_value:
if isinstance(element_value, (list, tuple)):
return any(self.compare(ev, comparison, value) for ev in element_value)
elif isinstance(value, re.Pattern):
result = bool(value.match(element_value)) if element_value is not None else False
elif isinstance(value, str):
if isinstance(element_value, int):
value = int(value)
elif isinstance(element_value, float):
value = float(value)
if comparison == "=":
result = element_value == value
elif comparison == ">":
result = element_value > value
elif comparison == "<":
result = element_value < value
elif comparison == ">=":
result = element_value >= value
elif comparison == "<= ":
result = element_value <= value
else:
result = element_value != value
if isinstance(element_value, (list, tuple)):
return any(self.compare(ev, comparison, value) for ev in element_value)
elif isinstance(value, str):
if isinstance(element_value, int):
value = int(value)
elif isinstance(element_value, float):
value = float(value)
elif value in (None, True, False):
result = element_value is value
else:
result = False
return result
if isinstance(element_value, (int, float)):
operator = comparison.lstrip("!")
if operator == ">=":
result = element_value >= value
elif operator == "<=":
result = element_value <= value
elif operator == ">":
result = element_value > value
elif operator == "<":
result = element_value < value
else:
result = element_value == value # Tolerance?
elif isinstance(element_value, str):
operator = comparison.lstrip("!")
if operator == "*=":
result = value in element_value
else:
result = element_value == value
else:
result = element_value == value
elif isinstance(value, re.Pattern):
result = bool(value.match(element_value)) if element_value is not None else False
elif value in (None, True, False):
result = element_value is value
if comparison.startswith("!"):
return not result
return result
class Selector:
@@ -192,6 +192,20 @@ class TestFilterElements(test.bootstrap.IFC4):
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Status": ["New"]})
assert subject.filter_elements(self.file, "IfcWall, Pset_WallCommon.Status=New") == {element}
def test_selecting_by_property_with_comparisons(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobar")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Baz": 123})
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz>100") == {element}
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz<100") == set()
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz>=100") == {element}
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz<=100") == set()
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo*=ar") == {element}
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo!*=ar") == {element2}
assert subject.filter_elements(self.file, "IfcWall, Foobar.Foo*=Foo") == set()
def test_selecting_by_classification(self):
project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")