Skip to content

mixins

xsdata.formats.dataclass.serializers.mixins

XmlWriterEvent

Event names.

Source code in xsdata/formats/dataclass/serializers/mixins.py
29
30
31
32
33
34
35
class XmlWriterEvent:
    """Event names."""

    START: Final = "start"
    ATTR: Final = "attr"
    DATA: Final = "data"
    END: Final = "end"

XmlWriter

Bases: ABC

A consistency wrapper for sax content handlers.

Parameters:

Name Type Description Default
config SerializerConfig

The serializer config instance

required
output TextIO

The output stream to write the result

required
ns_map Dict

A user defined namespace prefix-URI map

required

Attributes:

Name Type Description
handler

The content handler instance

in_tail

Specifies whether the text content has been written

tail Optional[str]

The current element tail content

attrs Dict

The current element attributes

ns_context List[Dict]

The namespace context queue

pending_tag Optional[Tuple]

The pending element namespace, name tuple

pending_prefixes List[List]

The pending element namespace prefixes

Source code in xsdata/formats/dataclass/serializers/mixins.py
 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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
class XmlWriter(abc.ABC):
    """A consistency wrapper for sax content handlers.

    Args:
        config: The serializer config instance
        output: The output stream to write the result
        ns_map: A user defined namespace prefix-URI map

    Attributes:
        handler: The content handler instance
        in_tail: Specifies whether the text content has been written
        tail: The current element tail content
        attrs: The current element attributes
        ns_context: The namespace context queue
        pending_tag: The pending element namespace, name tuple
        pending_prefixes: The pending element namespace prefixes
    """

    __slots__ = (
        "config",
        "output",
        "ns_map",
        # Instance attributes
        "handler",
        "in_tail",
        "tail",
        "attrs",
        "ns_context",
        "pending_tag",
        "pending_prefixes",
    )

    def __init__(
        self,
        config: SerializerConfig,
        output: TextIO,
        ns_map: Dict,
    ):
        self.config = config
        self.output = output
        self.ns_map = ns_map

        self.in_tail = False
        self.tail: Optional[str] = None
        self.attrs: Dict = {}
        self.ns_context: List[Dict] = []
        self.pending_tag: Optional[Tuple] = None
        self.pending_prefixes: List[List] = []
        self.handler = self.build_handler()

    @abc.abstractmethod
    def build_handler(self) -> ContentHandler:
        """Build the content handler instance.

        Returns:
            A content handler instance.
        """

    def write(self, events: EventIterator):
        """Feed the sax content handler with events.

        The receiver will also add additional root attributes
        like xsi or no namespace location.

        Args:
            events: An iterator of sax events

        Raises:
            XmlWriterError: On unknown events.
        """
        self.start_document()

        if self.config.schema_location:
            self.add_attribute(
                QNames.XSI_SCHEMA_LOCATION,
                self.config.schema_location,
                root=True,
            )

        if self.config.no_namespace_schema_location:
            self.add_attribute(
                QNames.XSI_NO_NAMESPACE_SCHEMA_LOCATION,
                self.config.no_namespace_schema_location,
                root=True,
            )

        for name, *args in events:
            if name == XmlWriterEvent.START:
                self.start_tag(*args)
            elif name == XmlWriterEvent.END:
                self.end_tag(*args)
            elif name == XmlWriterEvent.ATTR:
                self.add_attribute(*args)
            elif name == XmlWriterEvent.DATA:
                self.set_data(*args)
            else:
                raise XmlWriterError(f"Unhandled event: `{name}`")

        self.handler.endDocument()

    def start_document(self):
        """Start document notification receiver.

        Write the xml version and encoding, if the
        configuration is enabled.
        """
        if self.config.xml_declaration:
            self.output.write(f'<?xml version="{self.config.xml_version}"')
            self.output.write(f' encoding="{self.config.encoding}"?>\n')

    def start_tag(self, qname: str):
        """Start tag notification receiver.

        The receiver will flush the start of any pending element, create
        new namespaces context and queue the current tag for generation.

        Args:
            qname: The qualified name of the starting element
        """
        self.flush_start(False)

        self.ns_context.append(self.ns_map.copy())
        self.ns_map = self.ns_context[-1]

        self.pending_tag = split_qname(qname)
        self.add_namespace(self.pending_tag[0])

    def add_attribute(self, qname: str, value: Any, root: bool = False):
        """Add attribute notification receiver.

        The receiver will convert the key to a namespace, name tuple and
        convert the value to string. Internally the converter will also
        generate any missing namespace prefixes.

        Args:
            qname: The qualified name of the attribute
            value: The value of the attribute
            root: Specifies if attribute is for the root element

        Raises:
            XmlWriterError: If it's not a root element attribute
                and not no element is pending to start.
        """
        if not self.pending_tag and not root:
            raise XmlWriterError("Empty pending tag.")

        if self.is_xsi_type(qname, value):
            value = QName(value)

        name_tuple = split_qname(qname)
        self.attrs[name_tuple] = self.encode_data(value)

    def add_namespace(self, uri: Optional[str]):
        """Add the given uri to the current namespace context.

         If the uri empty or a prefix already exists, skip silently.

        Args:
            uri: The namespace URI
        """
        if uri and not prefix_exists(uri, self.ns_map):
            generate_prefix(uri, self.ns_map)

    def set_data(self, data: Any):
        """Set data notification receiver.

        The receiver will convert the data to string, flush any previous
        pending start element and send it to the handler for generation.

        If the text content of the tag has already been generated then
        treat the current data as element tail content and queue it to
        be generated when the tag ends.

        Args:
            data: The element text or tail content
        """
        value = self.encode_data(data)
        self.flush_start(is_nil=value is None)

        if value:
            if not self.in_tail:
                self.handler.characters(value)
            else:
                self.tail = value

        self.in_tail = True

    def end_tag(self, qname: str):
        """End tag notification receiver.

        The receiver will flush if pending the start of the element, end
        the element, its tail content and its namespaces prefix mapping
        and current context.

        Args:
            qname: The qualified name of the element
        """
        self.flush_start(True)
        self.handler.endElementNS(split_qname(qname), "")

        if self.tail:
            self.handler.characters(self.tail)

        self.tail = None
        self.in_tail = False
        self.ns_context.pop()
        if self.ns_context:
            self.ns_map = self.ns_context[-1]

        for prefix in self.pending_prefixes.pop():
            self.handler.endPrefixMapping(prefix)

    def flush_start(self, is_nil: bool = True):
        """Flush start notification receiver.

        The receiver will pop the xsi:nil attribute if the element is
        not empty, prepare and send the namespace prefix-URI map and
        the element with its attributes to the content handler for
        generation.

        Args:
            is_nil: Specify if the element requires `xsi:nil="true"`
                when content is empty
        """
        if not self.pending_tag:
            return

        if not is_nil:
            self.attrs.pop(XSI_NIL, None)

        for name in self.attrs:
            self.add_namespace(name[0])

        self.reset_default_namespace()
        self.start_namespaces()

        self.handler.startElementNS(self.pending_tag, "", self.attrs)  # type: ignore
        self.attrs = {}
        self.in_tail = False
        self.pending_tag = None

    def start_namespaces(self):
        """Send the current namespace prefix-URI map to the content handler.

        Save the list of prefixes to be removed at the end of the
        current pending tag.
        """
        prefixes: List[str] = []
        self.pending_prefixes.append(prefixes)

        try:
            parent_ns_map = self.ns_context[-2]
        except IndexError:
            parent_ns_map = EMPTY_MAP

        for prefix, uri in self.ns_map.items():
            if parent_ns_map.get(prefix) != uri:
                prefixes.append(prefix)
                self.handler.startPrefixMapping(prefix, uri)

    def reset_default_namespace(self):
        """Reset the default namespace if the pending element is not qualified."""
        if self.pending_tag and not self.pending_tag[0] and None in self.ns_map:
            self.ns_map[None] = ""

    @classmethod
    def is_xsi_type(cls, qname: str, value: Any) -> bool:
        """Return whether the value is a xsi:type.

        Args:
            qname: The attribute qualified name
            value: The attribute value

        Returns:
            The bool result.
        """
        if isinstance(value, str) and value.startswith("{"):
            return qname == QNames.XSI_TYPE or DataType.from_qname(value) is not None

        return False

    def encode_data(self, data: Any) -> Optional[str]:
        """Encode data for xml rendering.

        Args:
            data: The content to encode/serialize

        Returns:
            The xml encoded data
        """
        if data is None or isinstance(data, str):
            return data

        if isinstance(data, list) and not data:
            return None

        return converter.serialize(data, ns_map=self.ns_map)

