Coverage for gws-app/gws/lib/xmlx/tag.py: 96%

67 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 22:59 +0200

1"""XML builder. 

2 

3This module provides a single function ``tag``, which creates an Xml Element from a list of arguments. 

4 

5The first argument to this function is interpreted as a tag name 

6or a slash separated list of tag names, in which case nested elements are created. 

7 

8The remaining ``*args`` are interpreted as follows: 

9 

10- a simple string or number value - appended to the text content of the Element 

11- an `XmlElement` - appended as a child to the Element 

12- a dict - attributes of the Element are updated from this dict 

13- a list, tuple or a generator - used as arguments to ``tag`` to create a child tag 

14 

15If keyword arguments are given, they are added to the Element's attributes. 

16 

17**Example:** :: 

18 

19 tag('geometry/gml:Point', {'gml:id': 'xy'}, ['gml:coordinates', '12.345,56.789'], srsName=3857) 

20 

21creates the following element: :: 

22 

23 <geometry> 

24 <gml:Point gml:id="xy" srsName="3857"> 

25 <gml:coordinates>12.345,56.789</gml:coordinates> 

26 </gml:Point> 

27 </geometry> 

28 

29""" 

30 

31import re 

32 

33import gws 

34 

35from . import element, error, util 

36 

37 

38def tag(name: str, *args, **kwargs) -> gws.XmlElement: 

39 """Build an XML element from arguments.""" 

40 

41 stack = [] 

42 

43 for n in _split_name(name): 

44 el = element.XmlElement(n) 

45 if stack: 

46 stack[-1].append(el) 

47 stack.append(el) 

48 

49 if not stack: 

50 raise error.BuildError(f'invalid tag name: {name!r}') 

51 

52 for arg in args: 

53 _add(stack[-1], arg) 

54 

55 if kwargs: 

56 _add(stack[-1], kwargs) 

57 

58 return stack[0] 

59 

60 

61## 

62 

63 

64def _add(el: gws.XmlElement, arg): 

65 if arg is None: 

66 return 

67 

68 if hasattr(arg, 'tag'): 

69 # mimic ElementTree.iselement 

70 el.append(arg) 

71 return 

72 

73 s, ok = util.atom_to_string(arg) 

74 if ok: 

75 _add_text(el, s) 

76 return 

77 

78 if isinstance(arg, dict): 

79 for k, v in arg.items(): 

80 if v is not None: 

81 el.set(k, v) 

82 return 

83 

84 if isinstance(arg, (list, tuple)): 

85 _add_list(el, arg) 

86 return 

87 

88 try: 

89 ls = list(arg) 

90 except Exception as exc: 

91 raise error.BuildError(f'invalid argument: in {el.tag!r}, {arg=}') from exc 

92 

93 _add_list(el, ls) 

94 

95 

96def _add_text(el, s): 

97 if not s: 

98 return 

99 if len(el) == 0: 

100 el.text = (el.text or '') + s 

101 else: 

102 el[-1].tail = (el[-1].tail or '') + s 

103 

104 

105def _add_list(el, ls): 

106 if not ls: 

107 return 

108 if isinstance(ls[0], str): 

109 _add(el, tag(*ls)) 

110 return 

111 for arg in ls: 

112 _add(el, arg) 

113 

114 

115def _split_name(name): 

116 if '{' not in name: 

117 return [s.strip() for s in name.split('/')] 

118 

119 parts = [] 

120 ns = '' 

121 

122 for n, s in re.findall(r'({.+?})|([^/{}]+)', name): 

123 if n: 

124 ns = n.strip() 

125 else: 

126 s = s.strip() 

127 if s: 

128 parts.append(ns + s) 

129 ns = '' 

130 

131 return parts