Skip to content

utils

xsdata.codegen.utils

ClassUtils

General reusable utils methods that didn't fit anywhere else.

Source code in xsdata/codegen/utils.py
 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
class ClassUtils:
    """General reusable utils methods that didn't fit anywhere else."""

    @classmethod
    def find_value_attr(cls, source: Class) -> Attr:
        """Find the text attribute of the class.

        Args:
            source: The source class instance

        Returns:
            The matched attr instance.

        Raises:
             CodeGenerationError: If no text node/attribute exists
        """
        for attr in source.attrs:
            if not attr.xml_type:
                return attr

        raise CodegenError("Class has no value attr", type=source.qname)

    @classmethod
    def remove_attribute(cls, target: Class, attr: Attr):
        """Safely remove the given attr from the target class.

        Make sure you match the attr by the reference id,
        simple comparison might remove a duplicate attr
        with the same tag/namespace/name.

        Args:
            target: The target class instance
            attr: The attr instance to remove

        """
        target.attrs = [at for at in target.attrs if id(at) != id(attr)]

    @classmethod
    def clean_inner_classes(cls, target: Class):
        """Check if there are orphan inner classes and remove them.

        Args:
            target: The target class instance to inspect.
        """
        for inner in list(target.inner):
            if cls.is_orphan_inner(target, inner):
                target.inner.remove(inner)

    @classmethod
    def is_orphan_inner(cls, target: Class, inner: Class) -> bool:
        """Check if the inner class is references in the target class.

        Args:
            target: The target class instance
            inner: The inner class instance

        Returns:
            The bool result.
        """
        for attr in target.attrs:
            for attr_type in attr.types:
                if attr_type.forward and attr_type.qname == inner.qname:
                    return False

        return True

    @classmethod
    def copy_attributes(cls, source: Class, target: Class, extension: Extension):
        """Copy the attrs from the source to the target class.

        Remove the extension instance that connects the two classes.
        The new attrs are prepended in the list unless if they are
        supposed to be last in a sequence.

        Args:
            source: The source/parent class instance
            target: The target/child class instance
            extension: The extension instance that connects the classes
        """
        target.extensions.remove(extension)
        target_attr_names = {attr.name for attr in target.attrs}

        index = 0
        for attr in source.attrs:
            if attr.name not in target_attr_names:
                clone = cls.clone_attribute(attr, extension.restrictions)
                cls.copy_inner_classes(source, target, clone)

                if attr.index == sys.maxsize:
                    target.attrs.append(clone)
                    continue

                target.attrs.insert(index, clone)

            index += 1

    @classmethod
    def copy_group_attributes(
        cls, source: Class, target: Class, attr: Attr, skip_inner_classes: bool = False
    ):
        """Copy the attrs of the source class to the target class.

        The attr represents a reference to the source class which is
        derived from xs:group or xs:attributeGroup and will be removed.

        Args:
            source: The source class instance
            target: The target class instance
            attr: The group attr instance
            skip_inner_classes: Whether the attr is circular reference, which
                means we can skip copying the inner classes.
        """
        index = target.attrs.index(attr)
        target.attrs.pop(index)

        for source_attr in source.attrs:
            clone = cls.clone_attribute(source_attr, attr.restrictions)
            target.attrs.insert(index, clone)
            index += 1

            if not skip_inner_classes:
                cls.copy_inner_classes(source, target, clone)

    @classmethod
    def copy_extensions(cls, source: Class, target: Class, extension: Extension):
        """Copy the source class extensions to the target class instance.

        Merge the extension restrictions with the source class extensions
        restrictions.

        Args:
            source: The source class instance
            target: The target class instance
            extension: The extension instance that links the two classes together
        """
        for ext in source.extensions:
            clone = ext.clone()
            clone.restrictions.merge(extension.restrictions)
            target.extensions.append(clone)

    @classmethod
    def clone_attribute(cls, attr: Attr, restrictions: Restrictions) -> Attr:
        """Clone the given attr and merge its restrictions with the given.

        Args:
            attr: The source attr instance
            restrictions: The additional restrictions, originated from
                a substitution or another attr.
        """
        clone = attr.clone()
        clone.restrictions.merge(restrictions)
        return clone

    @classmethod
    def copy_inner_classes(cls, source: Class, target: Class, attr: Attr):
        """Copy inner classes from source to the target class instance.

        Args:
            source: The source class instance
            target: The target class instance
            attr: The attr with the possible forward references
        """
        for attr_type in attr.types:
            cls.copy_inner_class(source, target, attr_type)

    @classmethod
    def copy_inner_class(cls, source: Class, target: Class, attr_type: AttrType):
        """Find and copy the inner class from source to the target class instance.

        Steps:
            - Skip If the attr type is not a forward reference
            - Validate the inner class is not a circular reference to the target
            - Otherwise copy the inner class, and make sure it is re-sent for
                processing

        Args:
            source: The source class instance
            target: The target class instance
            attr_type: The attr type with the possible forward reference
        """
        if not attr_type.forward:
            return

        inner = ClassUtils.find_nested(source, attr_type.qname)
        if inner is target:
            attr_type.circular = True
            attr_type.reference = target.ref
        else:
            # In extreme cases this adds duplicate inner classes
            clone = inner.clone()
            clone.package = target.package
            clone.module = target.module
            clone.status = Status.RAW
            attr_type.reference = clone.ref
            clone.parent = target
            target.inner.append(clone)

    @classmethod
    def find_attr(cls, source: Class, name: str) -> Optional[Attr]:
        """Find an attr in the source class by its name.

        Args:
            source: The source class instance
            name: The attr name to lookup

        Returns:
            An attr instance or None if no attr matched.
        """
        for attr in source.attrs:
            if attr.name == name:
                return attr

        return None

    @classmethod
    def flatten(cls, target: Class, location: str) -> Iterator[Class]:
        """Flatten the target class instance and its inner classes.

        The inner classes are removed from target instance!

        Args:
            target: The target class instance
            location: The source location of the target class

        Yields:
            An iterator over all the found classes.
        """
        target.location = location
        target.parent = None

        while target.inner:
            yield from cls.flatten(target.inner.pop(), location)

        for attr in target.attrs:
            attr.types = collections.unique_sequence(attr.types, key="qname")
            for tp in attr.types:
                tp.forward = False

        yield target

    @classmethod
    def reduce_classes(cls, classes: List[Class]) -> List[Class]:
        """Find duplicate classes and attrs and reduce them.

        Args:
            classes: A list of classes

        Returns:
            A list of unique classes with no duplicate attrs.
        """
        result = []
        for group in collections.group_by(classes, key=get_qname).values():
            target = group[0].clone()
            target.attrs = cls.reduce_attributes(group)
            target.mixed = any(x.mixed for x in group)

            cls.cleanup_class(target)
            result.append(target)

        return result

    @classmethod
    def reduce_attributes(cls, classes: List[Class]) -> List[Attr]:
        """Find and merge duplicate attrs from the given class list.

        Args:
            classes: A list of class instances

        Returns:
            A list of unique attr instances.
        """
        result = []
        for attr in cls.sorted_attrs(classes):
            added = False
            optional = False
            for obj in classes:
                pos = collections.find(obj.attrs, attr)
                if pos == -1:
                    optional = True
                elif not added:
                    added = True
                    result.append(obj.attrs.pop(pos))
                else:
                    cls.merge_attributes(result[-1], obj.attrs.pop(pos))

            if optional:
                result[-1].restrictions.min_occurs = 0

        return result

    @classmethod
    def sorted_attrs(cls, classes: List[Class]) -> List[Attr]:
        """Sort and return the attrs from all the class list.

        The list contains duplicate classes, the method tries
        to find all the attrs and sorts them by first occurrence.

        Args:
            classes: A list of duplicate class instances.

        Returns:
            A list of sorted duplicate attr instances.
        """
        attrs: List[Attr] = []
        classes.sort(key=lambda x: len(x.attrs), reverse=True)

        for obj in classes:
            i = 0
            obj_attrs = obj.attrs.copy()

            while obj_attrs:
                pos = collections.find(attrs, obj_attrs[i])
                i += 1

                if pos > -1:
                    insert = obj_attrs[: i - 1]
                    del obj_attrs[:i]
                    while insert:
                        attrs.insert(pos, insert.pop())

                    i = 0
                elif i == len(obj_attrs):
                    attrs.extend(obj_attrs)
                    obj_attrs.clear()

        return attrs

    @classmethod
    def merge_attributes(cls, target: Attr, source: Attr):
        """Merge the source attr into the target instance.

        Merge the types, select the min min_occurs and the max max_occurs
        from the two instances and copy the source sequence number
        to the target if it's currently not set.

        Args:
            target: The target attr instance which will be updated
            source: The source attr instance
        """
        target.types.extend(tp for tp in source.types if tp not in target.types)

        target.restrictions.min_occurs = min(
            target.restrictions.min_occurs or 0,
            source.restrictions.min_occurs or 0,
        )

        target.restrictions.max_occurs = max(
            target.restrictions.max_occurs or 1,
            source.restrictions.max_occurs or 1,
        )

        if source.restrictions.sequence is not None:
            target.restrictions.sequence = source.restrictions.sequence

    @classmethod
    def rename_duplicate_attributes(cls, target: Class):
        """Find and rename attributes with the same slug."""
        grouped = collections.group_by(
            target.attrs, key=lambda x: x.slug or DEFAULT_ATTR_NAME
        )
        for items in grouped.values():
            total = len(items)
            if total == 2 and not items[0].is_enumeration:
                cls.rename_attribute_by_preference(*items)
            elif total > 1:
                cls.rename_attributes_by_index(target.attrs, items)

    @classmethod
    def rename_attribute_by_preference(cls, a: Attr, b: Attr):
        """Decide and rename one of the two given attributes.

        When both attributes are derived from the same xs:tag and one of
        the two fields has a specific namespace prepend it to the name.
        Preferable rename the second attribute.

        Otherwise, append the derived from tag to the name of one of the
        two attributes. Preferably rename the second field or the field
        derived from xs:attribute.

        Args:
            a: The first attr instance
            b: The second attr instance
        """
        if a.tag == b.tag and (a.namespace or b.namespace):
            change = b if b.namespace else a
            assert change.namespace is not None
            change.name = f"{namespaces.clean_uri(change.namespace)}_{change.name}"
        else:
            change = b if b.is_attribute else a
            change.name = f"{change.name}_{change.tag}"

    @classmethod
    def rename_attributes_by_index(cls, attrs: List[Attr], rename: List[Attr]):
        """Append the next available index number to all the rename attr names.

        Args:
            attrs: A list of attr instances whose names must be protected
            rename: A list of attr instances that need to be renamed
        """
        for index in range(1, len(rename)):
            reserved = set(map(get_slug, attrs))
            name = rename[index].name
            rename[index].name = cls.unique_name(name, reserved)

    @classmethod
    def unique_name(cls, name: str, reserved: Set[str]) -> str:
        """Append the next available index number to the name.

        Args:
            name: An object name
            reserved: A set of reserved names

        Returns:
            The new name with the index suffix
        """
        if text.alnum(name) in reserved:
            index = 1
            while text.alnum(f"{name}_{index}") in reserved:
                index += 1

            return f"{name}_{index}"

        return name

    @classmethod
    def cleanup_class(cls, target: Class):
        """Go through the target class attrs and filter their types.

        Removes duplicate and invalid types.

        Args:
            target: The target class instance to inspect
        """
        for attr in target.attrs:
            attr.types = cls.filter_types(attr.types)

    @classmethod
    def filter_types(cls, types: List[AttrType]) -> List[AttrType]:
        """Remove duplicate and invalid types.

        Invalid:
            1. xs:error
            2. xs:anyType and xs:anySimpleType when there are other types present

        Args:
            types: A list of attr type instances

        Returns:
            The new list of unique and valid attr type instances.
        """
        types = collections.unique_sequence(types, key="qname")
        types = collections.remove(types, lambda x: x.datatype == DataType.ERROR)

        if len(types) > 1:
            types = collections.remove(
                types,
                lambda x: x.datatype in (DataType.ANY_TYPE, DataType.ANY_SIMPLE_TYPE),
            )

        if not types:
            types.append(AttrType(qname=str(DataType.STRING), native=True))

        return types

    @classmethod
    def find_nested(cls, target: Class, qname: str) -> Class:
        """Find a nested class by qname.

        Breath-first search implementation, that goes
        from the current level to bottom before looking
        for outer classes.

        Args:
            target: The class instance to begin the search
            qname: The qualified name of the nested class to find

        Raises:
            CodegenException: If the nested class cannot be found.

        Returns:
            The nested class instance.
        """
        queue: Deque[Class] = deque()
        visited: Set[int] = set()

        if target.inner:
            queue.extend(target.inner)
        elif target.parent:
            queue.append(target.parent)

        while len(queue) > 0:
            item = queue.popleft()
            visited.add(item.ref)
            if item.qname == qname:
                return item

            for inner in item.inner:
                if inner.ref not in visited:
                    queue.append(inner)

            if len(queue) == 0 and item.parent:
                queue.append(item.parent)

        raise CodegenError("Missing inner class", parent=target, qname=qname)

