Skip to content

# validator

## `xsdata.codegen.validator`

### `ClassValidator`

Container class validator.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`container` | `ContainerInterface` | The class container instance | required

Source code in `xsdata/codegen/validator.py`

     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201

    class ClassValidator:
        """Container class validator.

        Args:
            container: The class container instance
        """

        __slots__ = "container"

        def __init__(self, container: ContainerInterface):
            """Initialize the class validator."""
            self.container = container

        def process(self) -> None:
            """Main process entrypoint.

            Runs on groups of classes with the same
            qualified name.

            Steps:
                1. Remove invalid classes
                2. Handle duplicate types
                3. Merge global types
            """
            for classes in self.container.data.values():
                if len(classes) > 1:
                    self.remove_invalid_classes(classes)

                if len(classes) > 1:
                    self.handle_duplicate_types(classes)

                if len(classes) > 1:
                    self.merge_global_types(classes)

        def remove_invalid_classes(self, classes: list[Class]) -> None:
            """Remove classes with undefined extensions.

            Args:
                classes: A list of class instances
            """

            def is_invalid(ext: Extension) -> bool:
                """Check if given type declaration is not native and is missing."""
                return not ext.type.native and ext.type.qname not in self.container.data

            for target in classes.copy():
                if any(is_invalid(extension) for extension in target.extensions):
                    classes.remove(target)

        @classmethod
        def handle_duplicate_types(cls, classes: list[Class]):
            """Find and handle duplicate classes.

            If a class is defined more than once, keep either
            the one that was in redefines or overrides, or the
            last definition. If a class was redefined merge
            circular group attrs and extensions.

            In order for two classes to be duplicated they must
            have the same qualified name and be derived from the
            same xsd element.

            Args:
                classes: A list of classes with the same qualified name
            """
            for items in group_by(classes, get_tag).values():
                if len(items) == 1:
                    continue

                index = cls.select_winner(items.copy())

                if index == -1:
                    logger.warning(
                        "Duplicate type %s, will keep the last defined",
                        items[0].qname,
                    )

                winner = items.pop(index)

                for item in items:
                    classes.remove(item)

                    if winner.container == Tag.REDEFINE:
                        cls.merge_redefined_type(item, winner)

        @classmethod
        def merge_redefined_type(cls, source: Class, target: Class):
            """Merge source properties to the target redefined target class instance.

            Redefined classes usually have references to the original
            class. We need to copy those.

            Args:
                source: The original source class instance
                target: The redefined target class instance
            """
            circular_extension = cls.find_circular_extension(target)
            circular_group = cls.find_circular_group(target)

            if circular_extension:
                ClassUtils.copy_attributes(source, target, circular_extension)
                ClassUtils.copy_extensions(source, target, circular_extension)

            if circular_group:
                ClassUtils.copy_group_attributes(source, target, circular_group)

        @classmethod
        def select_winner(cls, candidates: list[Class]) -> int:
            """From a list of classes select which class index will remain.

            Classes that were extracted from in xs:override/xs:redefined
            containers have priority, otherwise pick the last in the list.

            Args:
                candidates: A list of duplicate class instances

            Returns:
                The index of winner class or -1 if there is no clear winner.
            """
            for index, item in enumerate(candidates):
                if item.container in (Tag.OVERRIDE, Tag.REDEFINE):
                    return index

            return -1

        @classmethod
        def find_circular_extension(cls, target: Class) -> Extension | None:
            """Find the first circular reference extension.

            Redefined classes usually have references to the original
            class with the same qualified name. We need to locate
            those and copy any attrs.

            Args:
                target: The target class instance to inspect

            Returns:
                An extension instance or None if there is no circular extension.
            """
            for ext in target.extensions:
                if ext.type.name == target.name:
                    return ext

            return None

        @classmethod
        def find_circular_group(cls, target: Class) -> Attr | None:
            """Find an attr with the same name as the target class name.

            Redefined classes usually have references to the original
            class with the same qualified name. We need to locate
            those and copy any attrs.

            Args:
                target: The target class instance to inspect

            Returns:
                An attr instance or None if there is no circular attr.
            """
            return ClassUtils.find_attr(target, target.name)

        @classmethod
        def merge_global_types(cls, classes: list[Class]):
            """Merge parent-child global types.

            Conditions
                1. One of them is derived from xs:element
                2. One of them is derived from xs:complexType
                3. The xs:element is a subclass of the xs:complexType
                4. The xs:element has no attributes (This can't happen in a valid schema)

            Args:
                 classes: A list of duplicate classes
            """
            el = collections.first(x for x in classes if x.tag == Tag.ELEMENT)
            ct = collections.first(x for x in classes if x.tag == Tag.COMPLEX_TYPE)

            if (
                el is None
                or ct is None
                or el is ct
                or el.attrs
                or len(el.extensions) != 1
                or el.extensions[0].type.qname != el.qname
            ):
                return

            ct.namespace = el.namespace or ct.namespace
            ct.help = el.help or ct.help
            ct.substitutions = el.substitutions
            classes.remove(el)

