@@ -594,7 +594,7 @@ def _follow_link(
594594 return resolved_keys
595595
596596 def handle_field (node : NexusNode , keys : Mapping [str , Any ], prev_path : str ):
597- full_path = remove_from_not_visited ( f"{ prev_path } /{ node .name } " )
597+ full_path = f"{ prev_path } /{ node .name } "
598598 variants = get_variations_of (node , keys )
599599 if (
600600 not variants
@@ -605,15 +605,15 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
605605 return
606606
607607 for variant in variants :
608- variant_path = f"{ prev_path } /{ variant } "
608+ variant_path = remove_from_not_visited ( f"{ prev_path } /{ variant } " )
609609
610610 if (
611611 isinstance (keys [variant ], Mapping )
612612 and not all (k .startswith ("@" ) for k in keys [variant ])
613613 and not list (keys [variant ].keys ()) == ["compress" , "strength" ]
614614 ):
615615 # A field should not have a dict of keys that are _not_ all attributes,
616- # i.e. no sub-fields or sub-groups.
616+ # i.e. there should be no sub-fields or sub-groups.
617617 collector .collect_and_log (
618618 variant_path ,
619619 ValidationProblem .ExpectedField ,
@@ -640,8 +640,8 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
640640 )
641641 collector .collect_and_log (
642642 variant_path ,
643- ValidationProblem .AttributeForNonExistingField ,
644- None ,
643+ ValidationProblem .AttributeForNonExistingConcept ,
644+ "field" ,
645645 )
646646 return
647647 if variant not in keys or mapping .get (variant_path ) is None :
@@ -661,30 +661,31 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
661661
662662 # Check unit category
663663 if node .unit is not None :
664- unit_path = f"{ variant_path } /@units"
665- if node .unit != "NX_UNITLESS" :
666- remove_from_not_visited (unit_path )
667- if f"{ variant } @units" not in keys and (
668- node .unit != "NX_TRANSFORMATION"
669- or mapping .get (f"{ variant_path } /@transformation_type" )
670- in ("translation" , "rotation" )
671- ):
664+ unit_path = remove_from_not_visited (f"{ variant_path } /@units" )
665+ if f"{ variant } @units" not in keys and (
666+ node .unit != "NX_TRANSFORMATION"
667+ or mapping .get (f"{ variant_path } /@transformation_type" )
668+ in ("translation" , "rotation" )
669+ ):
670+ if node .unit != "NX_UNITLESS" :
672671 collector .collect_and_log (
673672 variant_path ,
674673 ValidationProblem .MissingUnit ,
675674 node .unit ,
676675 )
677- break
678676
679- unit = keys .get (f"{ variant } @units" )
680- # Special case: NX_TRANSFORMATION unit depends on `@transformation_type` attribute
681- if (
682- transformation_type := keys .get (f"{ variant } @transformation_type" )
683- ) is not None :
684- hints = {"transformation_type" : transformation_type }
685677 else :
686- hints = {}
687- is_valid_unit_for_node (node , unit , unit_path , hints )
678+ unit = keys .get (f"{ variant } @units" )
679+ # Special case: NX_TRANSFORMATION unit depends on `@transformation_type` attribute
680+ if (
681+ transformation_type := keys .get (
682+ f"{ variant } @transformation_type"
683+ )
684+ ) is not None :
685+ hints = {"transformation_type" : transformation_type }
686+ else :
687+ hints = {}
688+ is_valid_unit_for_node (node , unit , unit_path , hints )
688689
689690 field_attributes = get_field_attributes (variant , keys )
690691 field_attributes = _follow_link (field_attributes , variant_path )
@@ -696,7 +697,7 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
696697 )
697698
698699 def handle_attribute (node : NexusNode , keys : Mapping [str , Any ], prev_path : str ):
699- full_path = remove_from_not_visited ( f"{ prev_path } /@{ node .name } " )
700+ full_path = f"{ prev_path } /@{ node .name } "
700701 variants = get_variations_of (node , keys )
701702
702703 if (
@@ -708,7 +709,7 @@ def handle_attribute(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
708709 return
709710
710711 for variant in variants :
711- variant_path = (
712+ variant_path = remove_from_not_visited (
712713 f"{ prev_path } /{ variant if variant .startswith ('@' ) else f'@{ variant } ' } "
713714 )
714715 mapping [variant_path ] = is_valid_data_field (
@@ -1035,177 +1036,6 @@ def find_instance_name_conflicts(mapping: MutableMapping[str, str]) -> None:
10351036 )
10361037 keys_to_remove .append (valid_key )
10371038
1038- def check_attributes_of_nonexisting_field (
1039- node : NexusNode ,
1040- ):
1041- """
1042- This method runs through the mapping dictionary and checks if there are any
1043- attributes assigned to the fields (not groups!) which are not explicitly
1044- present in the mapping.
1045- If there are any found, a warning is logged and the corresponding items are
1046- added to the list that stores all keys that shall be removed.
1047-
1048- Args:
1049- node (NexusNode): the tree generated from application definition.
1050-
1051- """
1052-
1053- for key in mapping :
1054- last_index = key .rfind ("/" )
1055- if key [last_index + 1 ] == "@" and key [last_index + 1 :] != "@units" :
1056- # key is an attribute. Find a corresponding parent, check all the other
1057- # children of this parent
1058- # ignore units here, they are checked separately
1059- attribute_parent_checked = False
1060- for key_iterating in mapping :
1061- # check if key_iterating starts with parent of the key OR any
1062- # allowed variation of the parent of the key
1063- flag , extra_length = startswith_with_variations (
1064- key_iterating , key [0 :last_index ]
1065- )
1066- # the variation of the path to the parent might have different length
1067- # than the key itself, extra_length is adjusting for that
1068- if flag :
1069- if len (key_iterating ) == last_index + extra_length :
1070- # the parent is a field
1071- attribute_parent_checked = True
1072- break
1073- elif (key_iterating [last_index + extra_length ] == "/" ) and (
1074- key_iterating [last_index + extra_length + 1 ] != "@"
1075- ):
1076- # the parent is a group
1077- attribute_parent_checked = True
1078- break
1079- if not attribute_parent_checked :
1080- type_of_parent_from_tree = check_type_with_tree (
1081- node , key [0 :last_index ]
1082- )
1083- # The parent can be a group with only attributes as children; check
1084- # in the tree built from app. def. Alternatively, parent can be not
1085- # in the tree and then group with only attributes is indistinguishable
1086- # from missing field. Continue without warnings or changes.
1087- if not (
1088- type_of_parent_from_tree == "group"
1089- or type_of_parent_from_tree is None
1090- ):
1091- collector .collect_and_log (
1092- key [0 :last_index ],
1093- ValidationProblem .AttributeForNonExistingField ,
1094- "attribute" ,
1095- )
1096- collector .collect_and_log (
1097- key ,
1098- ValidationProblem .KeyToBeRemoved ,
1099- "attribute" ,
1100- )
1101- keys_to_remove .append (key )
1102- remove_from_not_visited (key )
1103-
1104- def check_type_with_tree (
1105- node : NexusNode ,
1106- path : str ,
1107- ) -> Optional [str ]:
1108- """
1109- Recursively search for the type of the object from Template
1110- (described by path) using subtree hanging below the node.
1111- The path should be relative to the current node.
1112-
1113- Args:
1114- node (NexusNode): the subtree to search in.
1115- path (str): the string addressing the object from Mapping (the Template)
1116- to search the type of.
1117-
1118- Returns:
1119- Optional str: type of the object as a string, if the object was found
1120- in the tree, None otherwise.
1121- """
1122-
1123- # already arrived to the object
1124- if path == "" :
1125- return node .type
1126- # searching for object among the children of the node
1127- next_child_name_index = path [1 :].find ("/" )
1128- if (
1129- next_child_name_index == - 1
1130- ): # the whole path from element #1 is the child name
1131- next_child_name_index = (
1132- len (path ) - 1
1133- ) # -1 because we count from element #1, not #0
1134- next_child_class , next_child_name = split_class_and_name_of (
1135- path [1 : next_child_name_index + 1 ]
1136- )
1137- if (next_child_class is not None ) or (next_child_name is not None ):
1138- output = None
1139- for child in node .children :
1140- # regexs to separate the class and the name from full name of the child
1141- child_class_from_node = re .sub (
1142- r"(\@.*)*(\[.*?\])*(\(.*?\))*([a-z]\_)*(\_[a-z])*[a-z]*\s*" ,
1143- "" ,
1144- child .__str__ (),
1145- )
1146- child_name_from_node = re .sub (
1147- r"(\@.*)*(\(.*?\))*(.*\[)*(\].*)*\s*" ,
1148- "" ,
1149- child .__str__ (),
1150- )
1151- if (child_class_from_node == next_child_class ) or (
1152- child_name_from_node == next_child_name
1153- ):
1154- output_new = check_type_with_tree (
1155- child , path [next_child_name_index + 1 :]
1156- )
1157- if output_new is not None :
1158- output = output_new
1159- return output
1160- else :
1161- return None
1162-
1163- def startswith_with_variations (
1164- large_str : str , baseline_str : str
1165- ) -> tuple [bool , int ]:
1166- """
1167- Recursively check if the large_str starts with baseline_str or an allowed
1168- equivalent (i.e. .../AXISNAME[energy]/... matches .../energy/...).
1169-
1170- Args:
1171- large_str (str): the string to be checked.
1172- baseline_str (str): the string used as a baseline for comparison.
1173-
1174- Returns:
1175- bool: True if large_str starts with baseline_str or equivalent, else False.
1176- int: The combined length difference between baseline_str and the equivalent
1177- part of the large_str.
1178- """
1179- if len (baseline_str .split ("/" )) == 1 :
1180- # if baseline_str has no separators left, match already found
1181- return (True , 0 )
1182- first_token_large_str = large_str .split ("/" )[1 ]
1183- first_token_baseline_str = baseline_str .split ("/" )[1 ]
1184-
1185- remaining_large_str = large_str [len (first_token_large_str ) + 1 :]
1186- remaining_baseline_str = baseline_str [len (first_token_baseline_str ) + 1 :]
1187- if first_token_large_str == first_token_baseline_str :
1188- # exact match of n-th token
1189- return startswith_with_variations (
1190- remaining_large_str , remaining_baseline_str
1191- )
1192- match_check = re .search (r"\[.*?\]" , first_token_large_str )
1193- if match_check is None :
1194- # tokens are different and do not contain []
1195- return (False , 0 )
1196- variation_first_token_large = match_check .group (0 )[1 :- 1 ]
1197- if variation_first_token_large == first_token_baseline_str :
1198- # equivalents match
1199- extra_length_this_step = len (first_token_large_str ) - len (
1200- first_token_baseline_str
1201- )
1202- a , b = startswith_with_variations (
1203- remaining_large_str , remaining_baseline_str
1204- )
1205- return (a , b + extra_length_this_step )
1206- # default
1207- return (False , 0 )
1208-
12091039 def check_reserved_suffix (key : str , mapping : MutableMapping [str , Any ]) -> bool :
12101040 """
12111041 Check if an associated field exists for a key with a reserved suffix.
@@ -1369,8 +1199,6 @@ def check_reserved_prefix(
13691199 keys = _follow_link (nested_keys , "" )
13701200 recurse_tree (tree , nested_keys )
13711201
1372- check_attributes_of_nonexisting_field (tree )
1373-
13741202 for not_visited_key in not_visited :
13751203 if mapping .get (not_visited_key ) is None :
13761204 # This value is not really set. Skip checking its validity.
@@ -1441,15 +1269,29 @@ def check_reserved_prefix(
14411269
14421270 if "@" in not_visited_key .rsplit ("/" )[- 1 ]:
14431271 # check that parent exists
1444- if not_visited_key .rsplit ("/" , 1 )[0 ] not in mapping .keys ():
1272+ parent_key = not_visited_key .rsplit ("/" , 1 )[0 ]
1273+ if (parent_key := not_visited_key .rsplit ("/" , 1 )[0 ]) not in mapping .keys ():
14451274 # check that parent is not a group
14461275 node = add_best_matches_for (not_visited_key .rsplit ("/" , 1 )[0 ], tree )
1447- if node is None or node .type != "group" :
1276+
1277+ remove_attr = False
1278+
1279+ if node is None :
14481280 collector .collect_and_log (
1449- not_visited_key .rsplit ("/" , 1 )[0 ],
1450- ValidationProblem .AttributeForNonExistingField ,
1451- None ,
1281+ parent_key ,
1282+ ValidationProblem .AttributeForNonExistingConcept ,
1283+ "group or field" ,
1284+ )
1285+ remove_attr = True
1286+ elif node .type != "group" :
1287+ collector .collect_and_log (
1288+ parent_key ,
1289+ ValidationProblem .AttributeForNonExistingConcept ,
1290+ "field" ,
14521291 )
1292+ remove_attr = True
1293+
1294+ if remove_attr :
14531295 collector .collect_and_log (
14541296 not_visited_key ,
14551297 ValidationProblem .KeyToBeRemoved ,
0 commit comments