find_value_attr(source) classmethod

Find the text attribute of the class.

Parameters:

Name Type Description Default
source Class

The source class instance

required

Returns:

Type Description
Attr

The matched attr instance.

Raises:

Type Description
CodeGenerationError

If no text node/attribute exists

Source code in xsdata/codegen/utils.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@classmethod
def find_value_attr(cls, source: Class) -> Attr:
    """Find the text attribute of the class.

    Args:
        source: The source class instance

    Returns:
        The matched attr instance.

    Raises:
         CodeGenerationError: If no text node/attribute exists
    """
    for attr in source.attrs:
        if not attr.xml_type:
            return attr

    raise CodegenError("Class has no value attr", type=source.qname)

remove_attribute(target, attr) classmethod

Safely remove the given attr from the target class.

Make sure you match the attr by the reference id, simple comparison might remove a duplicate attr with the same tag/namespace/name.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance to remove

required
Source code in xsdata/codegen/utils.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@classmethod
def remove_attribute(cls, target: Class, attr: Attr):
    """Safely remove the given attr from the target class.

    Make sure you match the attr by the reference id,
    simple comparison might remove a duplicate attr
    with the same tag/namespace/name.

    Args:
        target: The target class instance
        attr: The attr instance to remove

    """
    target.attrs = [at for at in target.attrs if id(at) != id(attr)]