#### `__init__(container)`

Initialize the class validator.

Source code in `xsdata/codegen/validator.py`

    19
    20
    21

    def __init__(self, container: ContainerInterface):
        """Initialize the class validator."""
        self.container = container

#### `process()`

Main process entrypoint.

Runs on groups of classes with the same qualified name.

Steps

1. Remove invalid classes
2. Handle duplicate types
3. Merge global types

Source code in `xsdata/codegen/validator.py`

    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    def process(self) -> None:
        """Main process entrypoint.

        Runs on groups of classes with the same
        qualified name.

        Steps:
            1. Remove invalid classes
            2. Handle duplicate types
            3. Merge global types
        """
        for classes in self.container.data.values():
            if len(classes) > 1:
                self.remove_invalid_classes(classes)

            if len(classes) > 1:
                self.handle_duplicate_types(classes)

            if len(classes) > 1:
                self.merge_global_types(classes)

#### `remove_invalid_classes(classes)`

Remove classes with undefined extensions.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`classes` | `list[Class]` | A list of class instances | required

Source code in `xsdata/codegen/validator.py`

    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57

    def remove_invalid_classes(self, classes: list[Class]) -> None:
        """Remove classes with undefined extensions.

        Args:
            classes: A list of class instances
        """

        def is_invalid(ext: Extension) -> bool:
            """Check if given type declaration is not native and is missing."""
            return not ext.type.native and ext.type.qname not in self.container.data

        for target in classes.copy():
            if any(is_invalid(extension) for extension in target.extensions):
                classes.remove(target)

#### `handle_duplicate_types(classes)` `classmethod`

Find and handle duplicate classes.

If a class is defined more than once, keep either the one that was in redefines or overrides, or the last definition. If a class was redefined merge circular group attrs and extensions.

In order for two classes to be duplicated they must have the same qualified name and be derived from the same xsd element.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`classes` | `list[Class]` | A list of classes with the same qualified name | required

Source code in `xsdata/codegen/validator.py`

    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93

    @classmethod
    def handle_duplicate_types(cls, classes: list[Class]):
        """Find and handle duplicate classes.

        If a class is defined more than once, keep either
        the one that was in redefines or overrides, or the
        last definition. If a class was redefined merge
        circular group attrs and extensions.

        In order for two classes to be duplicated they must
        have the same qualified name and be derived from the
        same xsd element.

        Args:
            classes: A list of classes with the same qualified name
        """
        for items in group_by(classes, get_tag).values():
            if len(items) == 1:
                continue

            index = cls.select_winner(items.copy())

            if index == -1:
                logger.warning(
                    "Duplicate type %s, will keep the last defined",
                    items[0].qname,
                )

            winner = items.pop(index)

            for item in items:
                classes.remove(item)

                if winner.container == Tag.REDEFINE:
                    cls.merge_redefined_type(item, winner)

#### `merge_redefined_type(source, target)` `classmethod`

Merge source properties to the target redefined target class instance.

Redefined classes usually have references to the original class. We need to copy those.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`source` | `Class` | The original source class instance | required
`target` | `Class` | The redefined target class instance | required

