Repository: tefra/xsdata

Issue:
infinite loop with Mexican electronic invoicing schema
Hello, I was trying `xsdata generate` with this xsd file http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd and sadly it creates an endless loop apparently, it seems related to the catCFDI.xsd include. if you try xsdata generate simply with this http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd file you alreay get the infinite loop.
Any idea?

xsdata, version 22.12

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=779)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=779&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=779&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=779&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=779&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=779) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=779&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=779&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/codegen/handlers/merge_attributes.py b/xsdata/codegen/handlers/merge_attributes.py
index ed9cffbe..0ed30118 100644
--- a/xsdata/codegen/handlers/merge_attributes.py
+++ b/xsdata/codegen/handlers/merge_attributes.py
@@ -15,12 +15,23 @@ class MergeAttributes(HandlerInterface):
     @classmethod
     def process(cls, target: Class):
         """
-        Detect same type attributes in order to merge them together with their
-        restrictions.
+        Detect and process duplicate attributes.
 
-        Two attributes are considered equal if they have the same name,
-        tag and namespace.
+        - Remove duplicates for enumerations.
+        - Merge duplicates with restrictions and types.
         """
+        if target.is_enumeration:
+            cls.filter_duplicate_attrs(target)
+        else:
+            cls.merge_duplicate_attrs(target)
+
+    @classmethod
+    def filter_duplicate_attrs(cls, target: Class):
+        attrs = collections.unique_sequence(target.attrs, key="default")
+        target.attrs = attrs
+
+    @classmethod
+    def merge_duplicate_attrs(self, target: Class):
         result: List[Attr] = []
         for attr in target.attrs:
             pos = collections.find(result, attr)


Test patch:
diff --git a/tests/codegen/handlers/test_merge_attributes.py b/tests/codegen/handlers/test_merge_attributes.py
index a9dc5d31..46e0e184 100644
--- a/tests/codegen/handlers/test_merge_attributes.py
+++ b/tests/codegen/handlers/test_merge_attributes.py
@@ -12,7 +12,19 @@ class MergeAttributesTests(FactoryTestCase):
         super().setUp()
         self.processor = MergeAttributes
 
-    def test_process(self):
+    def test_process_with_enumeration(self):
+        target = ClassFactory.create()
+        target.attrs = [
+            AttrFactory.enumeration(default=1),
+            AttrFactory.enumeration(default=1),
+            AttrFactory.enumeration(default=2),
+            AttrFactory.enumeration(default=2),
+        ]
+
+        self.processor.process(target)
+        self.assertEqual([1, 2], [x.default for x in target.attrs])
+
+    def test_process_with_non_enumeration(self):
         one = AttrFactory.attribute(fixed=True)
         one_clone = one.clone()
         restrictions = Restrictions(min_occurs=10, max_occurs=15)