clean_inner_classes(target) classmethod

Check if there are orphan inner classes and remove them.

Parameters:

Name Type Description Default
target Class

The target class instance to inspect.

required
Source code in xsdata/codegen/utils.py
58
59
60
61
62
63
64
65
66
67
@classmethod
def clean_inner_classes(cls, target: Class):
    """Check if there are orphan inner classes and remove them.

    Args:
        target: The target class instance to inspect.
    """
    for inner in list(target.inner):
        if cls.is_orphan_inner(target, inner):
            target.inner.remove(inner)

is_orphan_inner(target, inner) classmethod

Check if the inner class is references in the target class.

Parameters:

Name Type Description Default
target Class

The target class instance

required
inner Class

The inner class instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/utils.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@classmethod
def is_orphan_inner(cls, target: Class, inner: Class) -> bool:
    """Check if the inner class is references in the target class.

    Args:
        target: The target class instance
        inner: The inner class instance

    Returns:
        The bool result.
    """
    for attr in target.attrs:
        for attr_type in attr.types:
            if attr_type.forward and attr_type.qname == inner.qname:
                return False

    return True

copy_attributes(source, target, extension) classmethod

Copy the attrs from the source to the target class.

Remove the extension instance that connects the two classes. The new attrs are prepended in the list unless if they are supposed to be last in a sequence.