build_handler() abstractmethod

Build the content handler instance.

Returns:

Type Description
ContentHandler

A content handler instance.

Source code in xsdata/formats/dataclass/serializers/mixins.py
 96
 97
 98
 99
100
101
102
@abc.abstractmethod
def build_handler(self) -> ContentHandler:
    """Build the content handler instance.

    Returns:
        A content handler instance.
    """

write(events)

Feed the sax content handler with events.

The receiver will also add additional root attributes like xsi or no namespace location.

Parameters:

Name Type Description Default
events EventIterator

An iterator of sax events

required

Raises:

Type Description
XmlWriterError

On unknown events.

Source code in xsdata/formats/dataclass/serializers/mixins.py
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
def write(self, events: EventIterator):
    """Feed the sax content handler with events.

    The receiver will also add additional root attributes
    like xsi or no namespace location.

    Args:
        events: An iterator of sax events

    Raises:
        XmlWriterError: On unknown events.
    """
    self.start_document()

    if self.config.schema_location:
        self.add_attribute(
            QNames.XSI_SCHEMA_LOCATION,
            self.config.schema_location,
            root=True,
        )

    if self.config.no_namespace_schema_location:
        self.add_attribute(
            QNames.XSI_NO_NAMESPACE_SCHEMA_LOCATION,
            self.config.no_namespace_schema_location,
            root=True,
        )

    for name, *args in events:
        if name == XmlWriterEvent.START:
            self.start_tag(*args)
        elif name == XmlWriterEvent.END:
            self.end_tag(*args)
        elif name == XmlWriterEvent.ATTR:
            self.add_attribute(*args)
        elif name == XmlWriterEvent.DATA:
            self.set_data(*args)
        else:
            raise XmlWriterError(f"Unhandled event: `{name}`")

    self.handler.endDocument()