Source code in `xsdata/codegen/validator.py`

     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114

    @classmethod
    def merge_redefined_type(cls, source: Class, target: Class):
        """Merge source properties to the target redefined target class instance.

        Redefined classes usually have references to the original
        class. We need to copy those.

        Args:
            source: The original source class instance
            target: The redefined target class instance
        """
        circular_extension = cls.find_circular_extension(target)
        circular_group = cls.find_circular_group(target)

        if circular_extension:
            ClassUtils.copy_attributes(source, target, circular_extension)
            ClassUtils.copy_extensions(source, target, circular_extension)

        if circular_group:
            ClassUtils.copy_group_attributes(source, target, circular_group)

#### `select_winner(candidates)` `classmethod`

From a list of classes select which class index will remain.

Classes that were extracted from in xs:override/xs:redefined containers have priority, otherwise pick the last in the list.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`candidates` | `list[Class]` | A list of duplicate class instances | required

Returns:

Type | Description
--- | ---
`int` | The index of winner class or -1 if there is no clear winner.

Source code in `xsdata/codegen/validator.py`

    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133

    @classmethod
    def select_winner(cls, candidates: list[Class]) -> int:
        """From a list of classes select which class index will remain.

        Classes that were extracted from in xs:override/xs:redefined
        containers have priority, otherwise pick the last in the list.

        Args:
            candidates: A list of duplicate class instances

        Returns:
            The index of winner class or -1 if there is no clear winner.
        """
        for index, item in enumerate(candidates):
            if item.container in (Tag.OVERRIDE, Tag.REDEFINE):
                return index

        return -1

#### `find_circular_extension(target)` `classmethod`

Find the first circular reference extension.

Redefined classes usually have references to the original class with the same qualified name. We need to locate those and copy any attrs.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance to inspect | required

Returns:

Type | Description
--- | ---
`Extension | None` | An extension instance or None if there is no circular extension.

Source code in `xsdata/codegen/validator.py`

    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153

    @classmethod
    def find_circular_extension(cls, target: Class) -> Extension | None:
        """Find the first circular reference extension.

        Redefined classes usually have references to the original
        class with the same qualified name. We need to locate
        those and copy any attrs.

        Args:
            target: The target class instance to inspect

        Returns:
            An extension instance or None if there is no circular extension.
        """
        for ext in target.extensions:
            if ext.type.name == target.name:
                return ext

        return None

#### `find_circular_group(target)` `classmethod`

Find an attr with the same name as the target class name.

Redefined classes usually have references to the original class with the same qualified name. We need to locate those and copy any attrs.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance to inspect | required

Returns:

Type | Description
--- | ---
`Attr | None` | An attr instance or None if there is no circular attr.

Source code in `xsdata/codegen/validator.py`

    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169

    @classmethod
    def find_circular_group(cls, target: Class) -> Attr | None:
        """Find an attr with the same name as the target class name.

        Redefined classes usually have references to the original
        class with the same qualified name. We need to locate
        those and copy any attrs.

        Args:
            target: The target class instance to inspect

        Returns:
            An attr instance or None if there is no circular attr.
        """
        return ClassUtils.find_attr(target, target.name)

#### `merge_global_types(classes)` `classmethod`

Merge parent-child global types.

Conditions 1. One of them is derived from xs:element 2. One of them is derived from xs:complexType 3. The xs:element is a subclass of the xs:complexType 4. The xs:element has no attributes (This can't happen in a valid schema)

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`classes` | `list[Class]` | A list of duplicate classes | required

Source code in `xsdata/codegen/validator.py`

    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201

    @classmethod
    def merge_global_types(cls, classes: list[Class]):
        """Merge parent-child global types.

        Conditions
            1. One of them is derived from xs:element
            2. One of them is derived from xs:complexType
            3. The xs:element is a subclass of the xs:complexType
            4. The xs:element has no attributes (This can't happen in a valid schema)

        Args:
             classes: A list of duplicate classes
        """
        el = collections.first(x for x in classes if x.tag == Tag.ELEMENT)
        ct = collections.first(x for x in classes if x.tag == Tag.COMPLEX_TYPE)

        if (
            el is None
            or ct is None
            or el is ct
            or el.attrs
            or len(el.extensions) != 1
            or el.extensions[0].type.qname != el.qname
        ):
            return

        ct.namespace = el.namespace or ct.namespace
        ct.help = el.help or ct.help
        ct.substitutions = el.substitutions
        classes.remove(el)