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

71 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +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( 

20 'geometry/gml:Point', 

21 {'gml:id': 'xy'}, 

22 ['gml:coordinates', '12.345,56.789'], 

23 srsName=3857 

24 ) 

25 

26creates the following element: :: 

27 

28 <geometry> 

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

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

31 </gml:Point> 

32 </geometry> 

33 

34""" 

35 

36import re 

37 

38import gws 

39 

40from . import element, namespace, error 

41 

42 

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

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

45 

46 first = last = None 

47 

48 for n in _split_name(name): 

49 el = element.XmlElementImpl(n.strip()) 

50 if not first: 

51 first = last = el 

52 else: 

53 last.append(el) 

54 last = el 

55 

56 if not first: 

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

58 

59 for arg in args: 

60 _add(last, arg) 

61 

62 if kwargs: 

63 _add(last, kwargs) 

64 

65 return first 

66 

67 

68## 

69 

70 

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

72 if arg is None: 

73 return 

74 

75 if isinstance(arg, element.XmlElementImpl): 

76 el.append(arg) 

77 return 

78 

79 if isinstance(arg, str): 

80 if arg: 

81 _add_text(el, arg) 

82 return 

83 

84 if isinstance(arg, (int, float, bool)): 

85 _add_text(el, str(arg).lower()) 

86 return 

87 

88 if isinstance(arg, dict): 

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

90 if v is not None: 

91 el.set(k, v) 

92 return 

93 

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

95 _add_list(el, arg) 

96 return 

97 

98 try: 

99 ls = list(arg) 

100 except Exception as exc: 

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

102 

103 _add_list(el, ls) 

104 

105 

106def _add_text(el, s): 

107 if not s: 

108 return 

109 if len(el) == 0: 

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

111 else: 

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

113 

114 

115def _add_list(el, ls): 

116 if not ls: 

117 return 

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

119 _add(el, tag(*ls)) 

120 return 

121 for arg in ls: 

122 _add(el, arg) 

123 

124 

125def _split_name(name): 

126 if '{' not in name: 

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

128 

129 parts = [] 

130 ns = '' 

131 

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

133 if n: 

134 ns = n 

135 else: 

136 s = s.strip() 

137 if s: 

138 parts.append(ns + s) 

139 ns = '' 

140 

141 return parts