Repository: tefra/xsdata

Issue:
Abstract stuff has not fully been filtered
From the NeTEx schema;

```python
@dataclass
class Route2(LinkSequenceVersionStructure):
    """
    Dummy supertype for Route.
    """
    class Meta:
        name = "Route_"
        namespace = "http://www.netex.org.uk/netex"
```
```xsd
    <xsd:element name="Route_" type="LinkSequence_VersionStructure" abstract="true" substitutionGroup="LinkSequence">
        <xsd:annotation>
            <xsd:documentation>Dummy supertype for Route.</xsd:documentation>
        </xsd:annotation>
    </xsd:element>
```

Discussion:

Patch:
diff --git a/xsdata/codegen/container.py b/xsdata/codegen/container.py
index 72f1b728..ed195ae5 100644
--- a/xsdata/codegen/container.py
+++ b/xsdata/codegen/container.py
@@ -22,7 +22,6 @@ from xsdata.codegen.handlers import ClassNameConflictHandler
 from xsdata.codegen.mixins import ContainerInterface
 from xsdata.codegen.models import Class
 from xsdata.codegen.models import get_qname
-from xsdata.codegen.models import should_generate
 from xsdata.codegen.models import Status
 from xsdata.codegen.utils import ClassUtils
 from xsdata.models.config import GeneratorConfig
@@ -152,13 +151,25 @@ class ClassContainer(ContainerInterface):
             designator.run()
 
     def filter_classes(self):
-        """If there is any class derived from complexType or element then
-        filter classes that should be generated, otherwise leave the container
-        as it is."""
+        """
+        Filter classes to be generated.
+
+        1. If there are no global elements generate them all
+        2. Generate all global types and the referred types
+        """
+
+        # No global elements, generate them all!!!
+        if not any(obj.is_global_type for obj in self):
+            return
+
+        occurs = {}
+        for obj in self:
+            if obj.is_global_type:
+                occurs[obj.ref] = None
+                occurs.update({ref: None for ref in obj.references})
 
-        candidates = list(filter(should_generate, self))
-        if candidates:
-            self.data = collections.group_by(candidates, key=get_qname)
+        candidates = [obj for obj in self if obj.ref in occurs]
+        self.data = collections.group_by(candidates, key=get_qname)
 
     def add(self, item: Class):
         """Add class item to the container."""
diff --git a/xsdata/codegen/handlers/attribute_compound_choice.py b/xsdata/codegen/handlers/attribute_compound_choice.py
index b64b0bf5..6739c477 100644
--- a/xsdata/codegen/handlers/attribute_compound_choice.py
+++ b/xsdata/codegen/handlers/attribute_compound_choice.py
@@ -47,7 +47,7 @@ class AttributeCompoundChoiceHandler(RelativeHandlerInterface):
         min_occurs = []
         max_occurs = []
         for attr in attrs:
-            target.attrs.remove(attr)
+            ClassUtils.remove_attribute(target, attr)
             names.append(attr.local_name)
             min_occurs.append(attr.restrictions.min_occurs or 0)
             max_occurs.append(attr.restrictions.max_occurs or 0)
diff --git a/xsdata/codegen/handlers/attribute_group.py b/xsdata/codegen/handlers/attribute_group.py
index 7244f48d..ef1dbf60 100644
--- a/xsdata/codegen/handlers/attribute_group.py
+++ b/xsdata/codegen/handlers/attribute_group.py
@@ -42,6 +42,6 @@ class AttributeGroupHandler(RelativeHandlerInterface):
             raise AnalyzerValueError(f"Group attribute not found: `{qname}`")
 
         if source is target:
-            target.attrs.remove(attr)
+            ClassUtils.remove_attribute(target, attr)
         else:
             ClassUtils.copy_group_attributes(source, target, attr)
diff --git a/xsdata/codegen/handlers/attribute_overrides.py b/xsdata/codegen/handlers/attribute_overrides.py
index b66116ce..2490f1be 100644
--- a/xsdata/codegen/handlers/attribute_overrides.py
+++ b/xsdata/codegen/handlers/attribute_overrides.py
@@ -53,7 +53,12 @@ class AttributeOverridesHandler(RelativeHandlerInterface):
             and bool_eq(attr.restrictions.tokens, source_attr.restrictions.tokens)
             and bool_eq(attr.restrictions.nillable, source_attr.restrictions.nillable)
         ):
