Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/parser/xcal/adapter.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

73 statements  

1from __future__ import annotations 

2 

3import re 

4from collections import defaultdict 

5from xml.etree.ElementTree import Element 

6 

7from icalendar.error import XCalParsingError 

8from icalendar.parser.xcal.string import string_to_xsd_float 

9 

10REGEX_WHITESPACE = re.compile(r"\s+", re.MULTILINE) 

11 

12 

13class ElementAdapter: 

14 """An adapter class with convenience methods for XML elements. 

15 

16 This is a read-only access adapter. 

17 All operations on ElementAdapter are reproducible and 

18 do not change the underlying element. 

19 """ 

20 

21 @classmethod 

22 def with_element(cls, element: Element | ElementAdapter) -> ElementAdapter: 

23 """Create a new element adapter or return the present one.""" 

24 if isinstance(element, ElementAdapter): 

25 return element 

26 return cls(element) 

27 

28 def __init__(self, element: Element) -> None: 

29 self._element = element 

30 self._tag = self._element.tag.split("}")[-1].lower() 

31 

32 @property 

33 def element(self) -> Element: 

34 """The underlying element.""" 

35 return self._element 

36 

37 @property 

38 def tag(self) -> str: 

39 """A sanitized element tag.""" 

40 return self._tag 

41 

42 @property 

43 def children(self) -> list[ChildElementAdapter]: 

44 """A list of child elements.""" 

45 tags = defaultdict(int) 

46 children = [] 

47 for child_element in self._element: 

48 tag = child_element.tag 

49 tags[tag] += 1 

50 children.append(ChildElementAdapter(child_element, self, tags[tag])) 

51 return children 

52 

53 def get_child_with_tag(self, tag: str) -> ChildElementAdapter | None: 

54 """Get the first child element with the given tag.""" 

55 tag = tag.lower() 

56 for child in self.children: 

57 if child.tag == tag: 

58 return child 

59 return None 

60 

61 def parse_tag(self, tag: str): 

62 """Get the first child element with the given tag. 

63 

64 Returns: 

65 The first child element with the given tag. 

66 

67 Raises: 

68 XCalParsingError: If the tag is not found. 

69 """ 

70 child = self.get_child_with_tag(tag) 

71 if child is None: 

72 raise XCalParsingError(f"Tag {tag} not found", None, self) 

73 return child 

74 

75 def get_xsd_string(self) -> str: 

76 """Return the element's text as ``xsd:string``. 

77 

78 This is the only datatype that leaves all the whitespace. 

79 

80 .. seealso:: 

81 `xmlschemata.org <https://books.xmlschemata.org/relaxng/ch19-77303.html>`__ 

82 """ 

83 return self._element.text or "" 

84 

85 def get_xsd_token(self) -> str: 

86 """Return the element's text as ``xsd:token``. 

87 

88 ``xsd:token`` is the most appropriate datatype to use for strings 

89 that don't care about whitespace. 

90 

91 .. seealso:: 

92 `xmlschemata.org <https://books.xmlschemata.org/relaxng/ch19-77319.html>`__ 

93 """ 

94 return REGEX_WHITESPACE.sub(" ", self.get_xsd_string()).strip() 

95 

96 def get_text_without_whitespace(self) -> str: 

97 """Return the element's text with all whitespace removed.""" 

98 return REGEX_WHITESPACE.sub("", self.get_xsd_string()) 

99 

100 def get_xpath(self) -> str: 

101 """Return the path in the XML file where this element occurs.""" 

102 return f"/{self.tag}" 

103 

104 def get_xsd_float(self) -> float: 

105 """Return the element's text parsed as ``xsd:float``. 

106 

107 Returns: 

108 The parsed float value. 

109 

110 Raises: 

111 XCalParsingError: If the value is not a float. 

112 """ 

113 try: 

114 return string_to_xsd_float(self.get_xsd_token()) 

115 except (TypeError, ValueError) as e: 

116 raise XCalParsingError( 

117 "Expected xsd:float", 

118 self.get_xsd_token(), 

119 self, 

120 ) from e 

121 

122 def __repr__(self) -> str: 

123 """Return the text representation of this element.""" 

124 return f"Element@{self.get_xpath()}" 

125 

126 def make_parent(self, tag: str) -> ElementAdapter: 

127 """Return an element adapter for a parent.""" 

128 parent = Element(tag) 

129 parent.append(self._element) 

130 return ElementAdapter(parent) 

131 

132 def get_inner_text(self) -> str: 

133 """Return the inner text. 

134 

135 This is useful when there is a broken property. 

136 """ 

137 return self.get_xsd_string() 

138 

139 

140class ChildElementAdapter(ElementAdapter): 

141 """An adapter class with convenience methods for child elements. 

142 

143 Parameters: 

144 child: The child element. 

145 index: The index for building the XPath in the parent. 

146 parent: The parent element adapter. 

147 """ 

148 

149 def __init__(self, child: Element, parent: ElementAdapter, index: int = 1) -> None: 

150 super().__init__(child) 

151 self._parent = parent 

152 self._index = index 

153 

154 @property 

155 def parent(self) -> ElementAdapter: 

156 """The parent element.""" 

157 return self._parent 

158 

159 def get_xpath(self) -> str: 

160 """Return the path in the XML file where this element occurs.""" 

161 return f"{self.parent.get_xpath()}/{self.tag}[{self._index}]"