Skip to content

rename_duplicate_classes

xsdata.codegen.handlers.rename_duplicate_classes

RenameDuplicateClasses

Bases: ContainerHandlerInterface

Resolve class name conflicts depending on the output structure style.

Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
 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
class RenameDuplicateClasses(ContainerHandlerInterface):
    """Resolve class name conflicts depending on the output structure style."""

    __slots__ = ()

    def run(self):
        """Detect and resolve class name conflicts."""
        use_name = self.should_use_names()
        getter = get_name if use_name else get_qname
        groups = collections.group_by(self.container, lambda x: text.alnum(getter(x)))

        for classes in groups.values():
            if len(classes) > 1:
                self.rename_classes(classes, use_name)

    def should_use_names(self) -> bool:
        """Determine if names or qualified names should be used for detection.

        Strict unique names:
            - Single package
            - Clustered packages
            - All classes have the same source location.
        """
        return (
            self.container.config.output.structure_style in REQUIRE_UNIQUE_NAMES
            or len(set(map(get_location, self.container))) == 1
        )

    def rename_classes(self, classes: List[Class], use_name: bool):
        """Rename all the classes in the list.

        Protect classes derived from xs:element if there is only one in
        the list.

        Args:
            classes: A list of classes with duplicate names
            use_name: Whether simple or qualified names should be used
                during renaming
        """
        total_elements = sum(x.is_element for x in classes)
        for target in sorted(classes, key=get_name):
            if not target.is_element or total_elements > 1:
                self.rename_class(target, use_name)

    def rename_class(self, target: Class, use_name: bool):
        """Find the next available class name.

        Save the original name in the class metadata and update
        the class qualified name and all classes that depend on
        the target class.

        Args:
            target: The target class instance to rename
            use_name: Whether simple or qualified names should be
                used during renaming
        """
        qname = target.qname
        namespace, name = namespaces.split_qname(target.qname)
        target.qname = self.next_qname(namespace, name, use_name)
        target.meta_name = name
        self.container.reset(target, qname)

        for item in self.container:
            self.rename_class_dependencies(item, id(target), target.qname)

    def next_qname(self, namespace: str, name: str, use_name: bool) -> str:
        """Use int suffixes to get the next available qualified name.

        Args:
            namespace: The class namespace
            name: The class name
            use_name: Whether simple or qualified names should be
                used during renaming
        """
        index = 0

        if use_name:
            reserved = {text.alnum(obj.name) for obj in self.container}
        else:
            reserved = {text.alnum(obj.qname) for obj in self.container}

        while True:
            index += 1
            new_name = f"{name}_{index}"
            qname = namespaces.build_qname(namespace, new_name)
            cmp = text.alnum(new_name if use_name else qname)

            if cmp not in reserved:
                return qname

    def rename_class_dependencies(self, target: Class, reference: int, replace: str):
        """Search and replace the old qualified class name in all classes.

        Args:
            target: The target class instance to inspect
            reference: The reference id of the renamed class
            replace: The new qualified name of the renamed class
        """
        for attr in target.attrs:
            self.rename_attr_dependencies(attr, reference, replace)

        for ext in target.extensions:
            if ext.type.reference == reference:
                ext.type.qname = replace

        for inner in target.inner:
            self.rename_class_dependencies(inner, reference, replace)

    def rename_attr_dependencies(self, attr: Attr, reference: int, replace: str):
        """Search and replace the old qualified class name in the attr types.

        This also covers any choices and references to enum values.

        Args:
            attr: The target attr instance to inspect
            reference: The reference id of the renamed class
            replace: The new qualified name of the renamed class
        """
        for attr_type in attr.types:
            if attr_type.reference == reference:
                attr_type.qname = replace

                if isinstance(attr.default, str) and attr.default.startswith("@enum@"):
                    members = text.suffix(attr.default, "::")
                    attr.default = f"@enum@{replace}::{members}"

        for choice in attr.choices:
            self.rename_attr_dependencies(choice, reference, replace)

run()

Detect and resolve class name conflicts.

Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
16
17
18
19
20
21
22
23
24
def run(self):
    """Detect and resolve class name conflicts."""
    use_name = self.should_use_names()
    getter = get_name if use_name else get_qname
    groups = collections.group_by(self.container, lambda x: text.alnum(getter(x)))

    for classes in groups.values():
        if len(classes) > 1:
            self.rename_classes(classes, use_name)

should_use_names()

Determine if names or qualified names should be used for detection.

