diff --git a/tests/codegen/handlers/test_rename_duplicate_classes.py b/tests/codegen/handlers/test_rename_duplicate_classes.py index 0f77cbbf0..cbc48532c 100644 --- a/tests/codegen/handlers/test_rename_duplicate_classes.py +++ b/tests/codegen/handlers/test_rename_duplicate_classes.py @@ -143,8 +143,8 @@ def test_update_class_references(self): self.processor.update_class_references(target, replacements, 5) self.assertEqual([5, 5, 5, 5], list(target.references)) - @mock.patch.object(RenameDuplicateClasses, "rename_class") - def test_rename_classes(self, mock_rename_class): + @mock.patch.object(RenameDuplicateClasses, "add_numeric_suffix") + def test_rename_classes(self, mock_add_numeric_suffix): classes = [ ClassFactory.create(qname="_a", tag=Tag.ELEMENT), ClassFactory.create(qname="_A", tag=Tag.ELEMENT), @@ -153,7 +153,7 @@ def test_rename_classes(self, mock_rename_class): self.processor.rename_classes(classes, False) self.processor.rename_classes(classes, True) - mock_rename_class.assert_has_calls( + mock_add_numeric_suffix.assert_has_calls( [ mock.call(classes[1], False), mock.call(classes[0], False), @@ -164,7 +164,17 @@ def test_rename_classes(self, mock_rename_class): ] ) - @mock.patch.object(RenameDuplicateClasses, "rename_class") + @mock.patch.object(RenameDuplicateClasses, "add_abstract_suffix") + def test_rename_classes_with_abstract_type(self, mock_add_abstract_suffix): + classes = [ + ClassFactory.create(qname="_a", tag=Tag.ELEMENT), + ClassFactory.create(qname="_A", tag=Tag.ELEMENT, abstract=True), + ] + self.processor.rename_classes(classes, True) + + mock_add_abstract_suffix.assert_called_once_with(classes[1]) + + @mock.patch.object(RenameDuplicateClasses, "add_numeric_suffix") def test_rename_classes_protects_single_element(self, mock_rename_class): classes = [ ClassFactory.create(qname="_a", tag=Tag.ELEMENT), @@ -175,13 +185,13 @@ def test_rename_classes_protects_single_element(self, mock_rename_class): mock_rename_class.assert_called_once_with(classes[1], False) @mock.patch.object(RenameDuplicateClasses, "rename_class_dependencies") - def test_rename_class(self, mock_rename_class_dependencies): + def test_add_numeric_suffix_by_slug(self, mock_rename_class_dependencies): target = ClassFactory.create(qname="{foo}_a") self.processor.container.add(target) self.processor.container.add(ClassFactory.create(qname="{foo}a_1")) self.processor.container.add(ClassFactory.create(qname="{foo}A_2")) self.processor.container.add(ClassFactory.create(qname="{bar}a_3")) - self.processor.rename_class(target, False) + self.processor.add_numeric_suffix(target, False) self.assertEqual("{foo}_a_3", target.qname) self.assertEqual("_a", target.meta_name) @@ -195,13 +205,13 @@ def test_rename_class(self, mock_rename_class_dependencies): self.assertEqual([], self.container.data["{foo}_a"]) @mock.patch.object(RenameDuplicateClasses, "rename_class_dependencies") - def test_rename_class_by_name(self, mock_rename_class_dependencies): + def test_add_numeric_suffix_by_name(self, mock_rename_class_dependencies): target = ClassFactory.create(qname="{foo}_a") self.processor.container.add(target) self.processor.container.add(ClassFactory.create(qname="{bar}a_1")) self.processor.container.add(ClassFactory.create(qname="{thug}A_2")) self.processor.container.add(ClassFactory.create(qname="{bar}a_3")) - self.processor.rename_class(target, True) + self.processor.add_numeric_suffix(target, True) self.assertEqual("{foo}_a_4", target.qname) self.assertEqual("_a", target.meta_name) @@ -214,6 +224,15 @@ def test_rename_class_by_name(self, mock_rename_class_dependencies): self.assertEqual([target], self.container.data["{foo}_a_4"]) self.assertEqual([], self.container.data["{foo}_a"]) + def test_add_abstract_suffix(self): + target = ClassFactory.create(qname="{xsdata}line", abstract=True) + self.processor.container.add(target) + + self.processor.add_abstract_suffix(target) + + self.assertEqual("{xsdata}line_abstract", target.qname) + self.assertEqual("line", target.meta_name) + def test_rename_class_dependencies(self): attr_type = AttrTypeFactory.create(qname="{foo}bar", reference=1) diff --git a/xsdata/codegen/handlers/rename_duplicate_classes.py b/xsdata/codegen/handlers/rename_duplicate_classes.py index b7c26a6b5..dcd41ffb3 100644 --- a/xsdata/codegen/handlers/rename_duplicate_classes.py +++ b/xsdata/codegen/handlers/rename_duplicate_classes.py @@ -63,22 +63,33 @@ def merge_classes(self, classes: List[Class]): self.update_class_references(item, search, replace) def rename_classes(self, classes: List[Class], use_name: bool): - """Rename all the classes in the list. + """Rename the classes in the list. - Protect classes derived from xs:element if there is only one in - the list. + Cases: + - Two classes, one abstract append the abstract suffix + - One element, add numeric suffixes to the rest of the classes + - Add numeric suffixes to all classes. Args: classes: A list of classes with duplicate names use_name: Whether simple or qualified names should be used during renaming """ - total_elements = sum(x.is_element for x in classes) - for target in sorted(classes, key=get_name): - if not target.is_element or total_elements > 1: - self.rename_class(target, use_name) + abstract = [x for x in classes if x.abstract] + if len(classes) == 2 and len(abstract) == 1: + self.add_abstract_suffix(abstract[0]) + else: + total_elements = sum(x.is_element for x in classes) + for target in sorted(classes, key=get_name): + if not target.is_element or total_elements > 1: + self.add_numeric_suffix(target, use_name) + + def add_abstract_suffix(self, target: Class): + """Add the abstract suffix to class name.""" + new_qname = f"{target.qname}_abstract" + self.rename_class(target, new_qname) - def rename_class(self, target: Class, use_name: bool): + def add_numeric_suffix(self, target: Class, use_name: bool): """Find the next available class name. Save the original name in the class metadata and update @@ -90,14 +101,25 @@ def rename_class(self, target: Class, use_name: bool): use_name: Whether simple or qualified names should be used during renaming """ - qname = target.qname namespace, name = namespaces.split_qname(target.qname) - target.qname = self.next_qname(namespace, name, use_name) - target.meta_name = name + new_qname = self.next_qname(namespace, name, use_name) + self.rename_class(target, new_qname) + + def rename_class(self, target: Class, new_qname: str): + """Update the class qualified name and update references. + + Args: + target: The target class to update + new_qname: The new class qualified name + """ + qname = target.qname + target.qname = new_qname + target.meta_name = namespaces.local_name(qname) + self.container.reset(target, qname) for item in self.container: - self.rename_class_dependencies(item, id(target), target.qname) + self.rename_class_dependencies(item, id(target), new_qname) def next_qname(self, namespace: str, name: str, use_name: bool) -> str: """Use int suffixes to get the next available qualified name.