Coverage for gws-app/gws/lib/xmlx/serializer.py: 92%
119 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1from typing import Optional
2import gws
3from . import namespace, error
6class _SerializerState:
7 extra_namespaces: list[gws.XmlNamespace]
9 compact_whitespace = False
10 remove_namespaces = False
11 fold_tags = False
12 with_namespace_declarations = False
13 with_schema_locations = False
14 with_xml_declaration = False
16 buf: list[str]
17 defaultNamespace: Optional[gws.XmlNamespace]
20def _state(kwargs):
21 ser = _SerializerState()
23 ser.extra_namespaces = kwargs.get('extra_namespaces', [])
25 ser.compact_whitespace = kwargs.get('compact_whitespace', False)
26 ser.fold_tags = kwargs.get('fold_tags', False)
27 ser.remove_namespaces = kwargs.get('remove_namespaces', False)
28 ser.with_namespace_declarations = kwargs.get('with_namespace_declarations', False)
29 ser.with_schema_locations = kwargs.get('with_schema_locations', False)
30 ser.with_xml_declaration = kwargs.get('with_xml_declaration', False)
32 ser.buf = []
33 ser.defaultNamespace = []
35 return ser
38##
41def to_string(el: gws.XmlElement, **kwargs):
42 ser = _state(kwargs)
44 _set_default_namespace(ser, el)
46 extra_atts = None
48 if ser.with_namespace_declarations:
49 extra_atts = namespace.declarations(
50 for_element=el,
51 default_ns=ser.defaultNamespace,
52 with_schema_locations=ser.with_schema_locations,
53 extra_ns=ser.extra_namespaces,
54 )
56 if ser.with_xml_declaration:
57 ser.buf.append(_XML_DECL)
59 _to_string(ser, el, extra_atts)
61 return ''.join(ser.buf)
64def _to_string(ser: _SerializerState, el: gws.XmlElement, extra_atts=None):
65 atts = {}
67 for key, val in el.attrib.items():
68 if val is None:
69 continue
70 atts[_make_name(ser, key)] = _value_to_string(ser, val)
72 if extra_atts:
73 atts.update(extra_atts)
75 open_pos = len(ser.buf)
76 ser.buf.append('')
78 s = _text_to_string(ser, el.text)
79 if s:
80 ser.buf.append(s)
82 for c in el:
83 _to_string(ser, c)
85 open_tag = _make_name(ser, el.tag)
86 close_tag = open_tag
87 if atts:
88 open_tag += ' ' + ' '.join(f'{k}="{v}"' for k, v in atts.items())
90 if len(ser.buf) > open_pos + 1:
91 ser.buf[open_pos] = f'<{open_tag}>'
92 ser.buf.append(f'</{close_tag}>')
93 else:
94 ser.buf[open_pos] += f'<{open_tag}/>'
96 s = _text_to_string(ser, el.tail)
97 if s:
98 ser.buf.append(s)
101##
103def to_list(el: gws.XmlElement, **kwargs):
104 ser = _state(kwargs)
106 _set_default_namespace(ser, el)
108 return _to_list(ser, el)
111def _to_list(ser, el):
112 name = _make_name(ser, el.tag)
113 attr = {_make_name(ser, k): v for k, v in el.attrib.items()}
114 text = (el.text or '').strip()
115 tail = (el.tail or '').strip()
117 sub = [_to_list(ser, c) for c in el]
119 if ser.fold_tags and len(sub) == 1 and (not attr and not text and not tail):
120 # single wrapper tag, create 'tag/subtag
121 inner = sub[0]
122 inner[0] = name + '/' + inner[0]
123 return inner
125 res = [name, attr, text, sub, tail]
126 return [x for x in res if x]
129##
131def _set_default_namespace(ser, el):
132 xmlns = el.get('xmlns')
133 if xmlns:
134 ns = namespace.get(xmlns)
135 if not ns:
136 raise error.NamespaceError(f'unknown namespace {xmlns!r}')
137 ser.defaultNamespace = ns
140def _text_to_string(ser, s):
141 if s is None:
142 return ''
143 if isinstance(s, (int, float, bool)):
144 return str(s).lower()
145 if not isinstance(s, str):
146 s = str(s)
147 if ser.compact_whitespace:
148 s = ' '.join(s.strip().split())
149 s = s.replace("&", "&")
150 s = s.replace(">", ">")
151 s = s.replace("<", "<")
152 return s
155def _value_to_string(ser, s):
156 if s is None:
157 return ''
158 if isinstance(s, (int, float, bool)):
159 return str(s).lower()
160 if not isinstance(s, str):
161 s = str(s)
162 s = s.replace("&", "&")
163 s = s.replace('"', """)
164 s = s.replace(">", ">")
165 s = s.replace("<", "<")
166 return s
169def _make_name(ser, name):
170 if ser.remove_namespaces:
171 return namespace.unqualify_name(name)
172 ns, pname = namespace.parse_name(name)
173 if not ns:
174 return name
175 if ns and ser.defaultNamespace and ns.uri == ser.defaultNamespace.uri:
176 return pname
177 return ns.xmlns + ':' + pname
180_XML_DECL = '<?xml version="1.0" encoding="UTF-8"?>'