Coverage for gws-app/gws/lib/xmlx/tag.py: 98%
61 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""XML builder."""
3import re
5import gws
7from . import element, error, namespace, util
10def tag(name: str, *args, **kwargs) -> gws.XmlElement:
11 """Build an XML element from arguments.
13 Tag and attribute names are local names, Clark names (``{uri}name``) or ``ID:name``, where ``ID`` is the name
14 of a well-known namespace (``GML``, ``OWS_11``), which is resolved to a Clark name.
16 Example::
18 tag('geometry/GML:Point', {'GML:id': 'xy'}, tag('GML:coordinates', '12.345,56.789'), srsName=3857)
20 Args:
21 name: Tag name, or a slash-separated list of tag names, in which case nested elements are created.
22 Slashes inside the ``{uri}`` part of a Clark name do not separate.
23 *args: Content of the innermost element. A string, number, bool, date or datetime is appended
24 to the text, an ``XmlElement`` is appended as a child, a dict or a ``gws.Data`` object updates
25 the attributes (``None`` values are skipped), ``None`` is ignored, and any other iterable
26 is processed item by item with the same rules.
27 **kwargs: Additional attributes of the innermost element.
29 Returns:
30 The outermost element.
32 Raises:
33 BuildError: If a tag name is empty, a namespace ``ID`` is unknown, or an argument cannot be used.
34 """
36 elements = []
38 for part in _split_path(name):
39 part = part.strip()
40 if not part:
41 raise error.BuildError(f'invalid tag name: {name!r}')
42 el = element.XmlElement(_resolve_name(part))
43 if elements:
44 elements[-1].append(el)
45 elements.append(el)
47 if not elements:
48 raise error.BuildError(f'invalid tag name: {name!r}')
50 for arg in args:
51 _add(elements[-1], arg)
53 if kwargs:
54 _add(elements[-1], kwargs)
56 return elements[0]
59##
62def _resolve_name(name: str) -> str:
63 # local and Clark names are taken as they are, ``ID:name`` is resolved via the well-known table
65 uri, prefix, pname = namespace.parse_name(name)
66 if uri:
67 return name
68 if prefix:
69 ns = namespace.find_well_known(prefix)
70 if not ns:
71 raise error.BuildError(f'unknown namespace {prefix!r} in {name!r}')
72 return namespace.full_name(pname, ns)
73 return name
76def _split_path(name: str) -> list[str]:
77 # split on '/', but not inside the {uri} part of a Clark name
79 return re.split(r'/(?![^{]*})', name)
82def _add(el: gws.XmlElement, arg):
83 """Add an argument of ``tag()`` to an element."""
85 if arg is None:
86 return
88 if isinstance(arg, gws.XmlElement):
89 el.append(arg)
90 return
92 s, ok = util.atom_to_string(arg)
93 if ok:
94 _add_text(el, s)
95 return
97 if gws.is_data_object(arg):
98 arg = gws.u.to_dict(arg)
100 if isinstance(arg, dict):
101 for k, v in arg.items():
102 if v is not None:
103 el.set(_resolve_name(k), v)
104 return
106 try:
107 arg = list(arg)
108 except TypeError:
109 raise error.BuildError(f'invalid argument: in {el.tag!r}, {arg=}')
111 for a in arg:
112 _add(el, a)
115def _add_text(el, s):
116 """Append text to the element text or to the tail of its last child."""
118 if not s:
119 return
120 if len(el) == 0:
121 el.text = (el.text or '') + s
122 else:
123 el[-1].tail = (el[-1].tail or '') + s