Parameters:

Name Type Description Default
source Class

The source/parent class instance

required
target Class

The target/child class instance

required
extension Extension

The extension instance that connects the classes

required
Source code in xsdata/codegen/utils.py
 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
@classmethod
def copy_attributes(cls, source: Class, target: Class, extension: Extension):
    """Copy the attrs from the source to the target class.

    Remove the extension instance that connects the two classes.
    The new attrs are prepended in the list unless if they are
    supposed to be last in a sequence.

    Args:
        source: The source/parent class instance
        target: The target/child class instance
        extension: The extension instance that connects the classes
    """
    target.extensions.remove(extension)
    target_attr_names = {attr.name for attr in target.attrs}

    index = 0
    for attr in source.attrs:
        if attr.name not in target_attr_names:
            clone = cls.clone_attribute(attr, extension.restrictions)
            cls.copy_inner_classes(source, target, clone)

            if attr.index == sys.maxsize:
                target.attrs.append(clone)
                continue

            target.attrs.insert(index, clone)

        index += 1

copy_group_attributes(source, target, attr, skip_inner_classes=False) classmethod

Copy the attrs of the source class to the target class.

The attr represents a reference to the source class which is derived from xs:group or xs:attributeGroup and will be removed.

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
attr Attr

The group attr instance

