diff --git a/src/pynxtools/dataconverter/helpers.py b/src/pynxtools/dataconverter/helpers.py index d9664a967..b3c2e6fe8 100644 --- a/src/pynxtools/dataconverter/helpers.py +++ b/src/pynxtools/dataconverter/helpers.py @@ -24,7 +24,7 @@ from datetime import datetime, timezone from enum import Enum from functools import lru_cache -from typing import Any, Callable, List, Optional, Tuple, Union, Sequence, cast +from typing import Any, Callable, List, Optional, Sequence, Tuple, Union, cast import h5py import lxml.etree as ET @@ -77,7 +77,14 @@ def __init__(self): self.data = set() self.logging = True - def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *args): + def _log( + self, + path: str, + log_type: ValidationProblem, + value: Optional[Any], + *args, + **kwargs, + ): if value is None: value = "" @@ -153,9 +160,13 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar logger.warning(f"The attribute {path} will not be written.") elif log_type == ValidationProblem.InvalidConceptForNonVariadic: value = cast(Any, value) - log_text = f"Given {value.type} name '{path}' conflicts with the non-variadic name '{value}'" - if value.type == "group": - log_text += f", which should be of type {value.nx_class}." + log_text = ( + f"Defined non-variadic name '{value.name}' of {value.type} " + f"conflicts with the given variadic concept '{path}'." + ) + if "expected_type" in kwargs: + log_text = f"{log_text[:-1]} {kwargs['expected_type']}. Such conflict may break pynxtools writer." + logger.warning(log_text) def collect_and_log( diff --git a/src/pynxtools/dataconverter/validation.py b/src/pynxtools/dataconverter/validation.py index d0bd645e2..9e3b7534d 100644 --- a/src/pynxtools/dataconverter/validation.py +++ b/src/pynxtools/dataconverter/validation.py @@ -134,7 +134,9 @@ def split_class_and_name_of(name: str) -> Tuple[Optional[str], str]: ), f"{name_match.group(2)}{'' if prefix is None else prefix}" -def best_namefit_of(name: str, nodes: Iterable[NexusNode]) -> Optional[NexusNode]: +def best_namefit_of( + name: str, nodes: Iterable[NexusNode], expected_type: str +) -> Optional[NexusNode]: """ Get the best namefit of `name` in `keys`. @@ -166,13 +168,20 @@ def best_namefit_of(name: str, nodes: Iterable[NexusNode]) -> Optional[NexusNode and len(type_attr) > 2 ] if concept_name not in inherited_names: - if node.type == "group": + if node.type == "group" and node.type != expected_type: if concept_name != node.nx_class[2:].upper(): collector.collect_and_log( concept_name, ValidationProblem.InvalidConceptForNonVariadic, node, ) + elif node.type == "field" and node.type != expected_type: + collector.collect_and_log( + concept_name, + ValidationProblem.InvalidConceptForNonVariadic, + node, + expected_type=expected_type, + ) else: collector.collect_and_log( concept_name, @@ -589,12 +598,29 @@ def handle_unknown_type(node: NexusNode, keys: Mapping[str, Any], prev_path: str pass def add_best_matches_for(key: str, node: NexusNode) -> Optional[NexusNode]: - for name in key[1:].replace("@", "").split("/"): + is_last_attr = False + key_components = key[1:].split("/") + if "@" in key_components[-1]: + is_last_attr = True + key_components[-1] = key_components[-1].replace("@", "") + key_len = len(key_components) + expected_type = "group_or_field" + for ind, name in enumerate(key_components): children_to_check = [ node.search_add_child_for(child) for child in node.get_all_direct_children_names() ] - node = best_namefit_of(name, children_to_check) + if ind < (key_len - 2): + expected_type = "group" + elif ind == (key_len - 2) and not is_last_attr: + expected_type = "group" + elif ind == (key_len - 2) and is_last_attr: + expected_type = "group_or_field" + elif ind == (key_len - 1) and not is_last_attr: + expected_type = "field" + elif ind == (key_len - 1) and is_last_attr: + expected_type = "attribute" + node = best_namefit_of(name, children_to_check, expected_type) if node is None: return None diff --git a/src/pynxtools/dataconverter/writer.py b/src/pynxtools/dataconverter/writer.py index f2a289503..cc175609c 100644 --- a/src/pynxtools/dataconverter/writer.py +++ b/src/pynxtools/dataconverter/writer.py @@ -249,8 +249,12 @@ def ensure_and_get_parent_node(self, path: str, undocumented_paths) -> h5py.Grou attrs = self.__nxdl_to_attrs(parent_path) - if attrs is not None: + if attrs is not None and "type" in attrs.keys(): grp.attrs["NX_class"] = attrs["type"] + else: + logger.warning( + f"Nexus group {parent_path_hdf5} is written without NX_class attribute." + ) return grp return self.output_nexus[parent_path_hdf5]