@@ -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,16 @@ 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
269+ # reciprocal mappings
270+ self ._add_rename (old_path , new_path )
279271 if old_item .get ("data_type" ) in DDVersionMap .STRUCTURE_TYPES :
280272 # Add entries for common sub-elements
281273 for path in old_paths :
282274 if path .startswith (old_path ):
283275 npath = path .replace (old_path , new_path , 1 )
284276 if npath in new_path_set :
285- add_rename (path , npath )
277+ self . _add_rename (path , npath )
286278 elif nbc_description == "type_changed" :
287279 pass # We will handle this (if possible) in self._check_data_type
288280 elif nbc_description == "repeat_children_first_point" :
@@ -334,6 +326,28 @@ def add_rename(old_path: str, new_path: str):
334326 if self .version_old .major == 3 and new_version and new_version .major == 4 :
335327 self ._apply_3to4_conversion (old , new )
336328
329+ def _add_rename (self , old_path : str , new_path : str ) -> None :
330+ """Register a simple rename from old_path -> new_path using the
331+ path->Element maps stored on the instance (self.old_paths/self.new_paths).
332+ This will also add the reciprocal mapping when possible.
333+ """
334+ old_item = self .old_paths [old_path ]
335+ new_item = self .new_paths [new_path ]
336+
337+ # forward mapping
338+ self .old_to_new [old_path ] = (
339+ new_path ,
340+ _get_tbp (new_item , self .new_paths ),
341+ _get_ctxpath (new_path , self .new_paths ),
342+ )
343+
344+ # reciprocal mapping
345+ self .new_to_old [new_path ] = (
346+ old_path ,
347+ _get_tbp (old_item , self .old_paths ),
348+ _get_ctxpath (old_path , self .old_paths ),
349+ )
350+
337351 def _apply_3to4_conversion (self , old : Element , new : Element ) -> None :
338352 # Postprocessing for COCOS definition change:
339353 cocos_paths = []
@@ -391,6 +405,46 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
391405 to_update [p ] = v
392406 self .old_to_new .path .update (to_update )
393407
408+ # GH#59: To improve further the conversion of DD3 to DD4, especially the
409+ # Machine Description part of the IDSs, we would like to add a 3to4 specific
410+ # rule to convert any siblings name + identifier (that are not part of an
411+ # identifier structure, meaning that there is no index sibling) into
412+ # description + name. Meaning:
413+ # parent/name (DD3) -> parent/description (DD4)
414+ # parent/identifier (DD3) -> parent/name (DD4)
415+ # Only perform the mapping if the corresponding target fields exist in the
416+ # new DD and if we don't already have a mapping for the involved paths.
417+ # use self.old_paths and self.new_paths set in _build_map
418+ for p in self .old_paths :
419+ # look for name children
420+ if not p .endswith ("/name" ):
421+ continue
422+ parent = p .rsplit ("/" , 1 )[0 ]
423+ name_path = f"{ parent } /name"
424+ id_path = f"{ parent } /identifier"
425+ index_path = f"{ parent } /index"
426+ desc_path = f"{ parent } /description"
427+ new_name_path = name_path
428+
429+ # If neither 'name' nor 'identifier' existed in the old DD, skip this parent
430+ if name_path not in self .old_paths or id_path not in self .old_paths :
431+ continue
432+ # exclude identifier-structure (has index sibling)
433+ if index_path in self .old_paths :
434+ continue
435+
436+ # Ensure the candidate target fields exist in the new DD
437+ if desc_path not in self .new_paths or new_name_path not in self .new_paths :
438+ continue
439+
440+ # Map DD3 name -> DD4 description
441+ if name_path not in self .old_to_new .path :
442+ self ._add_rename (name_path , desc_path )
443+
444+ # Map DD3 identifier -> DD4 name
445+ if id_path in self .old_to_new .path :
446+ self ._add_rename (id_path , new_name_path )
447+
394448 def _map_missing (self , is_new : bool , missing_paths : Set [str ]):
395449 rename_map = self .new_to_old if is_new else self .old_to_new
396450 # Find all structures which have a renamed sub-item
0 commit comments