Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/webcolors/_html5.py: 11%

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

53 statements  

1""" 

2HTML5 color algorithms. 

3 

4Note that these functions are written in a way that may seem strange to developers 

5familiar with Python, because they do not use the most efficient or idiomatic way of 

6accomplishing their tasks. This is because, for compliance, these functions are written 

7as literal translations into Python of the algorithms in HTML5: 

8 

9https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#colours 

10 

11For ease of understanding, the relevant steps of the algorithm from the standard are 

12included as comments interspersed in the implementation. 

13 

14""" 

15 

16# SPDX-License-Identifier: BSD-3-Clause 

17 

18import string 

19 

20from ._definitions import _CSS3_NAMES_TO_HEX 

21from ._types import HTML5SimpleColor, IntTuple 

22 

23 

24def html5_parse_simple_color(value: str) -> HTML5SimpleColor: 

25 """ 

26 Apply the HTML5 simple color parsing algorithm. 

27 

28 Examples: 

29 

30 .. doctest:: 

31 

32 >>> html5_parse_simple_color("#ffffff") 

33 HTML5SimpleColor(red=255, green=255, blue=255) 

34 >>> html5_parse_simple_color("#fff") 

35 Traceback (most recent call last): 

36 ... 

37 ValueError: An HTML5 simple color must be a string seven characters long. 

38 

39 :param value: The color to parse. 

40 :type value: :class:`str`, which must consist of exactly the character ``"#"`` 

41 followed by six hexadecimal digits. 

42 :raises ValueError: when the given value is not a Unicode string of length 7, 

43 consisting of exactly the character ``#`` followed by six hexadecimal digits. 

44 

45 """ 

46 # 1. Let input be the string being parsed. 

47 # 

48 # 2. If input is not exactly seven characters long, then return an error. 

49 if not isinstance(value, str) or len(value) != 7: 

50 raise ValueError( 

51 "An HTML5 simple color must be a Unicode string seven characters long." 

52 ) 

53 

54 # 3. If the first character in input is not a U+0023 NUMBER SIGN character (#), then 

55 # return an error. 

56 if not value.startswith("#"): 

57 raise ValueError( 

58 "An HTML5 simple color must begin with the character '#' (U+0023)." 

59 ) 

60 

61 # 4. If the last six characters of input are not all ASCII hex digits, then return 

62 # an error. 

63 if not all(c in string.hexdigits for c in value[1:]): 

64 raise ValueError( 

65 "An HTML5 simple color must contain exactly six ASCII hex digits." 

66 ) 

67 

68 # 5. Let result be a simple color. 

69 # 

70 # 6. Interpret the second and third characters as a hexadecimal number and let the 

71 # result be the red component of result. 

72 # 

73 # 7. Interpret the fourth and fifth characters as a hexadecimal number and let the 

74 # result be the green component of result. 

75 # 

76 # 8. Interpret the sixth and seventh characters as a hexadecimal number and let the 

77 # result be the blue component of result. 

78 # 

79 # 9. Return result. 

80 return HTML5SimpleColor( 

81 int(value[1:3], 16), int(value[3:5], 16), int(value[5:7], 16) 

82 ) 

83 

84 

85def html5_serialize_simple_color(simple_color: IntTuple) -> str: 

86 """ 

87 Apply the HTML5 simple color serialization algorithm. 

88 

89 Examples: 

90 

91 .. doctest:: 

92 

93 >>> html5_serialize_simple_color((0, 0, 0)) 

94 '#000000' 

95 >>> html5_serialize_simple_color((255, 255, 255)) 

96 '#ffffff' 

97 

98 :param simple_color: The color to serialize. 

99 

100 """ 

101 red, green, blue = simple_color 

102 

103 # 1. Let result be a string consisting of a single "#" (U+0023) character. 

104 result = "#" 

105 

106 # 2. Convert the red, green, and blue components in turn to two-digit hexadecimal 

107 # numbers using lowercase ASCII hex digits, zero-padding if necessary, and append 

108 # these numbers to result, in the order red, green, blue. 

109 format_string = "{:02x}" 

110 result += format_string.format(red) 

111 result += format_string.format(green) 

112 result += format_string.format(blue) 

113 

114 # 3. Return result, which will be a valid lowercase simple color. 

115 return result 

116 

117 

118def html5_parse_legacy_color(value: str) -> HTML5SimpleColor: 

119 """ 

120 Apply the HTML5 legacy color parsing algorithm. 

121 

122 Note that, since this algorithm is intended to handle many _types of 

123 malformed color values present in real-world Web documents, it is 

124 *extremely* forgiving of input, but the results of parsing inputs 

125 with high levels of "junk" (i.e., text other than a color value) 

126 may be surprising. 

127 

128 Examples: 

129 

130 .. doctest:: 

131 

132 >>> html5_parse_legacy_color("black") 

133 HTML5SimpleColor(red=0, green=0, blue=0) 

134 >>> html5_parse_legacy_color("chucknorris") 

135 HTML5SimpleColor(red=192, green=0, blue=0) 

136 >>> html5_parse_legacy_color("Window") 

137 HTML5SimpleColor(red=0, green=13, blue=0) 

138 

139 :param value: The color to parse. 

140 

141 :raises ValueError: when the given value is not a Unicode string, when it is the 

142 empty string, or when it is precisely the string ``"transparent"``. 

143 

144 """ 

