Skip to content

# union

## `xsdata.formats.dataclass.parsers.nodes.union`

### `UnionNode`

Bases: `XmlNode`

XmlNode for union fields with at least one data class.

The node will record all child events and in the end will replay them and try to build all possible objects and sort them by score before deciding the winner.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`var` | `XmlVar` | The xml var instance | required
`attrs` | `dict` | The element attributes | required
`ns_map` | `dict` | The element namespace prefix-URI map | required
`position` | `int` | The current objects length, everything after this position are considered children of this node. | required
`config` | `ParserConfig` | The parser config instance | required
`context` | `XmlContext` | The xml context instance | required

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

     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

    class UnionNode(XmlNode):
        """XmlNode for union fields with at least one data class.

        The node will record all child events and in the end will replay
        them and try to build all possible objects and sort them by score
        before deciding the winner.

        Args:
            var: The xml var instance
            attrs: The element attributes
            ns_map: The element namespace prefix-URI map
            position: The current objects length, everything after
                this position are considered children of this node.
            config: The parser config instance
            context: The xml context instance
        """

        __slots__ = (
            "attrs",
            "candidates",
            "config",
            "context",
            "events",
            "level",
            "meta",
            "ns_map",
            "position",
            "var",
        )

        def __init__(
            self,
            meta: XmlMeta,
            var: XmlVar,
            attrs: dict,
            ns_map: dict,
            position: int,
            config: ParserConfig,
            context: XmlContext,
        ):
            """Initialize the xml node."""
            self.meta = meta
            self.var = var
            self.attrs = attrs
            self.ns_map = ns_map
            self.position = position
            self.config = config
            self.context = context
            self.level = 0
            self.candidates = self.filter_candidates()
            self.events: list[tuple[str, str, Any, Any]] = []

        def filter_candidates(self) -> list[type]:
            """Filter union candidates by fixed attributes."""
            candidates = list(self.var.types)
            fixed_attribute = functools.partial(
                self.filter_fixed_attrs, parent_ns=target_uri(self.var.qname)
            )

            return list(filter(fixed_attribute, candidates))

        def filter_fixed_attrs(self, candidate: type, parent_ns: str | None) -> bool:
            """Return whether the node attrs are incompatible with fixed attrs.

            Args:
                candidate: The candidate type
                parent_ns: The parent namespace
            """
            if not self.context.class_type.is_model(candidate):
                return not self.attrs

            meta = self.context.build(candidate, parent_ns=parent_ns)
            for qname, value in self.attrs.items():
                var = meta.find_attribute(qname)
                if not var or var.init:
                    continue

                try:
                    ParserUtils.validate_fixed_value(meta, var, value)
                except ParserError:
                    return False

            return True

        def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode:
            """Record the event for the child element.

            This entry point records all events, as it's not possible
            to detect the target parsed object type just yet. When
            this node ends, it will replay all events and attempt
            to find the best matching type for the parsed object.

            Args:
                qname: The element qualified name
                attrs: The element attributes
                ns_map: The element namespace prefix-URI map
                position: The current length of the intermediate objects
            """
            self.level += 1
            self.events.append(("start", qname, copy.deepcopy(attrs), ns_map))
            return self

        def bind(
            self,
            qname: str,
            text: str | None,
            tail: str | None,
            objects: list,
        ) -> bool:
            """Bind the parsed data into an object for the ending element.

            This entry point is called when a xml element ends and is
            responsible to replay all xml events and parse/bind all
            the children objects.

            Args:
                qname: The element qualified name
                text: The element text content
                tail: The element tail content
                objects: The list of intermediate parsed objects

            Returns:
                Always returns true, if the binding process fails
                it raises an exception.

            Raises:
                ParserError: If none of the candidate types matched
                    the replayed events.
            """
            self.events.append(("end", qname, text, tail))

            if self.level > 0:
                self.level -= 1
                return False

            self.events.insert(0, ("start", qname, copy.deepcopy(self.attrs), self.ns_map))

            obj = None
            max_score = -1.0
            parent_namespace = target_uri(qname)
            config = replace(self.config, fail_on_converter_warnings=True)

            for candidate in self.candidates:
                result: Any = None
                with suppress(Exception):
                    if self.context.class_type.is_model(candidate):
                        self.context.build(candidate, parent_ns=parent_namespace)
                        parser = NodeParser(
                            config=config,
                            context=self.context,
                            handler=EventsHandler,
                        )
                        result = parser.parse(self.events, candidate)
                    else:
                        result = ParserUtils.parse_var(
                            meta=self.meta,
                            var=self.var,
                            config=config,
                            value=text,
                            types=[candidate],
                            ns_map=self.ns_map,
                        )

                score = self.context.class_type.score_object(result)
                if score > max_score:
                    max_score = score
                    obj = result

            if obj:
                objects.append((self.var.qname, obj))

                return True

            raise ParserError(f"Failed to parse union node: {self.var.qname}")

#### `__init__(meta, var, attrs, ns_map, position, config, context)`