required
skip_inner_classes bool

Whether the attr is circular reference, which means we can skip copying the inner classes.

False
Source code in xsdata/codegen/utils.py
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
@classmethod
def copy_group_attributes(
    cls, source: Class, target: Class, attr: Attr, skip_inner_classes: bool = False
):
    """Copy the attrs of the source class to the target class.

    The attr represents a reference to the source class which is
    derived from xs:group or xs:attributeGroup and will be removed.

    Args:
        source: The source class instance
        target: The target class instance
        attr: The group attr instance
        skip_inner_classes: Whether the attr is circular reference, which
            means we can skip copying the inner classes.
    """
    index = target.attrs.index(attr)
    target.attrs.pop(index)

    for source_attr in source.attrs:
        clone = cls.clone_attribute(source_attr, attr.restrictions)
        target.attrs.insert(index, clone)
        index += 1

        if not skip_inner_classes:
            cls.copy_inner_classes(source, target, clone)

copy_extensions(source, target, extension) classmethod

Copy the source class extensions to the target class instance.

Merge the extension restrictions with the source class extensions restrictions.

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
extension Extension

The extension instance that links the two classes together

required
Source code in xsdata/codegen/utils.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@classmethod
def copy_extensions(cls, source: Class, target: Class, extension: Extension):
    """Copy the source class extensions to the target class instance.

    Merge the extension restrictions with the source class extensions
    restrictions.

    Args:
        source: The source class instance
        target: The target class instance
        extension: The extension instance that links the two classes together
    """
    for ext in source.extensions:
        clone = ext.clone()
        clone.restrictions.merge(extension.restrictions)
        target.extensions.append(clone)

clone_attribute(attr, restrictions) classmethod

Clone the given attr and merge its restrictions with the given.

Parameters:

Name Type Description Default
attr Attr

The source attr instance

required
restrictions Restrictions

The additional restrictions, originated from a substitution or another attr.

required
Source code in xsdata/codegen/utils.py
161
162
163
164
165
166
167
168
169
170
171
172
@classmethod
def clone_attribute(cls, attr: Attr, restrictions: Restrictions) -> Attr:
    """Clone the given attr and merge its restrictions with the given.

    Args:
        attr: The source attr instance
        restrictions: The additional restrictions, originated from
            a substitution or another attr.
    """
    clone = attr.clone()
    clone.restrictions.merge(restrictions)
    return clone

copy_inner_classes(source, target, attr) classmethod

Copy inner classes from source to the target class instance.

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
attr Attr

The attr with the possible forward references

required
Source code in xsdata/codegen/utils.py
174
175
176
177
178
179
180
181
182
183
184
@classmethod
def copy_inner_classes(cls, source: Class, target: Class, attr: Attr):
    """Copy inner classes from source to the target class instance.

    Args:
        source: The source class instance
        target: The target class instance
        attr: The attr with the possible forward references
    """
    for attr_type in attr.types:
        cls.copy_inner_class(source, target, attr_type)

copy_inner_class(source, target, attr_type) classmethod

Find and copy the inner class from source to the target class instance.

Steps
  • Skip If the attr type is not a forward reference
  • Validate the inner class is not a circular reference to the target
  • Otherwise copy the inner class, and make sure it is re-sent for processing

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
attr_type AttrType

The attr type with the possible forward reference

required
Source code in xsdata/codegen/utils.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
@classmethod
def copy_inner_class(cls, source: Class, target: Class, attr_type: AttrType):
    """Find and copy the inner class from source to the target class instance.

    Steps:
        - Skip If the attr type is not a forward reference
        - Validate the inner class is not a circular reference to the target
        - Otherwise copy the inner class, and make sure it is re-sent for
            processing

    Args:
        source: The source class instance
        target: The target class instance
        attr_type: The attr type with the possible forward reference
    """
    if not attr_type.forward:
        return

    inner = ClassUtils.find_nested(source, attr_type.qname)
    if inner is target:
        attr_type.circular = True
        attr_type.reference = target.ref
    else:
        # In extreme cases this adds duplicate inner classes
        clone = inner.clone()
        clone.package = target.package
        clone.module = target.module
        clone.status = Status.RAW
        attr_type.reference = clone.ref
        clone.parent = target
        target.inner.append(clone)