start_document()

Start document notification receiver.

Write the xml version and encoding, if the configuration is enabled.

Source code in xsdata/formats/dataclass/serializers/mixins.py
146
147
148
149
150
151
152
153
154
def start_document(self):
    """Start document notification receiver.

    Write the xml version and encoding, if the
    configuration is enabled.
    """
    if self.config.xml_declaration:
        self.output.write(f'<?xml version="{self.config.xml_version}"')
        self.output.write(f' encoding="{self.config.encoding}"?>\n')

start_tag(qname)

Start tag notification receiver.

The receiver will flush the start of any pending element, create new namespaces context and queue the current tag for generation.

Parameters:

Name Type Description Default
qname str

The qualified name of the starting element

required
Source code in xsdata/formats/dataclass/serializers/mixins.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def start_tag(self, qname: str):
    """Start tag notification receiver.

    The receiver will flush the start of any pending element, create
    new namespaces context and queue the current tag for generation.

    Args:
        qname: The qualified name of the starting element
    """
    self.flush_start(False)

    self.ns_context.append(self.ns_map.copy())
    self.ns_map = self.ns_context[-1]

    self.pending_tag = split_qname(qname)
    self.add_namespace(self.pending_tag[0])

add_attribute(qname, value, root=False)

Add attribute notification receiver.

The receiver will convert the key to a namespace, name tuple and convert the value to string. Internally the converter will also generate any missing namespace prefixes.

Parameters:

Name Type Description Default
qname str

The qualified name of the attribute

required
value Any

The value of the attribute

required
root bool

Specifies if attribute is for the root element

False

Raises:

Type Description
XmlWriterError

If it's not a root element attribute and not no element is pending to start.

Source code in xsdata/formats/dataclass/serializers/mixins.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def add_attribute(self, qname: str, value: Any, root: bool = False):
    """Add attribute notification receiver.

    The receiver will convert the key to a namespace, name tuple and
    convert the value to string. Internally the converter will also
    generate any missing namespace prefixes.

    Args:
        qname: The qualified name of the attribute
        value: The value of the attribute
        root: Specifies if attribute is for the root element

    Raises:
        XmlWriterError: If it's not a root element attribute
            and not no element is pending to start.
    """
    if not self.pending_tag and not root:
        raise XmlWriterError("Empty pending tag.")

    if self.is_xsi_type(qname, value):
        value = QName(value)

    name_tuple = split_qname(qname)
    self.attrs[name_tuple] = self.encode_data(value)

add_namespace(uri)

Add the given uri to the current namespace context.

If the uri empty or a prefix already exists, skip silently.

Parameters:

Name Type Description Default
uri Optional[str]

The namespace URI

required
Source code in xsdata/formats/dataclass/serializers/mixins.py
198
199
200
201
202
203
204
205
206
207
def add_namespace(self, uri: Optional[str]):
    """Add the given uri to the current namespace context.

     If the uri empty or a prefix already exists, skip silently.

    Args:
        uri: The namespace URI
    """
    if uri and not prefix_exists(uri, self.ns_map):
        generate_prefix(uri, self.ns_map)

set_data(data)

Set data notification receiver.

The receiver will convert the data to string, flush any previous pending start element and send it to the handler for generation.

If the text content of the tag has already been generated then treat the current data as element tail content and queue it to be generated when the tag ends.

Parameters:

Name Type Description Default
data Any

The element text or tail content