-            ClassUtils.remove_attribute(target, attr)
+            cls.remove_attribute(target, attr)
+
+    @classmethod
+    def remove_attribute(cls, target: Class, attr: Attr):
+        ClassUtils.remove_attribute(target, attr)
+        ClassUtils.clean_inner_classes(target)
 
     @classmethod
     def resolve_conflict(cls, attr: Attr, source_attr: Attr):
diff --git a/xsdata/codegen/handlers/attribute_type.py b/xsdata/codegen/handlers/attribute_type.py
index e89b45ce..102f5c3e 100644
--- a/xsdata/codegen/handlers/attribute_type.py
+++ b/xsdata/codegen/handlers/attribute_type.py
@@ -143,7 +143,7 @@ class AttributeTypeHandler(RelativeHandlerInterface):
         elif source.is_element and source.abstract:
             # Substitution groups with abstract elements are used like
             # placeholders and shouldn't be added as standalone fields.
-            target.attrs.remove(attr)
+            ClassUtils.remove_attribute(target, attr)
         else:
             if source.nillable:
                 attr.restrictions.nillable = True
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index b4568dfc..d1dd9b01 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -31,6 +31,7 @@ xml_type_map = {
 }
 
 SIMPLE_TYPES = (Tag.EXTENSION, Tag.LIST, Tag.SIMPLE_TYPE, Tag.UNION)
+GLOBAL_TYPES = (Tag.ELEMENT, Tag.BINDING_OPERATION, Tag.BINDING_MESSAGE, Tag.MESSAGE)
 
 
 @dataclass
@@ -458,6 +459,10 @@ class Class:
         """Shortcut for qname local name."""
         return namespaces.local_name(self.qname)
 
+    @property
+    def ref(self) -> int:
+        return id(self)
+
     @property
     def target_namespace(self) -> Optional[str]:
         return namespaces.target_uri(self.qname)
