Skip to content

update_attributes_effective_choice

xsdata.codegen.handlers.update_attributes_effective_choice

UpdateAttributesEffectiveChoice

Bases: HandlerInterface

Detect implied repeated choices and update them.

valid eg: symmetrical sequence:

Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
  9
 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
class UpdateAttributesEffectiveChoice(HandlerInterface):
    """Detect implied repeated choices and update them.

    valid eg: <a/><b/><a/><c/>
    symmetrical sequence: <a/><b/><a/><b/>
    """

    __slots__ = ()

    def process(self, target: Class):
        """Process entrypoint for classes.

        Ignore enumerations, for performance reasons.

        Args:
            target: The target class instance
        """
        if target.is_enumeration:
            return

        groups = self.group_repeating_attrs(target)
        if groups:
            groups = list(collections.connected_components(groups))
            target.attrs = self.merge_attrs(target, groups)

            self.reset_symmetrical_choices(target)

    @classmethod
    def reset_symmetrical_choices(cls, target: Class):
        """Mark symmetrical choices as sequences.

        Args:
            target: The target class instance
        """
        groups = collections.group_by(target.attrs, get_restriction_choice)
        for choice, attrs in groups.items():
            if choice is None or choice > 0:
                continue

            min_occurs = set()
            max_occurs = set()
            sequences = set()
            for attr in attrs:
                min_occurs.add(attr.restrictions.min_occurs)
                max_occurs.add(attr.restrictions.max_occurs)

                if attr.restrictions.sequence:
                    sequences.add(attr.restrictions.sequence)

            if len(min_occurs) == len(max_occurs) == len(sequences) == 1:
                for attr in attrs:
                    assert attr.restrictions.max_occurs is not None
                    assert attr.restrictions.sequence is not None

                    attr.restrictions.choice = None
                    cls.reset_effective_choice(
                        attr.restrictions.path,
                        attr.restrictions.sequence,
                        attr.restrictions.max_occurs,
                    )

    @classmethod
    def reset_effective_choice(
        cls,
        paths: List[Tuple[str, int, int, int]],
        index: int,
        max_occur: int,
    ):
        """Update an attr path to resemble a repeatable sequence.

        Args:
            paths: The paths of an attr
            index: The sequence index
            max_occur: The new max occurrences
        """
        for i, path in enumerate(paths):
            if path[0] == "s" and path[1] == index and path[3] == 1:
                new_path = (*path[:-1], max_occur)
                paths[i] = new_path
                break

    @classmethod
    def merge_attrs(cls, target: Class, groups: List[List[int]]) -> List[Attr]:
        """Merge same name/tag/namespace attrs.

        Args:
            target: The target class
            groups: The list of connected attr indexes

        Returns:
            The final list of target class attrs
        """
        attrs = []

        for index, attr in enumerate(target.attrs):
            group = collections.find_connected_component(groups, index)

            if group == -1:
                attrs.append(attr)
                continue

            pos = collections.find(attrs, attr)
            if pos == -1:
                attr.restrictions.choice = (group * -1) - 1
                attrs.append(attr)
            else:
                existing = attrs[pos]
                assert existing.restrictions.min_occurs is not None
                assert existing.restrictions.max_occurs is not None

                existing.restrictions.min_occurs += attr.restrictions.min_occurs or 0
                existing.restrictions.max_occurs += attr.restrictions.max_occurs or 0

        return attrs

    @classmethod
    def group_repeating_attrs(cls, target: Class) -> List[List[int]]:
        """Create a list of indexes of the same attrs.

        Example: [
            [0, 1 ,2],
            [3, 4, 6],
            [5,]
        ]

        Args:
            target: The target class instance

        Returns:
            The list of indexes

        """
        counters = defaultdict(list)
        for index, attr in enumerate(target.attrs):
            if not attr.is_attribute:
                counters[attr.key].append(index)

        groups = []
        for x in counters.values():
            if len(x) > 1:
                groups.append(list(range(x[0], x[-1] + 1)))

        return groups

process(target)

Process entrypoint for classes.

Ignore enumerations, for performance reasons.

Parameters:

Name Type Description Default
target Class

The target class instance

