Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/unknown.py: 65%

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

72 statements  

1"""UNKNOWN values from :rfc:`7265`.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Any, ClassVar 

6from xml.etree.ElementTree import Element, SubElement 

7 

8from icalendar.error import JCalParsingError 

9from icalendar.parser import Parameters 

10from icalendar.parser.xcal.string import _to_valid_xml_string 

11from icalendar.parser.xcal.wrapper import from_xcal_wrapper 

12from icalendar.parser_tools import DEFAULT_ENCODING, ICAL_TYPE, to_unicode 

13 

14if TYPE_CHECKING: 

15 from icalendar.compatibility import Self 

16 from icalendar.parser.content_line import Contentline 

17 from icalendar.parser.xcal.base import XCalParser 

18 

19 

20class vUnknown(str): 

21 """A property value of the :rfc:`7265#section-5` reserved UNKNOWN value data type. 

22 

23 .. versionchanged:: 7.2.0 

24 

25 Previously ``vUnknown`` inherited from ``vText``, which unescapes values. 

26 Now ``vUnknown`` doesn't unescape its values, which is the correct behavior. 

27 

28 Unlike :class:`~icalendar.prop.text.vText`, the value is preserved verbatim 

29 when imported from and exported to iCalendar data, without :rfc:`5545` escaping 

30 or unescaping. When the value type of an unrecognized property is not known, 

31 then no escaping rules can be applied, and the value must be preserved as is 

32 round-trip. 

33 

34 For :rfc:`6321`, vUnknown plays an important role in preserving the VALUE parameter. 

35 

36 .. code-block:: pycon 

37 

38 >>> from icalendar import Calendar, vUnknown 

39 >>> cal = Calendar() 

40 >>> cal.add("X-PROP", vUnknown("lalala", params={"VALUE": "X-VALUE"})) 

41 >>> print(cal.to_xcal(indent=2).decode("utf-8")) 

42 <?xml version="1.0" encoding="UTF-8"?> 

43 <icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> 

44 <vcalendar> 

45 <properties> 

46 <x-prop> 

47 <x-value>lalala</x-value> 

48 </x-prop> 

49 </properties> 

50 </vcalendar> 

51 </icalendar> 

52 

53 See also: 

54 

55 :rfc:`7265#section-5.1` 

56 """ 

57 

58 default_value: ClassVar[str] = "UNKNOWN" 

59 params: Parameters 

60 __slots__ = ("encoding", "params") 

61 

62 def __new__( 

63 cls, 

64 value: ICAL_TYPE, 

65 encoding: str = DEFAULT_ENCODING, 

66 /, 

67 params: dict[str, Any] | None = None, 

68 ) -> Self: 

69 value = to_unicode(value, encoding=encoding) 

70 self = super().__new__(cls, value) 

71 self.encoding = encoding 

72 self.params = Parameters(params) 

73 return self 

74 

75 def __repr__(self) -> str: 

76 return f"{self.__class__.__name__}({self.to_ical()!r})" 

77 

78 def to_ical(self) -> bytes: 

79 r"""Return the value verbatim, without :rfc:`5545` escaping. 

80 

81 This method's implementation is different from that in 

82 :class:`~icalendar.prop.text.vText`, whose 

83 :meth:`~icalendar.prop.text.vText.to_ical` method escapes ``;``, ``,``, 

84 ``\``, and newlines. 

85 

86 Example: 

87 

88 The semicolon is kept verbatim for UNKNOWN, unlike a TEXT value 

89 which would escape it as ``\\;``. 

90 

91 .. code-block:: pycon 

92 

93 >>> from icalendar.prop import vText, vUnknown 

94 >>> vUnknown("a;b").to_ical() 

95 b'a;b' 

96 >>> vText("a;b").to_ical() 

97 b'a\\;b' 

98 

99 See also: 

100 

101 :rfc:`7265#section-5.2` 

102 

