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
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 | """Xml versions of fields."""
from collections.abc import Mapping
from typing import Any
from marshmallow.fields import Field, Nested
from marshmallow_union import Union
from typing_extensions import override
from comicbox.fields.collection_fields import (
IntegerListField,
LegacyNestedMDStringSetField,
ListField,
StringListField,
StringSetField,
)
from comicbox.fields.enum_fields import (
ComicInfoMangaField,
EnumField,
OriginalFormatField,
ReadingDirectionField,
YesNoField,
)
from comicbox.fields.fields import IssueField, StringField
from comicbox.fields.number_fields import BooleanField, DecimalField, IntegerField
from comicbox.fields.pdf import PdfDateTimeField
from comicbox.fields.pycountry import LanguageField
from comicbox.fields.time_fields import DateField, DateTimeField
from comicbox.fields.union import UNION_SCHEMA_IGNORE_ERRORS
from comicbox.schemas.base import BaseSubSchema
CDATA_KEY = "#text"
def get_cdata(value: Any) -> Any:
"""Return the cdata value if it exists or the whole value."""
if isinstance(value, Mapping):
return value.get(CDATA_KEY)
return value
class CDataFieldMixin:
"""Get value or cdata."""
def _deserialize(self, value, *args, **kwargs):
value = get_cdata(value)
return super()._deserialize(value, *args, **kwargs) # pyright: ignore[reportAttributeAccessIssue], # ty: ignore[unresolved-attribute]
# FIELDS
class XmlStringField(StringField, CDataFieldMixin):
"""Get value or cdata."""
class XmlIssueField(IssueField, CDataFieldMixin):
"""Get value or cdata."""
# DATETIME
class XmlDateField(DateField, CDataFieldMixin):
"""Get value or cdata."""
class XmlDateTimeField(DateTimeField, CDataFieldMixin):
"""Get value or cdata."""
class XmlPdfDateTimeField(PdfDateTimeField, CDataFieldMixin):
"""Get value or cdata."""
# ENUM
class XmlEnumField(EnumField, CDataFieldMixin):
"""Get value or cdata."""
class XmlReadingDirectionField(ReadingDirectionField, CDataFieldMixin):
"""Get value or cdata."""
class XmlOriginalFormatField(OriginalFormatField, CDataFieldMixin):
"""Get value or cdata."""
class XmlComicInfoMangaField(ComicInfoMangaField, CDataFieldMixin):
"""Get value or cdata."""
class XmlYesNoField(YesNoField, CDataFieldMixin):
"""Get value or cdata."""
# NUMBERS
class XmlBooleanField(BooleanField, CDataFieldMixin):
"""Get value or cdata."""
@override
def _serialize(
self,
*args: Any,
**kwargs: Any,
) -> str | None:
# xml booleans are lowercase
result = super()._serialize(*args, **kwargs)
return result if result is None else str(result).lower()
class XmlIntegerField(IntegerField, CDataFieldMixin):
"""Get value or cdata."""
class XmlDecimalField(DecimalField, CDataFieldMixin):
"""Get value or cdata."""
# PYCOUNTRY
class XmlLanguageField(LanguageField, CDataFieldMixin):
"""Get value or cdata."""
# COLLECTIONS
class XmlListFieldMixin:
"""Get value or cdata."""
@staticmethod
def get_tag_value(value: Any) -> Any:
"""Get data for the tag value."""
return get_cdata(value)
class XmlListField(XmlListFieldMixin, ListField):
"""XML List Field."""
class XmlStringListField(XmlListFieldMixin, StringListField):
"""XML String List Field."""
class XmlStringSetField(XmlListFieldMixin, StringSetField):
"""XML String Set Field."""
FIELD = XmlStringField
class XmlLegacyNestedMDStringSetField(XmlListFieldMixin, LegacyNestedMDStringSetField):
"""XML Legacy Nested String Set Field."""
FIELD = XmlStringField
class XmlIntegerListField(XmlListFieldMixin, IntegerListField):
"""XML Integer List Field."""
FIELD = XmlIntegerField
def create_sub_tag_field(
sub_tag: str,
field: Field,
) -> Nested:
"""Create a nested single schema, common to xml schemas."""
sub_tag_schema_name = sub_tag + "Schema"
sub_tag_schema_class = type(sub_tag_schema_name, (BaseSubSchema,), {sub_tag: field})
return Nested(sub_tag_schema_class)
def xml_polyfield(schema_class: type[BaseSubSchema], field: Field) -> Union:
"""Get a Union of nested schemas and fields."""
return Union(
[
# First field is the serialize type
Nested(schema_class(ignore_errors=UNION_SCHEMA_IGNORE_ERRORS)),
field,
]
)
def xml_list_polyfield(
schema_class: type[BaseSubSchema],
field: Field,
sort_keys: tuple[str, ...] = ("#text",),
**kwargs: Any,
) -> ListField:
"""Get a List of unions."""
union_field = xml_polyfield(schema_class, field)
return ListField(union_field, sort_keys=sort_keys, **kwargs)
|