find_attr(source, name) classmethod

Find an attr in the source class by its name.

Parameters:

Name Type Description Default
source Class

The source class instance

required
name str

The attr name to lookup

required

Returns:

Type Description
Optional[Attr]

An attr instance or None if no attr matched.

Source code in xsdata/codegen/utils.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
@classmethod
def find_attr(cls, source: Class, name: str) -> Optional[Attr]:
    """Find an attr in the source class by its name.

    Args:
        source: The source class instance
        name: The attr name to lookup

    Returns:
        An attr instance or None if no attr matched.
    """
    for attr in source.attrs:
        if attr.name == name:
            return attr

    return None

flatten(target, location) classmethod

Flatten the target class instance and its inner classes.

The inner classes are removed from target instance!

Parameters:

Name Type Description Default
target Class

The target class instance

required
location str

The source location of the target class

required

Yields:

Type Description
Class

An iterator over all the found classes.

Source code in xsdata/codegen/utils.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@classmethod
def flatten(cls, target: Class, location: str) -> Iterator[Class]:
    """Flatten the target class instance and its inner classes.

    The inner classes are removed from target instance!

    Args:
        target: The target class instance
        location: The source location of the target class

    Yields:
        An iterator over all the found classes.
    """
    target.location = location
    target.parent = None

    while target.inner:
        yield from cls.flatten(target.inner.pop(), location)

    for attr in target.attrs:
        attr.types = collections.unique_sequence(attr.types, key="qname")
        for tp in attr.types:
            tp.forward = False

    yield target

reduce_classes(classes) classmethod

Find duplicate classes and attrs and reduce them.

Parameters:

Name Type Description Default
classes List[Class]

A list of classes

required

Returns:

Type Description
List[Class]

A list of unique classes with no duplicate attrs.

Source code in xsdata/codegen/utils.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
@classmethod
def reduce_classes(cls, classes: List[Class]) -> List[Class]:
    """Find duplicate classes and attrs and reduce them.

    Args:
        classes: A list of classes

    Returns:
        A list of unique classes with no duplicate attrs.
    """
    result = []
    for group in collections.group_by(classes, key=get_qname).values():
        target = group[0].clone()
        target.attrs = cls.reduce_attributes(group)
        target.mixed = any(x.mixed for x in group)

        cls.cleanup_class(target)
        result.append(target)

    return result

reduce_attributes(classes) classmethod

Find and merge duplicate attrs from the given class list.

Parameters:

Name Type Description Default
classes List[Class]

A list of class instances

required

Returns:

Type Description
List[Attr]

A list of unique attr instances.

Source code in xsdata/codegen/utils.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
@classmethod
def reduce_attributes(cls, classes: List[Class]) -> List[Attr]:
    """Find and merge duplicate attrs from the given class list.

    Args:
        classes: A list of class instances

    Returns:
        A list of unique attr instances.
    """
    result = []
    for attr in cls.sorted_attrs(classes):
        added = False
        optional = False
        for obj in classes:
            pos = collections.find(obj.attrs, attr)
            if pos == -1:
                optional = True
            elif not added:
                added = True
                result.append(obj.attrs.pop(pos))
            else:
                cls.merge_attributes(result[-1], obj.attrs.pop(pos))

        if optional:
            result[-1].restrictions.min_occurs = 0

    return result

sorted_attrs(classes) classmethod

Sort and return the attrs from all the class list.

The list contains duplicate classes, the method tries to find all the attrs and sorts them by first occurrence.

Parameters:

Name Type Description Default
classes List[Class]

A list of duplicate class instances.

required

Returns:

Type Description
List[Attr]

A list of sorted duplicate attr instances.

