mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-24 20:00:02 +00:00
Fix #2435. The selector now supports regex filtering.
This commit is contained in:
@@ -16,10 +16,11 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import re
|
||||||
|
import lark
|
||||||
import ifcopenshell.util
|
import ifcopenshell.util
|
||||||
import ifcopenshell.util.fm
|
import ifcopenshell.util.fm
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import lark
|
|
||||||
|
|
||||||
|
|
||||||
class Selector:
|
class Selector:
|
||||||
@@ -35,8 +36,9 @@ class Selector:
|
|||||||
guid_selector: "#" /[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$]{22}/
|
guid_selector: "#" /[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$]{22}/
|
||||||
class_selector: "." WORD filter ?
|
class_selector: "." WORD filter ?
|
||||||
filter: "[" filter_key (comparison filter_value)? "]"
|
filter: "[" filter_key (comparison filter_value)? "]"
|
||||||
filter_key: WORD | ESCAPED_STRING | keys_quoted | keys_simple
|
filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
||||||
filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL
|
filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL
|
||||||
|
keys_regex: "r" ESCAPED_STRING "." ESCAPED_STRING
|
||||||
keys_quoted: ESCAPED_STRING "." ESCAPED_STRING
|
keys_quoted: ESCAPED_STRING "." ESCAPED_STRING
|
||||||
keys_simple: /[^\\W][^.=<>]*[^\\W]/ "." /[^\\W][^.=<>]*[^\\W]/
|
keys_simple: /[^\\W][^.=<>]*[^\\W]/ "." /[^\\W][^.=<>]*[^\\W]/
|
||||||
lfunction: and | or
|
lfunction: and | or
|
||||||
@@ -179,15 +181,17 @@ class Selector:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def filter_elements(cls, elements, filter_rule):
|
def filter_elements(cls, elements, filter_rule):
|
||||||
results = []
|
results = []
|
||||||
key = filter_rule.children[0].children[0]
|
keys = filter_rule.children[0].children[0]
|
||||||
if not isinstance(key, str):
|
is_regex = False
|
||||||
k0 = key.children[0]
|
if isinstance(keys, str):
|
||||||
if k0.type == "ESCAPED_STRING":
|
keys = [keys]
|
||||||
k0 = k0[1:-1].replace("\\\"", '"')
|
elif keys.data == "keys_regex":
|
||||||
k1 = key.children[1]
|
is_regex = True
|
||||||
if k1.type == "ESCAPED_STRING":
|
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
||||||
k1 = k1[1:-1].replace("\\\"", '"')
|
elif keys.data == "keys_quoted":
|
||||||
key = [k0, k1]
|
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
||||||
|
elif keys.data == "keys_simple":
|
||||||
|
keys = [keys.children[0], keys.children[1]]
|
||||||
comparison = value = None
|
comparison = value = None
|
||||||
if len(filter_rule.children) > 1:
|
if len(filter_rule.children) > 1:
|
||||||
comparison = filter_rule.children[1].children[0].data
|
comparison = filter_rule.children[1].children[0].data
|
||||||
@@ -205,7 +209,7 @@ class Selector:
|
|||||||
elif token_type == "NULL":
|
elif token_type == "NULL":
|
||||||
value = None
|
value = None
|
||||||
for element in elements:
|
for element in elements:
|
||||||
element_value = cls.get_element_value(element, key, value)
|
element_value = cls.get_element_value(element, keys, is_regex=is_regex)
|
||||||
if element_value is None and value is not None and "not" not in comparison:
|
if element_value is None and value is not None and "not" not in comparison:
|
||||||
continue
|
continue
|
||||||
if comparison and cls.filter_element(
|
if comparison and cls.filter_element(
|
||||||
@@ -217,9 +221,7 @@ class Selector:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_element_value(cls, element, keys, value=None):
|
def get_element_value(cls, element, keys, is_regex=False):
|
||||||
if not isinstance(keys, (tuple, list)):
|
|
||||||
keys = [keys]
|
|
||||||
value = element
|
value = element
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key == "type":
|
if key == "type":
|
||||||
@@ -229,16 +231,41 @@ class Selector:
|
|||||||
elif key == "container":
|
elif key == "container":
|
||||||
value = ifcopenshell.util.element.get_container(element)
|
value = ifcopenshell.util.element.get_container(element)
|
||||||
elif isinstance(value, ifcopenshell.entity_instance):
|
elif isinstance(value, ifcopenshell.entity_instance):
|
||||||
result = value.get_info().get(key, None)
|
attribute = value.get_info().get(key, None)
|
||||||
if result is not None:
|
|
||||||
return result
|
if attribute is not None:
|
||||||
result = ifcopenshell.util.element.get_pset(value, key)
|
value = attribute
|
||||||
if result is None:
|
|
||||||
value = None
|
|
||||||
else:
|
else:
|
||||||
|
# Try to extract pset
|
||||||
|
if is_regex:
|
||||||
|
psets = ifcopenshell.util.element.get_psets(value)
|
||||||
|
matching_psets = []
|
||||||
|
for pset_name, pset in psets.items():
|
||||||
|
if re.match(key, pset_name):
|
||||||
|
matching_psets.append(pset)
|
||||||
|
result = matching_psets or None
|
||||||
|
else:
|
||||||
|
result = ifcopenshell.util.element.get_pset(value, key)
|
||||||
|
|
||||||
value = result
|
value = result
|
||||||
elif isinstance(value, dict): # Such as from the result of a prior get_pset
|
elif isinstance(value, dict): # Such as from the result of a prior get_pset
|
||||||
return value.get(key, None)
|
if is_regex:
|
||||||
|
results = []
|
||||||
|
for prop_name, prop_value in value.items():
|
||||||
|
if re.match(key, prop_name):
|
||||||
|
results.append(prop_value)
|
||||||
|
value = results
|
||||||
|
else:
|
||||||
|
value = value.get(key, None)
|
||||||
|
elif isinstance(value, list): # If we use regex
|
||||||
|
results = []
|
||||||
|
for v in value:
|
||||||
|
subvalue = cls.get_element_value(v, [key], is_regex=is_regex)
|
||||||
|
if isinstance(subvalue, list):
|
||||||
|
results.extend(subvalue)
|
||||||
|
else:
|
||||||
|
results.append(subvalue)
|
||||||
|
value = results
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -188,3 +188,26 @@ class TestSelector(test.bootstrap.IFC4):
|
|||||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
|
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
|
||||||
assert subject.Selector.parse(self.file, ".IfcWall", elements=[element])
|
assert subject.Selector.parse(self.file, ".IfcWall", elements=[element])
|
||||||
assert not subject.Selector.parse(self.file, ".IfcWall", elements=[element2])
|
assert not subject.Selector.parse(self.file, ".IfcWall", elements=[element2])
|
||||||
|
|
||||||
|
def test_selecting_a_property_via_a_wildcard_pset_name(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="Foobar")
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
|
||||||
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobaz")
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"})
|
||||||
|
assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."Foo"="Bar"]') == [element]
|
||||||
|
|
||||||
|
def test_selecting_a_property_via_a_wildcard_property_name(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="Foobar")
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
|
||||||
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobaz")
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"})
|
||||||
|
assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."F.*"="Bar"]') == [element]
|
||||||
|
|
||||||
|
def test_selecting_a_property_via_a_type_attribute(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
|
||||||
|
element_type.Name = "Foo"
|
||||||
|
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type)
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[type.Name="Foo"]')) == {element}
|
||||||
|
|||||||
Reference in New Issue
Block a user