@@ -484,17 +489,25 @@ class Class:
         xs:element."""
         return self.tag == Tag.ELEMENT
 
+    @property
+    def is_enumeration(self) -> bool:
+        """Return whether all attributes are derived from xs:enumeration."""
+        return len(self.attrs) > 0 and all(attr.is_enumeration for attr in self.attrs)
+
+    @property
+    def is_global_type(self) -> bool:
+        """Return whether this instance is a non abstract element, wsdl binding
+        class or a complex type without simple content."""
+        return (not self.abstract and self.tag in GLOBAL_TYPES) or (
+            self.tag == Tag.COMPLEX_TYPE and not self.is_simple_type
+        )
+
     @property
     def is_group(self) -> bool:
         """Return whether this attribute is derived from an xs:group or
         xs:attributeGroup."""
         return self.tag in (Tag.ATTRIBUTE_GROUP, Tag.GROUP)
 
-    @property
-    def is_enumeration(self) -> bool:
-        """Return whether all attributes are derived from xs:enumeration."""
-        return len(self.attrs) > 0 and all(attr.is_enumeration for attr in self.attrs)
-
     @property
     def is_nillable(self) -> bool:
         """Return whether this class represents a nillable xml element."""
@@ -520,15 +533,25 @@ class Class:
         )
 
     @property
-    def should_generate(self) -> bool:
-        """Return whether this instance should be generated."""
-        return (
-            self.tag
-            in (Tag.ELEMENT, Tag.BINDING_OPERATION, Tag.BINDING_MESSAGE, Tag.MESSAGE)
-            or self.tag == Tag.COMPLEX_TYPE
-            and not self.is_simple_type
-            or self.is_enumeration
-        )
+    def references(self) -> Iterator[int]:
+        def all_refs():
+            for ext in self.extensions:
+                yield ext.type.reference
+
+            for attr in self.attrs:
+                for tp in attr.types:
+                    yield tp.reference
+
+                for choice in attr.choices:
+                    for ctp in choice.types:
+                        yield ctp.reference
+
+            for inner in self.inner:
+                yield from inner.references
+
+        for ref in all_refs():
+            if ref:
+                yield ref
 
     @property
     def target_module(self) -> str:
@@ -602,4 +625,3 @@ get_slug = operator.attrgetter("slug")
 get_target_namespace = operator.attrgetter("target_namespace")
 is_enumeration = operator.attrgetter("is_enumeration")
 is_group = operator.attrgetter("is_group")
-should_generate = operator.attrgetter("should_generate")
diff --git a/xsdata/codegen/utils.py b/xsdata/codegen/utils.py
index 917745e8..40af0222 100644
--- a/xsdata/codegen/utils.py
+++ b/xsdata/codegen/utils.py
@@ -23,11 +23,9 @@ class ClassUtils:
 
     @classmethod
     def remove_attribute(cls, target: Class, attr: Attr):
-        """Remove the given attr from the target class and clean inner
-        classes."""
-        target.attrs.remove(attr)
-
-        cls.clean_inner_classes(target)
+        """Safely remove the given attr from the target class by check obj
+        ids."""
+        target.attrs = [at for at in target.attrs if id(at) != id(attr)]
 
     @classmethod
     def clean_inner_classes(cls, target: Class):


Test patch:
diff --git a/tests/codegen/models/test_class.py b/tests/codegen/models/test_class.py
index 5526164b..899ce6c8 100644
--- a/tests/codegen/models/test_class.py
+++ b/tests/codegen/models/test_class.py
@@ -112,6 +112,13 @@ class ClassTests(FactoryTestCase):
         obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE)
         self.assertFalse(obj.is_complex)
 
+    def test_property_is_element(self):
+        obj = ClassFactory.create(tag=Tag.ELEMENT)
+        self.assertTrue(obj.is_element)
+
+        obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE)
+        self.assertFalse(obj.is_element)
+
     def test_property_is_enumeration(self):
         obj = ClassFactory.enumeration(2)
         self.assertTrue(obj.is_enumeration)
@@ -122,7 +129,22 @@ class ClassTests(FactoryTestCase):
         obj.attrs.clear()
         self.assertFalse(obj.is_enumeration)
 
-    def test_is_simple_type(self):
+    def test_property_is_global_type(self):
+        obj = ClassFactory.create(abstract=True, tag=Tag.ELEMENT)
+        self.assertFalse(obj.is_global_type)
+
+        obj = ClassFactory.create(tag=Tag.ELEMENT)
+        self.assertTrue(obj.is_global_type)
+
+        obj = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
+        obj.extensions.append(ExtensionFactory.create())
+        self.assertTrue(obj.is_global_type)
+
+        obj.extensions.clear()
+        obj.attrs.append(AttrFactory.create(tag=Tag.EXTENSION))
+        self.assertFalse(obj.is_global_type)
+
+    def test_property_is_simple_type(self):
         obj = ClassFactory.elements(2)
 
         self.assertFalse(obj.is_simple_type)
@@ -142,27 +164,27 @@ class ClassTests(FactoryTestCase):
         self.assertTrue(ClassFactory.create(tag=Tag.ATTRIBUTE_GROUP).is_group)
         self.assertFalse(ClassFactory.create(tag=Tag.ELEMENT).is_group)
 
-    def test_property_should_generate(self):
-        obj = ClassFactory.create(tag=Tag.ELEMENT)
-        self.assertTrue(obj.should_generate)
-
-        obj = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
-        self.assertTrue(obj.should_generate)
+    def test_property_ref(self):
+        obj = ClassFactory.create()
+        self.assertEqual(id(obj), obj.ref)
 
-        obj.attrs.append(AttrFactory.create(tag=Tag.EXTENSION))
-        self.assertFalse(obj.should_generate)
+    def test_property_references(self):
+        ext_1 = ExtensionFactory.create(AttrTypeFactory.create(reference=1))
+        ext_2 = ExtensionFactory.create(AttrTypeFactory.create(reference=2))
 
-        obj = ClassFactory.create(tag=Tag.BINDING_OPERATION)
-        self.assertTrue(obj.should_generate)
+        obj = ClassFactory.elements(3)
+        obj.extensions.append(ext_1)
+        obj.extensions.append(ext_2)
 
-        obj = ClassFactory.create(tag=Tag.BINDING_MESSAGE)
-        self.assertTrue(obj.should_generate)
+        obj.attrs[0].types[0].reference = 3
+        obj.attrs[1].choices.append(AttrFactory.create())
+        obj.attrs[1].choices[0].types[0].reference = 4
+        obj.attrs[2].types[0].reference = 5
 
-        obj = ClassFactory.enumeration(2)
-        self.assertTrue(obj.should_generate)
+        obj.inner.append(ClassFactory.elements(2))
+        obj.inner[0].attrs[1].types[0].reference = 6
 
-        obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE)
-        self.assertFalse(obj.should_generate)
+        self.assertEqual(list(range(1, 7)), list(obj.references))
 
     def test_property_target_module(self):
 
diff --git a/tests/codegen/test_container.py b/tests/codegen/test_container.py
index b4cc109b..9d84ff96 100644
--- a/tests/codegen/test_container.py
+++ b/tests/codegen/test_container.py
@@ -7,6 +7,7 @@ from xsdata.codegen.models import Status
 from xsdata.models.config import GeneratorConfig
 from xsdata.models.enums import Tag
 from xsdata.utils.testing import AttrFactory
+from xsdata.utils.testing import AttrTypeFactory
 from xsdata.utils.testing import ClassFactory
 from xsdata.utils.testing import FactoryTestCase
 
@@ -131,24 +132,25 @@ class ClassContainerTests(FactoryTestCase):
         for obj in self.container:
             self.assertEqual(Status.FLATTENED, obj.status)
 
-    @mock.patch.object(Class, "should_generate", new_callable=mock.PropertyMock)
-    def test_filter_classes(self, mock_class_should_generate):
-        mock_class_should_generate.side_effect = [True, False, False, True, False]
+    def test_filter_classes(self):
+        complex_type = ClassFactory.elements(1)
+        enum_1 = ClassFactory.enumeration(2)
+        complex_type.attrs[0].types[0].reference = enum_1.ref
+
+        simple_type = ClassFactory.simple_type()
+        enum_2 = ClassFactory.enumeration(3)
+        simple_type.attrs[0].types[0].reference = enum_2.ref
+
+        element = ClassFactory.create(tag=Tag.ELEMENT, abstract=True)
 
-        classes = ClassFactory.list(5)
         container = ClassContainer(config=GeneratorConfig())
-        container.extend(classes)
+        container.extend([complex_type, enum_1, enum_2, simple_type, element])
 
-        expected = [
-            classes[0],
-            classes[3],
-        ]
+        expected = [complex_type, enum_1]
         container.filter_classes()
         self.assertEqual(expected, list(container))
 
-    @mock.patch.object(Class, "should_generate", new_callable=mock.PropertyMock)
-    def test_filter_classes_with_only_simple_types(self, mock_class_should_generate):
-        mock_class_should_generate.return_value = False
+    def test_filter_classes_with_only_simple_types(self):
         classes = [ClassFactory.enumeration(2), ClassFactory.simple_type()]
         container = ClassContainer(config=GeneratorConfig())
         container.extend(classes)
diff --git a/tests/codegen/test_utils.py b/tests/codegen/test_utils.py
index bfc6dfcd..41bbf24b 100644
--- a/tests/codegen/test_utils.py
+++ b/tests/codegen/test_utils.py
@@ -15,8 +15,7 @@ from xsdata.utils.testing import FactoryTestCase
 
 
 class ClassUtilsTests(FactoryTestCase):
-    @mock.patch.object(ClassUtils, "clean_inner_classes")
-    def test_remove_attribute(self, mock_clean_inner_classes):
+    def test_remove_attribute(self):
 
         target = ClassFactory.elements(1)
         attr = target.attrs[0]
@@ -24,8 +23,6 @@ class ClassUtilsTests(FactoryTestCase):
         ClassUtils.remove_attribute(target, attr)
         self.assertEqual(0, len(target.attrs))
 
-        mock_clean_inner_classes.assert_called_once_with(target)
-
     @mock.patch.object(ClassUtils, "is_orphan_inner")
     def test_clean_inner_classes(self, mock_is_orphan_inner):
         mock_is_orphan_inner.side_effect = [True, False, True]