|
41 | 41 | is_variadic, |
42 | 42 | is_appdef, |
43 | 43 | remove_namespace_from_tag, |
| 44 | + NEXUS_TO_PYTHON_DATA_TYPES, |
44 | 45 | ) |
45 | 46 | from pynxtools.definitions.dev_tools.utils.nxdl_utils import ( |
46 | 47 | get_nx_namefit, |
@@ -214,19 +215,6 @@ def __init__( |
214 | 215 | self.is_a = [] |
215 | 216 | self.parent_of = [] |
216 | 217 |
|
217 | | - def _construct_inheritance_chain_from_parent(self): |
218 | | - """ |
219 | | - Builds the inheritance chain of the current node based on the parent node. |
220 | | - """ |
221 | | - if self.parent is None: |
222 | | - return |
223 | | - for xml_elem in self.parent.inheritance: |
224 | | - elem = xml_elem.find( |
225 | | - f"nx:{self.type}/[@name='{self.name}']", namespaces=namespaces |
226 | | - ) |
227 | | - if elem is not None: |
228 | | - self.inheritance.append(elem) |
229 | | - |
230 | 218 | def get_path(self) -> str: |
231 | 219 | """ |
232 | 220 | Gets the path of the current node based on the node name. |
@@ -356,6 +344,7 @@ def get_all_direct_children_names( |
356 | 344 | Returns: |
357 | 345 | Set[str]: A set of children names. |
358 | 346 | """ |
| 347 | + |
359 | 348 | if depth is not None and (not isinstance(depth, int) or depth < 0): |
360 | 349 | raise ValueError("Depth must be a positive integer or None") |
361 | 350 |
|
@@ -601,6 +590,7 @@ def add_node_from(self, xml_elem: ET._Element) -> Optional["NexusNode"]: |
601 | 590 | type=tag, |
602 | 591 | optionality=default_optionality, |
603 | 592 | nxdl_base=xml_elem.base, |
| 593 | + inheritance=[xml_elem], |
604 | 594 | ) |
605 | 595 | elif tag == "group": |
606 | 596 | name = xml_elem.attrib.get("name") |
@@ -684,6 +674,19 @@ def __init__(self, **data) -> None: |
684 | 674 | self._construct_inheritance_chain_from_parent() |
685 | 675 | self._set_optionality() |
686 | 676 |
|
| 677 | + def _construct_inheritance_chain_from_parent(self): |
| 678 | + """ |
| 679 | + Builds the inheritance chain of the current node based on the parent node. |
| 680 | + """ |
| 681 | + if self.parent is None: |
| 682 | + return |
| 683 | + for xml_elem in self.parent.inheritance: |
| 684 | + elem = xml_elem.find( |
| 685 | + f"nx:{self.type}/[@name='{self.name}']", namespaces=namespaces |
| 686 | + ) |
| 687 | + if elem is not None: |
| 688 | + self.inheritance.append(elem) |
| 689 | + |
687 | 690 |
|
688 | 691 | class NexusGroup(NexusNode): |
689 | 692 | """ |
@@ -864,6 +867,145 @@ class NexusEntity(NexusNode): |
864 | 867 | open_enum: bool = False |
865 | 868 | shape: Optional[Tuple[Optional[int], ...]] = None |
866 | 869 |
|
| 870 | + def _check_compatibility_with(self, xml_elem: ET._Element) -> bool: |
| 871 | + """Check compatibility of this node with an XML element from the (possible) inheritance""" |
| 872 | + |
| 873 | + def _check_name_fit(xml_elem: ET._Element) -> bool: |
| 874 | + elem_name = xml_elem.attrib.get("name") |
| 875 | + name_any = is_name_type(xml_elem, "any") |
| 876 | + name_partial = is_name_type(xml_elem, "partial") |
| 877 | + |
| 878 | + if get_nx_namefit(self.name, elem_name, name_any, name_partial) < 0: |
| 879 | + return False |
| 880 | + return True |
| 881 | + |
| 882 | + def _check_type_fit(xml_elem: ET._Element) -> bool: |
| 883 | + elem_type = xml_elem.attrib.get("type") |
| 884 | + if elem_type: |
| 885 | + if not set(NEXUS_TO_PYTHON_DATA_TYPES[self.dtype]).issubset( |
| 886 | + NEXUS_TO_PYTHON_DATA_TYPES[elem_type] |
| 887 | + ): |
| 888 | + return False |
| 889 | + return True |
| 890 | + |
| 891 | + def _check_units_fit(xml_elem: ET._Element) -> bool: |
| 892 | + elem_units = xml_elem.attrib.get("units") |
| 893 | + if elem_units and elem_units != "NX_ANY": |
| 894 | + if elem_units != self.unit: |
| 895 | + if not elem_units == "NX_TRANSFORMATION" and self.unit in [ |
| 896 | + "NX_LENGTH", |
| 897 | + "NX_ANGLE", |
| 898 | + "NX_UNITLESS", |
| 899 | + ]: |
| 900 | + return False |
| 901 | + return True |
| 902 | + |
| 903 | + def _check_enum_fit(xml_elem: ET._Element) -> bool: |
| 904 | + elem_enum = xml_elem.find(f"nx:enumeration", namespaces=namespaces) |
| 905 | + if elem_enum is not None: |
| 906 | + if self.items is None: |
| 907 | + # Case where inherited entity is enumerated, but current node isn't |
| 908 | + return False |
| 909 | + elem_enum_open = elem_enum.attrib.get("open", "false") |
| 910 | + |
| 911 | + if elem_enum_open == "true": |
| 912 | + return True |
| 913 | + |
| 914 | + elem_enum_items = [] |
| 915 | + for items in elem_enum.findall(f"nx:item", namespaces=namespaces): |
| 916 | + value = items.attrib["value"] |
| 917 | + if value[0] == "[" and value[-1] == "]": |
| 918 | + import ast |
| 919 | + |
| 920 | + try: |
| 921 | + elem_enum_items.append(ast.literal_eval(value)) |
| 922 | + except (ValueError, SyntaxError): |
| 923 | + raise Exception( |
| 924 | + f"Error parsing enumeration item in the provided NXDL: {value}" |
| 925 | + ) |
| 926 | + else: |
| 927 | + elem_enum_items.append(value) |
| 928 | + |
| 929 | + def convert_to_hashable(item): |
| 930 | + """Convert lists to tuples for hashable types, leave non-list items as they are.""" |
| 931 | + if isinstance(item, list): |
| 932 | + return tuple(item) # Convert sublists to tuples |
| 933 | + return item # Non-list items remain as they are |
| 934 | + |
| 935 | + set_items = {convert_to_hashable(sublist) for sublist in self.items} |
| 936 | + set_elem_enum_items = { |
| 937 | + convert_to_hashable(sublist) for sublist in elem_enum_items |
| 938 | + } |
| 939 | + |
| 940 | + if not set(set_items).issubset(set_elem_enum_items): |
| 941 | + if self.name == "definition": |
| 942 | + pass |
| 943 | + else: |
| 944 | + # TODO: should we be this strict here? Or can appdefs define additional terms? |
| 945 | + pass |
| 946 | + return True |
| 947 | + |
| 948 | + def _check_dimensions_fit(xml_elem: ET._Element) -> bool: |
| 949 | + if not self.shape: |
| 950 | + return True |
| 951 | + elem_dimensions = xml_elem.find(f"nx:dimensions", namespaces=namespaces) |
| 952 | + if elem_dimensions is not None: |
| 953 | + rank = elem_dimensions.attrib.get("rank") |
| 954 | + if rank is not None and not isinstance(rank, int): |
| 955 | + try: |
| 956 | + int(rank) |
| 957 | + except ValueError: |
| 958 | + # TODO: Handling of symbols |
| 959 | + return True |
| 960 | + elem_dim = elem_dimensions.findall("nx:dim", namespaces=namespaces) |
| 961 | + elem_dimension_rank = rank if rank is not None else len(rank) |
| 962 | + dims: List[Optional[int]] = [None] * int(rank) |
| 963 | + |
| 964 | + for dim in elem_dim: |
| 965 | + idx = int(dim.attrib["index"]) |
| 966 | + if value := dim.attrib.get("value", None): |
| 967 | + # If not, this is probably an old dim element with ref. |
| 968 | + try: |
| 969 | + value = int(value) |
| 970 | + dims[idx] = value |
| 971 | + except ValueError: |
| 972 | + # TODO: Handling of symbols |
| 973 | + pass |
| 974 | + elem_shape = tuple(dims) |
| 975 | + |
| 976 | + if elem_shape: |
| 977 | + if elem_shape != self.shape: |
| 978 | + return False |
| 979 | + |
| 980 | + return True |
| 981 | + |
| 982 | + check_functions = [ |
| 983 | + _check_name_fit, |
| 984 | + _check_type_fit, |
| 985 | + _check_units_fit, |
| 986 | + _check_enum_fit, |
| 987 | + # TODO: check if any inheritance is wrongfully assigned without dim checks |
| 988 | + # _check_dimensions_fit, |
| 989 | + ] |
| 990 | + |
| 991 | + for func in check_functions: |
| 992 | + if not func(xml_elem): |
| 993 | + return False |
| 994 | + return True |
| 995 | + |
| 996 | + def _construct_inheritance_chain_from_parent(self): |
| 997 | + """ |
| 998 | + Builds the inheritance chain of the current node based on the parent node. |
| 999 | + """ |
| 1000 | + if self.parent is None: |
| 1001 | + return |
| 1002 | + for xml_elem in self.parent.inheritance: |
| 1003 | + subelems = xml_elem.findall(f"nx:{self.type}", namespaces=namespaces) |
| 1004 | + if subelems is not None: |
| 1005 | + for elem in subelems: |
| 1006 | + if self._check_compatibility_with(elem): |
| 1007 | + self.inheritance.append(elem) |
| 1008 | + |
867 | 1009 | def _set_type(self): |
868 | 1010 | """ |
869 | 1011 | Sets the dtype of the current entity based on the values in the inheritance chain. |
@@ -950,7 +1092,13 @@ def _set_shape(self): |
950 | 1092 |
|
951 | 1093 | def __init__(self, **data) -> None: |
952 | 1094 | super().__init__(**data) |
| 1095 | + self._set_unit() |
| 1096 | + self._set_type() |
| 1097 | + self._set_items_and_enum_type() |
| 1098 | + self._set_optionality() |
| 1099 | + self._set_shape() |
953 | 1100 | self._construct_inheritance_chain_from_parent() |
| 1101 | + # Set all parameters again based on the acquired inheritance |
954 | 1102 | self._set_unit() |
955 | 1103 | self._set_type() |
956 | 1104 | self._set_items_and_enum_type() |
|
0 commit comments