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
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__ = (
        "meta",
        "var",
        "attrs",
        "ns_map",
        "position",
        "config",
        "context",
        "level",
        "events",
        "candidates",
    )

    def __init__(
        self,
        meta: XmlMeta,
        var: XmlVar,
        attrs: Dict,
        ns_map: Dict,
        position: int,
        config: ParserConfig,
        context: XmlContext,
    ):
        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: Optional[str]) -> 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: Optional[str],
        tail: Optional[str],
        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 = 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}")

filter_candidates()

Filter union candidates by fixed attributes.

Source code in xsdata/formats/dataclass/parsers/nodes/union.py
68
69
70
71
72
73
74
75
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 Optional[str]

The parent namespace

required
Source code in xsdata/formats/dataclass/parsers/nodes/union.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def filter_fixed_attrs(self, candidate: Type, parent_ns: Optional[str]) -> 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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 Optional[str]

The element text content

required
tail Optional[str]

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
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
def bind(
    self,
    qname: str,
    text: Optional[str],
    tail: Optional[str],
    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 = 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}")