comicbox.formats.base.schemas.xml_schemas

[docs] module comicbox.formats.base.schemas.xml_schemas

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 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
"""XML Metadata parser superclass."""

from abc import ABC
from types import MappingProxyType
from typing import Any

import xmltodict
from marshmallow.fields import Constant
from typing_extensions import override

from comicbox.formats.base.schemas.base import (
    BaseRenderModule,
    BaseSchema,
    BaseSubSchema,
)

XML_UNPARSE_ARGS = MappingProxyType(
    # used by tests
    {
        # Capitalize UTF-8 to be standard.
        "encoding": "UTF-8",
        "pretty": True,
        "short_empty_elements": True,
    }
)

# xmltodict-style key for the XML `Image` attribute (`@` is xmltodict's
# attribute prefix). Used by ComicInfo's <Page Image="…"> elements and
# referenced by the YAML render module for flow-style rendering.
IMAGE_ATTRIBUTE = "@Image"


class XmlRenderModule(BaseRenderModule):
    """Marshmallow Render Module imitates json module."""

    @override
    @classmethod
    def dumps(cls, obj: dict, *args: Any, **kwargs: Any) -> str:
        """Dump dict to XML string."""
        return xmltodict.unparse(  # ty: ignore[no-matching-overload]
            obj,
            *args,
            **XML_UNPARSE_ARGS,
            **kwargs,
        )

    @override
    @classmethod
    def loads(
        cls,
        s: str | bytes | bytearray,
        *args: Any,
        **kwargs: Any,
    ) -> Any:
        """Load XML string into a dict."""
        if cleaned_s := cls.clean_string(s):
            return xmltodict.parse(cleaned_s, *args, **kwargs)
        return None


class XmlSubSchema(BaseSubSchema, ABC):
    """XML Rendered Sub Schema."""

    class Meta(BaseSubSchema.Meta):
        """Schema Options."""

        render_module = XmlRenderModule


def create_xml_headers(
    ns: str, ns_uri: str, xsd_uri: str
) -> MappingProxyType[str, Constant]:
    """Create Namespace and Schema Location XML Attributes."""
    return MappingProxyType(
        {
            f"@xmlns:{ns}": Constant(ns_uri),
            "@xsi:schemaLocation": Constant(f"{ns_uri} {xsd_uri}"),
        }
    )


class XmlSubHeadSchema(XmlSubSchema, ABC):
    """XML Head Sub Schema customizations."""

    class Meta(XmlSubSchema.Meta):
        """Schema Options."""

        NS = ""
        NS_URI = ""
        XSD_URI = ""

        include = MappingProxyType(
            {
                "@xmlns:xsd": Constant("http://www.w3.org/2001/XMLSchema"),
                "@xmlns:xsi": Constant("http://www.w3.org/2001/XMLSchema-instance"),
            }
        )


class XmlSchema(BaseSchema, ABC):
    """Xml Rendered Schema."""

    class Meta(BaseSchema.Meta):
        """Schema Options."""

        render_module = XmlRenderModule