@@ -201,6 +201,10 @@ def _build_map(self, old: Element, new: Element) -> None:
201201 old_path_set = set (old_paths )
202202 new_path_set = set (new_paths )
203203
204+ # expose the path->Element maps as members so other methods can reuse them
205+ self .old_paths = old_paths
206+ self .new_paths = new_paths
207+
204208 def process_parent_renames (path : str ) -> str :
205209 # Apply any parent AoS/structure rename
206210 # Loop in reverse order to find the closest parent which was renamed:
@@ -222,20 +226,6 @@ def get_old_path(path: str, previous_name: str) -> str:
222226 old_path = previous_name
223227 return process_parent_renames (old_path )
224228
225- def add_rename (old_path : str , new_path : str ):
226- old_item = old_paths [old_path ]
227- new_item = new_paths [new_path ]
228- self .new_to_old [new_path ] = (
229- old_path ,
230- _get_tbp (old_item , old_paths ),
231- _get_ctxpath (old_path , old_paths ),
232- )
233- self .old_to_new [old_path ] = (
234- new_path ,
235- _get_tbp (new_item , new_paths ),
236- _get_ctxpath (new_path , new_paths ),
237- )
238-
239229 # Iterate through all NBC metadata and add entries
240230 for new_item in new .iterfind (".//field[@change_nbc_description]" ):
241231 new_path = new_item .get ("path" )
@@ -275,14 +265,15 @@ def add_rename(old_path: str, new_path: str):
275265 self .version_old ,
276266 )
277267 elif self ._check_data_type (old_item , new_item ):
278- add_rename (old_path , new_path )
268+ # use class helper to register simple renames and reciprocal mappings
269+ self ._add_rename (old_path , new_path )
279270 if old_item .get ("data_type" ) in DDVersionMap .STRUCTURE_TYPES :
280271 # Add entries for common sub-elements
281272 for path in old_paths :
282273 if path .startswith (old_path ):
283274 npath = path .replace (old_path , new_path , 1 )
284275 if npath in new_path_set :
285- add_rename (path , npath )
276+ self . _add_rename (path , npath )
286277 elif nbc_description == "type_changed" :
287278 pass # We will handle this (if possible) in self._check_data_type
288279 elif nbc_description == "repeat_children_first_point" :
@@ -334,6 +325,28 @@ def add_rename(old_path: str, new_path: str):
334325 if self .version_old .major == 3 and new_version and new_version .major == 4 :
335326 self ._apply_3to4_conversion (old , new )
336327
328+ def _add_rename (self , old_path : str , new_path : str ) -> None :
329+ """Register a simple rename from old_path -> new_path using the
330+ path->Element maps stored on the instance (self.old_paths/self.new_paths).
331+ This will also add the reciprocal mapping when possible.
332+ """
333+ old_item = self .old_paths [old_path ]
334+ new_item = self .new_paths [new_path ]
335+
336+ # forward mapping
337+ self .old_to_new [old_path ] = (
338+ new_path ,
339+ _get_tbp (new_item , self .new_paths ),
340+ _get_ctxpath (new_path , self .new_paths ),
341+ )
342+
343+ # reciprocal mapping
344+ self .new_to_old [new_path ] = (
345+ old_path ,
346+ _get_tbp (old_item , self .old_paths ),
347+ _get_ctxpath (old_path , self .old_paths ),
348+ )
349+
337350 def _apply_3to4_conversion (self , old : Element , new : Element ) -> None :
338351 # Postprocessing for COCOS definition change:
339352 for psi_like in ["psi_like" , "dodpsi_like" ]:
@@ -386,6 +399,46 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
386399 to_update [p ] = v
387400 self .old_to_new .path .update (to_update )
388401
402+ # GH#59: To improve further the conversion of DD3 to DD4, especially the
403+ # Machine Description part of the IDSs, we would like to add a 3to4 specific
404+ # rule to convert any siblings name + identifier (that are not part of an
405+ # identifier structure, meaning that there is no index sibling) into
406+ # description + name. Meaning:
407+ # parent/name (DD3) -> parent/description (DD4)
408+ # parent/identifier (DD3) -> parent/name (DD4)
409+ # Only perform the mapping if the corresponding target fields exist in the
410+ # new DD and if we don't already have a mapping for the involved paths.
411+ # use self.old_paths and self.new_paths set in _build_map
412+ for p in self .old_paths :
413+ # look for name children
414+ if not p .endswith ("/name" ):
415+ continue
416+ parent = p .rsplit ("/" , 1 )[0 ]
417+ name_path = f"{ parent } /name"
418+ id_path = f"{ parent } /identifier"
419+ index_path = f"{ parent } /index"
420+ desc_path = f"{ parent } /description"
421+ new_name_path = name_path
422+
423+ # If neither 'name' nor 'identifier' existed in the old DD, skip this parent
424+ if name_path not in self .old_paths or id_path not in self .old_paths :
425+ continue
426+ # exclude identifier-structure (has index sibling)
427+ if index_path in self .old_paths :
428+ continue
429+
430+ # Ensure the candidate target fields exist in the new DD
431+ if desc_path not in self .new_paths or new_name_path not in self .new_paths :
432+ continue
433+
434+ # Map DD3 name -> DD4 description
435+ if name_path not in self .old_to_new .path :
436+ self ._add_rename (name_path , desc_path )
437+
438+ # Map DD3 identifier -> DD4 name
439+ if id_path in self .old_to_new .path :
440+ self ._add_rename (id_path , new_name_path )
441+
389442 def _map_missing (self , is_new : bool , missing_paths : Set [str ]):
390443 rename_map = self .new_to_old if is_new else self .old_to_new
391444 # Find all structures which have a renamed sub-item
0 commit comments