Source code for odin.codecs.xml_codec
"""
XML Codec (output only)
Output a resource structure as XML
XML has a unique attribute in the form of the text. This is plain text that can
be placed within a pair of tags.
To support this the XML codec includes a TextField, any TextField found on a
resource will be exported (if multiple are defined they will all be exported
into the one text block).
The TextField is for all intents and purposes just a StringField, other codecs
will export any value as a String.
"""
import datetime
from io import StringIO
from typing import TextIO
from xml.sax import saxutils
from odin import fields, serializers
from odin.fields import StringField, composite
from odin.utils import attribute_field_iter_items, element_field_iter_items, getmeta
XML_TYPES = {
datetime.date: serializers.date_iso_format,
datetime.time: serializers.time_iso_format,
datetime.datetime: serializers.datetime_iso_format,
}
CONTENT_TYPE = "application/xml"
class TextField(StringField):
"""
Special String field for representing XML text blocks.
"""
def _serialize_to_string(value):
if value.__class__ in XML_TYPES:
return XML_TYPES[value.__class__](value)
else:
return str(value)
[docs]
def dump(
fp: TextIO,
resource, # type: Resource
line_ending: str = "",
):
"""
Dump a resource to a file like object.
:param fp: File pointer or file like object.
:param resource: Resource to dump
:param line_ending: End of line character to apply
"""
meta = getmeta(resource)
# Write container and any attributes
attributes = "".join(
f" {f.name}={saxutils.quoteattr(_serialize_to_string(v))}" # Encode attributes
for f, v in attribute_field_iter_items(resource)
)
fp.write(f"<{meta.name}{attributes}>{line_ending}")
# Write any element fields
for field, value in element_field_iter_items(resource):
if isinstance(field, composite.ListOf):
if field.use_container:
fp.write(f"<{field.name}>{line_ending}")
for v in value:
dump(fp, v, line_ending)
if field.use_container:
fp.write(f"</{field.name}>{line_ending}")
elif isinstance(field, composite.DictAs):
if value is not None:
dump(fp, value, line_ending)
elif isinstance(field, fields.ArrayField):
for v in value:
fp.write(
f"<{field.name}>{_serialize_to_string(v)}</{field.name}>{line_ending}"
)
elif isinstance(field, TextField):
if value is not None:
fp.write(f"{saxutils.escape(_serialize_to_string(value))}{line_ending}")
else:
fp.write(
f"<{field.name}>{saxutils.escape(_serialize_to_string(value))}</{field.name}>{line_ending}"
)
fp.write(f"</{meta.name}>{line_ending}")
[docs]
def dumps(resource, **kwargs):
"""
Dump a resource to a string.
:param resource: Resource to dump
"""
f = StringIO()
dump(f, resource, **kwargs)
return f.getvalue()