Initialize the xml node.

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

    def __init__(
        self,
        meta: XmlMeta,
        var: XmlVar,
        attrs: dict,
        ns_map: dict,
        position: int,
        config: ParserConfig,
        context: XmlContext,
    ):
        """Initialize the xml node."""
        self.meta = meta
        self.var = var
        self.attrs = attrs
        self.ns_map = ns_map
        self.position = position
        self.config = config
        self.context = context
        self.level = 0
        self.candidates = self.filter_candidates()
        self.events: list[tuple[str, str, Any, Any]] = []

#### `filter_candidates()`

Filter union candidates by fixed attributes.

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

    69
    70
    71
    72
    73
    74
    75
    76

    def filter_candidates(self) -> list[type]:
        """Filter union candidates by fixed attributes."""
        candidates = list(self.var.types)
        fixed_attribute = functools.partial(
            self.filter_fixed_attrs, parent_ns=target_uri(self.var.qname)
        )

        return list(filter(fixed_attribute, candidates))

#### `filter_fixed_attrs(candidate, parent_ns)`

Return whether the node attrs are incompatible with fixed attrs.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`candidate` | `type` | The candidate type | required
`parent_ns` | `str | None` | The parent namespace | required

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99

    def filter_fixed_attrs(self, candidate: type, parent_ns: str | None) -> bool:
        """Return whether the node attrs are incompatible with fixed attrs.

        Args:
            candidate: The candidate type
            parent_ns: The parent namespace
        """
        if not self.context.class_type.is_model(candidate):
            return not self.attrs

        meta = self.context.build(candidate, parent_ns=parent_ns)
        for qname, value in self.attrs.items():
            var = meta.find_attribute(qname)
            if not var or var.init:
                continue

            try:
                ParserUtils.validate_fixed_value(meta, var, value)
            except ParserError:
                return False

        return True

#### `child(qname, attrs, ns_map, position)`

Record the event for the child element.

This entry point records all events, as it's not possible to detect the target parsed object type just yet. When this node ends, it will replay all events and attempt to find the best matching type for the parsed object.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`qname` | `str` | The element qualified name | required
`attrs` | `dict` | The element attributes | required
`ns_map` | `dict` | The element namespace prefix-URI map | required
`position` | `int` | The current length of the intermediate objects | required

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117

    def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode:
        """Record the event for the child element.

        This entry point records all events, as it's not possible
        to detect the target parsed object type just yet. When
        this node ends, it will replay all events and attempt
        to find the best matching type for the parsed object.

        Args:
            qname: The element qualified name
            attrs: The element attributes
            ns_map: The element namespace prefix-URI map
            position: The current length of the intermediate objects
        """
        self.level += 1
        self.events.append(("start", qname, copy.deepcopy(attrs), ns_map))
        return self

#### `bind(qname, text, tail, objects)`

Bind the parsed data into an object for the ending element.

This entry point is called when a xml element ends and is responsible to replay all xml events and parse/bind all the children objects.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`qname` | `str` | The element qualified name | required
`text` | `str | None` | The element text content | required
`tail` | `str | None` | The element tail content | required
`objects` | `list` | The list of intermediate parsed objects | required

Returns:

Type | Description
--- | ---
`bool` | Always returns true, if the binding process fails
`bool` | it raises an exception.

Raises:

Type | Description
--- | ---
`ParserError` | If none of the candidate types matched the replayed events.

Source code in `xsdata/formats/dataclass/parsers/nodes/union.py`

    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

    def bind(
        self,
        qname: str,
        text: str | None,
        tail: str | None,
        objects: list,
    ) -> bool:
        """Bind the parsed data into an object for the ending element.

        This entry point is called when a xml element ends and is
        responsible to replay all xml events and parse/bind all
        the children objects.

        Args:
            qname: The element qualified name
            text: The element text content
            tail: The element tail content
            objects: The list of intermediate parsed objects

        Returns:
            Always returns true, if the binding process fails
            it raises an exception.

        Raises:
            ParserError: If none of the candidate types matched
                the replayed events.
        """
        self.events.append(("end", qname, text, tail))

        if self.level > 0:
            self.level -= 1
            return False

        self.events.insert(0, ("start", qname, copy.deepcopy(self.attrs), self.ns_map))

        obj = None
        max_score = -1.0
        parent_namespace = target_uri(qname)
        config = replace(self.config, fail_on_converter_warnings=True)

        for candidate in self.candidates:
            result: Any = None
            with suppress(Exception):
                if self.context.class_type.is_model(candidate):
                    self.context.build(candidate, parent_ns=parent_namespace)
                    parser = NodeParser(
                        config=config,
                        context=self.context,
                        handler=EventsHandler,
                    )
                    result = parser.parse(self.events, candidate)
                else:
                    result = ParserUtils.parse_var(
                        meta=self.meta,
                        var=self.var,
                        config=config,
                        value=text,
                        types=[candidate],
                        ns_map=self.ns_map,
                    )

            score = self.context.class_type.score_object(result)
            if score > max_score:
                max_score = score
                obj = result

        if obj:
            objects.append((self.var.qname, obj))

            return True

        raise ParserError(f"Failed to parse union node: {self.var.qname}")