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

    def __init__(
        self,
        var: XmlVar,
        attrs: Dict,
        ns_map: Dict,
        position: int,
        config: ParserConfig,
        context: XmlContext,
    ):
        self.var = var
        self.attrs = attrs
        self.ns_map = ns_map
        self.position = position
        self.config = config
        self.context = context
        self.level = 0
        self.events: List[Tuple[str, str, Any, Any]] = []

    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)
        for clazz in self.var.types:
            if self.context.class_type.is_model(clazz):
                self.context.build(clazz, parent_ns=parent_namespace)
                candidate = self.parse_class(clazz)
            else:
                candidate = self.parse_value(text, [clazz])

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

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

            return True

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

    def parse_class(self, clazz: Type[T]) -> Optional[T]:
        """Replay the recorded events and attempt to build the target class.

        Args:
            clazz: The target class

        Returns:
             The target class instance or None if the recorded
             xml events didn't fit the class.
        """
        try:
            with warnings.catch_warnings():
                warnings.filterwarnings("error", category=ConverterWarning)

                parser = NodeParser(
                    config=self.config, context=self.context, handler=EventsHandler
                )
                return parser.parse(self.events, clazz)
        except Exception:
            return None

    def parse_value(self, value: Any, types: List[Type]) -> Any:
        """Parse simple values.

        Args:
            value: The xml value
            types: The list of the candidate simple types

        Returns:
            The parsed value or None if value didn't match
            with any of the given types.
        """
        try:
            with warnings.catch_warnings():
                warnings.filterwarnings("error", category=ConverterWarning)
                return ParserUtils.parse_value(
                    value=value, types=types, ns_map=self.ns_map
                )
        except Exception:
            return None

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
 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
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)
    for clazz in self.var.types:
        if self.context.class_type.is_model(clazz):
            self.context.build(clazz, parent_ns=parent_namespace)
            candidate = self.parse_class(clazz)
        else:
            candidate = self.parse_value(text, [clazz])

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

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

        return True

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

parse_class(clazz)

Replay the recorded events and attempt to build the target class.

Parameters:

Name Type Description Default
clazz Type[T]

The target class

required

Returns:

Type Description
Optional[T]

The target class instance or None if the recorded

Optional[T]

xml events didn't fit the class.

Source code in xsdata/formats/dataclass/parsers/nodes/union.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def parse_class(self, clazz: Type[T]) -> Optional[T]:
    """Replay the recorded events and attempt to build the target class.

    Args:
        clazz: The target class

    Returns:
         The target class instance or None if the recorded
         xml events didn't fit the class.
    """
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("error", category=ConverterWarning)

            parser = NodeParser(
                config=self.config, context=self.context, handler=EventsHandler
            )
            return parser.parse(self.events, clazz)
    except Exception:
        return None

parse_value(value, types)

Parse simple values.

Parameters:

Name Type Description Default
value Any

The xml value

required
types List[Type]

The list of the candidate simple types

required

Returns:

Type Description
Any

The parsed value or None if value didn't match

Any

with any of the given types.

Source code in xsdata/formats/dataclass/parsers/nodes/union.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def parse_value(self, value: Any, types: List[Type]) -> Any:
    """Parse simple values.

    Args:
        value: The xml value
        types: The list of the candidate simple types

    Returns:
        The parsed value or None if value didn't match
        with any of the given types.
    """
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("error", category=ConverterWarning)
            return ParserUtils.parse_value(
                value=value, types=types, ns_map=self.ns_map
            )
    except Exception:
        return None