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
 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
202
class ClassValidator:
    """Container class validator.

    Args:
        container: The class container instance
    """

    __slots__ = "container"

    def __init__(self, container: ContainerInterface):
        self.container = container

    def process(self):
        """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]):
        """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 list(classes):
            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(list(items))

            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) -> Optional[Extension]:
        """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) -> Optional[Attr]:
        """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)

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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def process(self):
    """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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def remove_invalid_classes(self, classes: List[Class]):
    """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 list(classes):
        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
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
@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(list(items))

        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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@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
Optional[Extension]

An extension instance or None if there is no circular extension.

Source code in xsdata/codegen/validator.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@classmethod
def find_circular_extension(cls, target: Class) -> Optional[Extension]:
    """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
Optional[Attr]

An attr instance or None if there is no circular attr.

Source code in xsdata/codegen/validator.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
@classmethod
def find_circular_group(cls, target: Class) -> Optional[Attr]:
    """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
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
202
@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)