145 # 1. Let input be the string being parsed. 

146 if not isinstance(value, str): 

147 raise ValueError( 

148 "HTML5 legacy color parsing requires a Unicode string as input." 

149 ) 

150 

151 # 2. If input is the empty string, then return an error. 

152 if value == "": 

153 raise ValueError("HTML5 legacy color parsing forbids empty string as a value.") 

154 

155 # 3. Strip leading and trailing ASCII whitespace from input. 

156 value = value.strip() 

157 

158 # 4. If input is an ASCII case-insensitive match for the string "transparent", then 

159 # return an error. 

160 if value.lower() == "transparent": 

161 raise ValueError('HTML5 legacy color parsing forbids "transparent" as a value.') 

162 

163 # 5. If input is an ASCII case-insensitive match for one of the named colors, then 

164 # return the simple color corresponding to that keyword. 

165 # 

166 # Note: CSS2 System Colors are not recognized. 

167 if keyword_hex := _CSS3_NAMES_TO_HEX.get(value.lower()): 

168 return html5_parse_simple_color(keyword_hex) 

169 

170 # 6. If input's code point length is four, and the first character in input is 

171 # U+0023 (#), and the last three characters of input are all ASCII hex digits, 

172 # then: 

173 if ( 

174 len(value) == 4 

175 and value.startswith("#") 

176 and all(c in string.hexdigits for c in value[1:]) 

177 ): 

178 # 1. Let result be a simple color. 

179 # 

180 # 2. Interpret the second character of input as a hexadecimal digit; let the red 

181 # component of result be the resulting number multiplied by 17. 

182 # 

183 # 3. Interpret the third character of input as a hexadecimal digit; let the 

184 # green component of result be the resulting number multiplied by 17. 

185 # 

186 # 4. Interpret the fourth character of input as a hexadecimal digit; let the 

187 # blue component of result be the resulting number multiplied by 17. 

188 result = HTML5SimpleColor( 

189 int(value[1], 16) * 17, int(value[2], 16) * 17, int(value[3], 16) * 17 

190 ) 

191 

192 # 5. Return result. 

193 return result 

194 

195 # 7. Replace any code points greater than U+FFFF in input (i.e., any characters that 

196 # are not in the basic multilingual plane) with the two-character string "00". 

197 value = "".join("00" if ord(c) > 0xFFFF else c for c in value) 

198 

199 # 8. If input's code point length is greater than 128, truncate input, leaving only 

200 # the first 128 characters. 

201 if len(value) > 128: 

202 value = value[:128] 

203 

204 # 9. If the first character in input is a U+0023 NUMBER SIGN character (#), remove 

205 # it. 

206 if value.startswith("#"): 

207 value = value[1:] 

208 

209 # 10. Replace any character in input that is not an ASCII hex digit with the 

210 # character U+0030 DIGIT ZERO (0). 

211 value = "".join(c if c in string.hexdigits else "0" for c in value) 

212 

213 # 11. While input's code point length is zero or not a multiple of three, append a 

214 # U+0030 DIGIT ZERO (0) character to input. 

215 while (len(value) == 0) or (len(value) % 3 != 0): 

216 value += "0" 

217 

218 # 12. Split input into three strings of equal code point length, to obtain three 

219 # components. Let length be the code point length that all of those components 

220 # have (one third the code point length of input). 

221 length = int(len(value) / 3) 

222 red = value[:length] 

223 green = value[length : length * 2] 

224 blue = value[length * 2 :] 

225 

226 # 13. If length is greater than 8, then remove the leading length-8 characters in 

227 # each component, and let length be 8. 

228 if length > 8: 

229 red, green, blue = (red[length - 8 :], green[length - 8 :], blue[length - 8 :]) 

230 length = 8 

231 

232 # 14. While length is greater than two and the first character in each component is 

233 # a U+0030 DIGIT ZERO (0) character, remove that character and reduce length by 

234 # one. 

235 while (length > 2) and (red[0] == "0" and green[0] == "0" and blue[0] == "0"): 

236 red, green, blue = (red[1:], green[1:], blue[1:]) 

237 length -= 1 

238 

239 # 15. If length is still greater than two, truncate each component, leaving only the 

240 # first two characters in each. 

241 if length > 2: 

242 red, green, blue = (red[:2], green[:2], blue[:2]) 

243 

244 # 16. Let result be a simple color. 

245 # 

246 # 17. Interpret the first component as a hexadecimal number; let the red component 

247 # of result be the resulting number. 

248 # 

249 # 18. Interpret the second component as a hexadecimal number; let the green 

250 # component of result be the resulting number. 

251 # 

252 # 19. Interpret the third component as a hexadecimal number; let the blue component 

253 # of result be the resulting number. 

254 # 

255 # 20. Return result. 

256 return HTML5SimpleColor(int(red, 16), int(green, 16), int(blue, 16))