required
Source code in xsdata/formats/dataclass/serializers/mixins.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def set_data(self, data: Any):
    """Set data notification receiver.

    The receiver will convert the data to string, flush any previous
    pending start element and send it to the handler for generation.

    If the text content of the tag has already been generated then
    treat the current data as element tail content and queue it to
    be generated when the tag ends.

    Args:
        data: The element text or tail content
    """
    value = self.encode_data(data)
    self.flush_start(is_nil=value is None)

    if value:
        if not self.in_tail:
            self.handler.characters(value)
        else:
            self.tail = value

    self.in_tail = True

end_tag(qname)

End tag notification receiver.

The receiver will flush if pending the start of the element, end the element, its tail content and its namespaces prefix mapping and current context.

Parameters:

Name Type Description Default
qname str

The qualified name of the element

required
Source code in xsdata/formats/dataclass/serializers/mixins.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def end_tag(self, qname: str):
    """End tag notification receiver.

    The receiver will flush if pending the start of the element, end
    the element, its tail content and its namespaces prefix mapping
    and current context.

    Args:
        qname: The qualified name of the element
    """
    self.flush_start(True)
    self.handler.endElementNS(split_qname(qname), "")

    if self.tail:
        self.handler.characters(self.tail)

    self.tail = None
    self.in_tail = False
    self.ns_context.pop()
    if self.ns_context:
        self.ns_map = self.ns_context[-1]

    for prefix in self.pending_prefixes.pop():
        self.handler.endPrefixMapping(prefix)

flush_start(is_nil=True)

Flush start notification receiver.

The receiver will pop the xsi:nil attribute if the element is not empty, prepare and send the namespace prefix-URI map and the element with its attributes to the content handler for generation.

Parameters:

Name Type Description Default
is_nil bool

Specify if the element requires xsi:nil="true" when content is empty

True
Source code in xsdata/formats/dataclass/serializers/mixins.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def flush_start(self, is_nil: bool = True):
    """Flush start notification receiver.

    The receiver will pop the xsi:nil attribute if the element is
    not empty, prepare and send the namespace prefix-URI map and
    the element with its attributes to the content handler for
    generation.

    Args:
        is_nil: Specify if the element requires `xsi:nil="true"`
            when content is empty
    """
    if not self.pending_tag:
        return

    if not is_nil:
        self.attrs.pop(XSI_NIL, None)

    for name in self.attrs:
        self.add_namespace(name[0])

    self.reset_default_namespace()
    self.start_namespaces()

    self.handler.startElementNS(self.pending_tag, "", self.attrs)  # type: ignore
    self.attrs = {}
    self.in_tail = False
    self.pending_tag = None

start_namespaces()

Send the current namespace prefix-URI map to the content handler.

Save the list of prefixes to be removed at the end of the current pending tag.

Source code in xsdata/formats/dataclass/serializers/mixins.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def start_namespaces(self):
    """Send the current namespace prefix-URI map to the content handler.

    Save the list of prefixes to be removed at the end of the
    current pending tag.
    """
    prefixes: List[str] = []
    self.pending_prefixes.append(prefixes)

    try:
        parent_ns_map = self.ns_context[-2]
    except IndexError:
        parent_ns_map = EMPTY_MAP

    for prefix, uri in self.ns_map.items():
        if parent_ns_map.get(prefix) != uri:
            prefixes.append(prefix)
            self.handler.startPrefixMapping(prefix, uri)

reset_default_namespace()

Reset the default namespace if the pending element is not qualified.

Source code in xsdata/formats/dataclass/serializers/mixins.py
306
307
308
309
def reset_default_namespace(self):
    """Reset the default namespace if the pending element is not qualified."""
    if self.pending_tag and not self.pending_tag[0] and None in self.ns_map:
        self.ns_map[None] = ""

is_xsi_type(qname, value) classmethod

Return whether the value is a xsi:type.

Parameters:

Name Type Description Default
qname str

The attribute qualified name

required
value Any

The attribute value

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/formats/dataclass/serializers/mixins.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
@classmethod
def is_xsi_type(cls, qname: str, value: Any) -> bool:
    """Return whether the value is a xsi:type.

    Args:
        qname: The attribute qualified name
        value: The attribute value

    Returns:
        The bool result.
    """
    if isinstance(value, str) and value.startswith("{"):
        return qname == QNames.XSI_TYPE or DataType.from_qname(value) is not None

    return False

encode_data(data)

Encode data for xml rendering.

Parameters:

Name Type Description Default
data Any

The content to encode/serialize

required

Returns:

Type Description
Optional[str]

The xml encoded data

Source code in xsdata/formats/dataclass/serializers/mixins.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def encode_data(self, data: Any) -> Optional[str]:
    """Encode data for xml rendering.

    Args:
        data: The content to encode/serialize

    Returns:
        The xml encoded data
    """
    if data is None or isinstance(data, str):
        return data

    if isinstance(data, list) and not data:
        return None

    return converter.serialize(data, ns_map=self.ns_map)