Source code in xsdata/codegen/utils.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
@classmethod
def sorted_attrs(cls, classes: List[Class]) -> List[Attr]:
    """Sort and return the attrs from all the class list.

    The list contains duplicate classes, the method tries
    to find all the attrs and sorts them by first occurrence.

    Args:
        classes: A list of duplicate class instances.

    Returns:
        A list of sorted duplicate attr instances.
    """
    attrs: List[Attr] = []
    classes.sort(key=lambda x: len(x.attrs), reverse=True)

    for obj in classes:
        i = 0
        obj_attrs = obj.attrs.copy()

        while obj_attrs:
            pos = collections.find(attrs, obj_attrs[i])
            i += 1

            if pos > -1:
                insert = obj_attrs[: i - 1]
                del obj_attrs[:i]
                while insert:
                    attrs.insert(pos, insert.pop())

                i = 0
            elif i == len(obj_attrs):
                attrs.extend(obj_attrs)
                obj_attrs.clear()

    return attrs

merge_attributes(target, source) classmethod

Merge the source attr into the target instance.

Merge the types, select the min min_occurs and the max max_occurs from the two instances and copy the source sequence number to the target if it's currently not set.

Parameters:

Name Type Description Default
target Attr

The target attr instance which will be updated

required
source Attr

The source attr instance

required
Source code in xsdata/codegen/utils.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
@classmethod
def merge_attributes(cls, target: Attr, source: Attr):
    """Merge the source attr into the target instance.

    Merge the types, select the min min_occurs and the max max_occurs
    from the two instances and copy the source sequence number
    to the target if it's currently not set.

    Args:
        target: The target attr instance which will be updated
        source: The source attr instance
    """
    target.types.extend(tp for tp in source.types if tp not in target.types)

    target.restrictions.min_occurs = min(
        target.restrictions.min_occurs or 0,
        source.restrictions.min_occurs or 0,
    )

    target.restrictions.max_occurs = max(
        target.restrictions.max_occurs or 1,
        source.restrictions.max_occurs or 1,
    )

    if source.restrictions.sequence is not None:
        target.restrictions.sequence = source.restrictions.sequence

rename_duplicate_attributes(target) classmethod

Find and rename attributes with the same slug.

Source code in xsdata/codegen/utils.py
375
376
377
378
379
380
381
382
383
384
385
386
@classmethod
def rename_duplicate_attributes(cls, target: Class):
    """Find and rename attributes with the same slug."""
    grouped = collections.group_by(
        target.attrs, key=lambda x: x.slug or DEFAULT_ATTR_NAME
    )
    for items in grouped.values():
        total = len(items)
        if total == 2 and not items[0].is_enumeration:
            cls.rename_attribute_by_preference(*items)
        elif total > 1:
            cls.rename_attributes_by_index(target.attrs, items)

rename_attribute_by_preference(a, b) classmethod

Decide and rename one of the two given attributes.

When both attributes are derived from the same xs:tag and one of the two fields has a specific namespace prepend it to the name. Preferable rename the second attribute.

Otherwise, append the derived from tag to the name of one of the two attributes. Preferably rename the second field or the field derived from xs:attribute.

Parameters:

Name Type Description Default
a Attr

The first attr instance

required
b Attr

The second attr instance

required
Source code in xsdata/codegen/utils.py
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
@classmethod
def rename_attribute_by_preference(cls, a: Attr, b: Attr):
    """Decide and rename one of the two given attributes.

    When both attributes are derived from the same xs:tag and one of
    the two fields has a specific namespace prepend it to the name.
    Preferable rename the second attribute.

    Otherwise, append the derived from tag to the name of one of the
    two attributes. Preferably rename the second field or the field
    derived from xs:attribute.

    Args:
        a: The first attr instance
        b: The second attr instance
    """
    if a.tag == b.tag and (a.namespace or b.namespace):
        change = b if b.namespace else a
        assert change.namespace is not None
        change.name = f"{namespaces.clean_uri(change.namespace)}_{change.name}"
    else:
        change = b if b.is_attribute else a
        change.name = f"{change.name}_{change.tag}"

rename_attributes_by_index(attrs, rename) classmethod

Append the next available index number to all the rename attr names.

Parameters:

Name Type Description Default
attrs List[Attr]

A list of attr instances whose names must be protected

required
rename List[Attr]

A list of attr instances that need to be renamed