Strict unique names
  • Single package
  • Clustered packages
  • All classes have the same source location.
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
26
27
28
29
30
31
32
33
34
35
36
37
def should_use_names(self) -> bool:
    """Determine if names or qualified names should be used for detection.

    Strict unique names:
        - Single package
        - Clustered packages
        - All classes have the same source location.
    """
    return (
        self.container.config.output.structure_style in REQUIRE_UNIQUE_NAMES
        or len(set(map(get_location, self.container))) == 1
    )

rename_classes(classes, use_name)

Rename all the classes in the list.

Protect classes derived from xs:element if there is only one in the list.

Parameters:

Name Type Description Default
classes List[Class]

A list of classes with duplicate names

required
use_name bool

Whether simple or qualified names should be used during renaming

required
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def rename_classes(self, classes: List[Class], use_name: bool):
    """Rename all the classes in the list.

    Protect classes derived from xs:element if there is only one in
    the list.

    Args:
        classes: A list of classes with duplicate names
        use_name: Whether simple or qualified names should be used
            during renaming
    """
    total_elements = sum(x.is_element for x in classes)
    for target in sorted(classes, key=get_name):
        if not target.is_element or total_elements > 1:
            self.rename_class(target, use_name)

rename_class(target, use_name)

Find the next available class name.

Save the original name in the class metadata and update the class qualified name and all classes that depend on the target class.

Parameters:

Name Type Description Default
target Class

The target class instance to rename

required
use_name bool

Whether simple or qualified names should be used during renaming

required
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def rename_class(self, target: Class, use_name: bool):
    """Find the next available class name.

    Save the original name in the class metadata and update
    the class qualified name and all classes that depend on
    the target class.

    Args:
        target: The target class instance to rename
        use_name: Whether simple or qualified names should be
            used during renaming
    """
    qname = target.qname
    namespace, name = namespaces.split_qname(target.qname)
    target.qname = self.next_qname(namespace, name, use_name)
    target.meta_name = name
    self.container.reset(target, qname)

    for item in self.container:
        self.rename_class_dependencies(item, id(target), target.qname)

next_qname(namespace, name, use_name)

Use int suffixes to get the next available qualified name.

Parameters:

Name Type Description Default
namespace str

The class namespace

required
name str

The class name

required
use_name bool

Whether simple or qualified names should be used during renaming

required
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def next_qname(self, namespace: str, name: str, use_name: bool) -> str:
    """Use int suffixes to get the next available qualified name.

    Args:
        namespace: The class namespace
        name: The class name
        use_name: Whether simple or qualified names should be
            used during renaming
    """
    index = 0

    if use_name:
        reserved = {text.alnum(obj.name) for obj in self.container}
    else:
        reserved = {text.alnum(obj.qname) for obj in self.container}

    while True:
        index += 1
        new_name = f"{name}_{index}"
        qname = namespaces.build_qname(namespace, new_name)
        cmp = text.alnum(new_name if use_name else qname)

        if cmp not in reserved:
            return qname

rename_class_dependencies(target, reference, replace)

Search and replace the old qualified class name in all classes.

Parameters:

Name Type Description Default
target Class

The target class instance to inspect

required
reference int

The reference id of the renamed class

required
replace str

The new qualified name of the renamed class

required
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def rename_class_dependencies(self, target: Class, reference: int, replace: str):
    """Search and replace the old qualified class name in all classes.

    Args:
        target: The target class instance to inspect
        reference: The reference id of the renamed class
        replace: The new qualified name of the renamed class
    """
    for attr in target.attrs:
        self.rename_attr_dependencies(attr, reference, replace)

    for ext in target.extensions:
        if ext.type.reference == reference:
            ext.type.qname = replace

    for inner in target.inner:
        self.rename_class_dependencies(inner, reference, replace)

rename_attr_dependencies(attr, reference, replace)

Search and replace the old qualified class name in the attr types.

This also covers any choices and references to enum values.

Parameters:

Name Type Description Default
attr Attr

The target attr instance to inspect

required
reference int

The reference id of the renamed class

required
replace str

The new qualified name of the renamed class

required
Source code in xsdata/codegen/handlers/rename_duplicate_classes.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def rename_attr_dependencies(self, attr: Attr, reference: int, replace: str):
    """Search and replace the old qualified class name in the attr types.

    This also covers any choices and references to enum values.

    Args:
        attr: The target attr instance to inspect
        reference: The reference id of the renamed class
        replace: The new qualified name of the renamed class
    """
    for attr_type in attr.types:
        if attr_type.reference == reference:
            attr_type.qname = replace

            if isinstance(attr.default, str) and attr.default.startswith("@enum@"):
                members = text.suffix(attr.default, "::")
                attr.default = f"@enum@{replace}::{members}"

    for choice in attr.choices:
        self.rename_attr_dependencies(choice, reference, replace)