required
Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def process(self, target: Class):
    """Process entrypoint for classes.

    Ignore enumerations, for performance reasons.

    Args:
        target: The target class instance
    """
    if target.is_enumeration:
        return

    groups = self.group_repeating_attrs(target)
    if groups:
        groups = list(collections.connected_components(groups))
        target.attrs = self.merge_attrs(target, groups)

        self.reset_symmetrical_choices(target)

reset_symmetrical_choices(target) classmethod

Mark symmetrical choices as sequences.

Parameters:

Name Type Description Default
target Class

The target class instance

required
Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
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
@classmethod
def reset_symmetrical_choices(cls, target: Class):
    """Mark symmetrical choices as sequences.

    Args:
        target: The target class instance
    """
    groups = collections.group_by(target.attrs, get_restriction_choice)
    for choice, attrs in groups.items():
        if choice is None or choice > 0:
            continue

        min_occurs = set()
        max_occurs = set()
        sequences = set()
        for attr in attrs:
            min_occurs.add(attr.restrictions.min_occurs)
            max_occurs.add(attr.restrictions.max_occurs)

            if attr.restrictions.sequence:
                sequences.add(attr.restrictions.sequence)

        if len(min_occurs) == len(max_occurs) == len(sequences) == 1:
            for attr in attrs:
                assert attr.restrictions.max_occurs is not None
                assert attr.restrictions.sequence is not None

                attr.restrictions.choice = None
                cls.reset_effective_choice(
                    attr.restrictions.path,
                    attr.restrictions.sequence,
                    attr.restrictions.max_occurs,
                )

reset_effective_choice(paths, index, max_occur) classmethod

Update an attr path to resemble a repeatable sequence.

Parameters:

Name Type Description Default
paths List[Tuple[str, int, int, int]]

The paths of an attr

required
index int

The sequence index

required
max_occur int

The new max occurrences

required
Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@classmethod
def reset_effective_choice(
    cls,
    paths: List[Tuple[str, int, int, int]],
    index: int,
    max_occur: int,
):
    """Update an attr path to resemble a repeatable sequence.

    Args:
        paths: The paths of an attr
        index: The sequence index
        max_occur: The new max occurrences
    """
    for i, path in enumerate(paths):
        if path[0] == "s" and path[1] == index and path[3] == 1:
            new_path = (*path[:-1], max_occur)
            paths[i] = new_path
            break

merge_attrs(target, groups) classmethod

Merge same name/tag/namespace attrs.

Parameters:

Name Type Description Default
target Class

The target class

required
groups List[List[int]]

The list of connected attr indexes

required

Returns:

Type Description
List[Attr]

The final list of target class attrs

Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
 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
@classmethod
def merge_attrs(cls, target: Class, groups: List[List[int]]) -> List[Attr]:
    """Merge same name/tag/namespace attrs.

    Args:
        target: The target class
        groups: The list of connected attr indexes

    Returns:
        The final list of target class attrs
    """
    attrs = []

    for index, attr in enumerate(target.attrs):
        group = collections.find_connected_component(groups, index)

        if group == -1:
            attrs.append(attr)
            continue

        pos = collections.find(attrs, attr)
        if pos == -1:
            attr.restrictions.choice = (group * -1) - 1
            attrs.append(attr)
        else:
            existing = attrs[pos]
            assert existing.restrictions.min_occurs is not None
            assert existing.restrictions.max_occurs is not None

            existing.restrictions.min_occurs += attr.restrictions.min_occurs or 0
            existing.restrictions.max_occurs += attr.restrictions.max_occurs or 0

    return attrs

group_repeating_attrs(target) classmethod

Create a list of indexes of the same attrs.

[

[0, 1 ,2], [3, 4, 6], [5,]

]

Parameters:

Name Type Description Default
target Class

The target class instance

required

Returns:

Type Description
List[List[int]]

The list of indexes

Source code in xsdata/codegen/handlers/update_attributes_effective_choice.py
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
@classmethod
def group_repeating_attrs(cls, target: Class) -> List[List[int]]:
    """Create a list of indexes of the same attrs.

    Example: [
        [0, 1 ,2],
        [3, 4, 6],
        [5,]
    ]

    Args:
        target: The target class instance

    Returns:
        The list of indexes

    """
    counters = defaultdict(list)
    for index, attr in enumerate(target.attrs):
        if not attr.is_attribute:
            counters[attr.key].append(index)

    groups = []
    for x in counters.values():
        if len(x) > 1:
            groups.append(list(range(x[0], x[-1] + 1)))

    return groups