required
Source code in xsdata/codegen/utils.py
412
413
414
415
416
417
418
419
420
421
422
423
@classmethod
def rename_attributes_by_index(cls, attrs: List[Attr], rename: List[Attr]):
    """Append the next available index number to all the rename attr names.

    Args:
        attrs: A list of attr instances whose names must be protected
        rename: A list of attr instances that need to be renamed
    """
    for index in range(1, len(rename)):
        reserved = set(map(get_slug, attrs))
        name = rename[index].name
        rename[index].name = cls.unique_name(name, reserved)

unique_name(name, reserved) classmethod

Append the next available index number to the name.

Parameters:

Name Type Description Default
name str

An object name

required
reserved Set[str]

A set of reserved names

required

Returns:

Type Description
str

The new name with the index suffix

Source code in xsdata/codegen/utils.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
@classmethod
def unique_name(cls, name: str, reserved: Set[str]) -> str:
    """Append the next available index number to the name.

    Args:
        name: An object name
        reserved: A set of reserved names

    Returns:
        The new name with the index suffix
    """
    if text.alnum(name) in reserved:
        index = 1
        while text.alnum(f"{name}_{index}") in reserved:
            index += 1

        return f"{name}_{index}"

    return name

cleanup_class(target) classmethod

Go through the target class attrs and filter their types.

Removes duplicate and invalid types.

Parameters:

Name Type Description Default
target Class

The target class instance to inspect

required
Source code in xsdata/codegen/utils.py
445
446
447
448
449
450
451
452
453
454
455
@classmethod
def cleanup_class(cls, target: Class):
    """Go through the target class attrs and filter their types.

    Removes duplicate and invalid types.

    Args:
        target: The target class instance to inspect
    """
    for attr in target.attrs:
        attr.types = cls.filter_types(attr.types)

filter_types(types) classmethod

Remove duplicate and invalid types.

Invalid
  1. xs:error
  2. xs:anyType and xs:anySimpleType when there are other types present

Parameters:

Name Type Description Default
types List[AttrType]

A list of attr type instances

required

Returns:

Type Description
List[AttrType]

The new list of unique and valid attr type instances.

Source code in xsdata/codegen/utils.py
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
@classmethod
def filter_types(cls, types: List[AttrType]) -> List[AttrType]:
    """Remove duplicate and invalid types.

    Invalid:
        1. xs:error
        2. xs:anyType and xs:anySimpleType when there are other types present

    Args:
        types: A list of attr type instances

    Returns:
        The new list of unique and valid attr type instances.
    """
    types = collections.unique_sequence(types, key="qname")
    types = collections.remove(types, lambda x: x.datatype == DataType.ERROR)

    if len(types) > 1:
        types = collections.remove(
            types,
            lambda x: x.datatype in (DataType.ANY_TYPE, DataType.ANY_SIMPLE_TYPE),
        )

    if not types:
        types.append(AttrType(qname=str(DataType.STRING), native=True))

    return types

find_nested(target, qname) classmethod

Find a nested class by qname.

Breath-first search implementation, that goes from the current level to bottom before looking for outer classes.

Parameters:

Name Type Description Default
target Class

The class instance to begin the search

required
qname str

The qualified name of the nested class to find

required

Raises:

Type Description
CodegenException

If the nested class cannot be found.

Returns:

Type Description
Class

The nested class instance.

Source code in xsdata/codegen/utils.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
@classmethod
def find_nested(cls, target: Class, qname: str) -> Class:
    """Find a nested class by qname.

    Breath-first search implementation, that goes
    from the current level to bottom before looking
    for outer classes.

    Args:
        target: The class instance to begin the search
        qname: The qualified name of the nested class to find

    Raises:
        CodegenException: If the nested class cannot be found.

    Returns:
        The nested class instance.
    """
    queue: Deque[Class] = deque()
    visited: Set[int] = set()

    if target.inner:
        queue.extend(target.inner)
    elif target.parent:
        queue.append(target.parent)

    while len(queue) > 0:
        item = queue.popleft()
        visited.add(item.ref)
        if item.qname == qname:
            return item

        for inner in item.inner:
            if inner.ref not in visited:
                queue.append(inner)

        if len(queue) == 0 and item.parent:
            queue.append(item.parent)

    raise CodegenError("Missing inner class", parent=target, qname=qname)