103 """ 

104 return self.encode(self.encoding) 

105 

106 @classmethod 

107 def from_ical(cls, ical: ICAL_TYPE) -> Self: 

108 """Take the value verbatim, without unescaping.""" 

109 return cls(ical) 

110 

111 @classmethod 

112 def get_value_from_content_line(cls, line: Contentline) -> str: 

113 """Return this type's value from ``line``, taken verbatim. 

114 

115 Value types that must not have their value unescaped provide this 

116 method, and the parser uses it to obtain the value instead of 

117 :meth:`~icalendar.parser.content_line.Contentline.parts`. For 

118 :rfc:`7265` ``UNKNOWN`` values, the escaping rules of the real value 

119 type are not known, so no unescaping can be applied. 

120 """ 

121 return line.raw_parts()[2] 

122 

123 @property 

124 def ical_value(self) -> str: 

125 """The string value of the property.""" 

126 return str(self) 

127 

128 from icalendar.param import ALTREP, GAP, LANGUAGE, RELTYPE, VALUE 

129 

130 def to_jcal(self, name: str) -> list: 

131 """The jCal representation of this property, according to :rfc:`7265#section-5.1`. 

132 

133 If the property doesn't include a VALUE property parameter and its value 

134 type is not known, then its value type is set to ``"unknown"``. Else the 

135 property's value type is converted to lowercase. 

136 

137 The property's value is the unprocessed value text, aside from standard 

138 JSON string escaping. 

139 """ 

140 return [name, self.params.to_jcal(), self.VALUE.lower(), str(self)] 

141 

142 @classmethod 

143 def examples(cls) -> list[Self]: 

144 """Examples of vUnknown.""" 

145 return [cls("Some property text.")] 

146 

147 @classmethod 

148 def from_jcal(cls, jcal_property: list) -> Self: 

149 """Parse jCal from :rfc:`7265`, taking the value verbatim. 

150 

151 Parameters: 

152 jcal_property: The jCal property to parse. 

153 

154 Raises: 

155 ~error.JCalParsingError: If the provided jCal is invalid. 

156 """ 

157 JCalParsingError.validate_property(jcal_property, cls) 

158 string = jcal_property[3] 

159 JCalParsingError.validate_value_type(string, str, cls, 3) 

160 return cls( 

161 string, 

162 params=Parameters.from_jcal_property(jcal_property), 

163 ) 

164 

165 @classmethod 

166 def parse_jcal_value(cls, jcal_value: Any) -> Self: 

167 """Parse a jCal value into a vUnknown.""" 

168 JCalParsingError.validate_value_type(jcal_value, (str, int, float), cls) 

169 return cls(str(jcal_value)) 

170 

171 def to_xcal(self, element: Element) -> None: 

172 """Add the xCal representation of this property according to :rfc:`6321`.""" 

173 self.params.to_xcal(element) 

174 value_parameter = self.params.value 

175 if value_parameter is None: 

176 value_parameter = self.default_value 

177 element = SubElement(element, value_parameter.lower()) 

178 element.text = _to_valid_xml_string(self) 

179 

180 @classmethod 

181 @from_xcal_wrapper 

182 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: 

183 """Parse xCal from :rfc:`6321`. 

184 

185 Parameters: 

186 xml: The XML to parse or a parser. 

187 

188 Raises: 

189 ~error.XCalParsingError: If the provided xCal is invalid. 

190 """ 

191 element = parser.parse_tag(cls.default_value) 

192 params.value = cls.default_value # UNKNOWN is never the default type 

193 return cls(element.get_xsd_string(), params=params) 

194 

195 @classmethod 

196 @from_xcal_wrapper 

197 def from_first_xcal_element(cls, parser: XCalParser, params: Parameters) -> Self: 

198 """Parse xCal from :rfc:`6321`. 

199 

200 This class is used as a fallback if no other type can parse this. 

201 

202 Parameters: 

203 xml: The XML to parse or a parser. 

204 

205 Raises: 

206 ~error.XCalParsingError: If the provided xCal is invalid. 

207 """ 

208 element = parser.parse_tag() 

209 params.value = element.tag.upper() # set the VALUE parameter 

210 return cls(element.get_xsd_string(), params=params) 

211 

212 

213__all__ = ["vUnknown"]