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

130 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1from .ast import AtRule, Declaration, ParseError, QualifiedRule 

2from .tokenizer import parse_component_value_list 

3 

4 

5def _to_token_iterator(input, skip_comments=False): 

6 """Iterate component values out of string or component values iterable. 

7 

8 :type input: :obj:`str` or :term:`iterable` 

9 :param input: A string or an iterable of :term:`component values`. 

10 :type skip_comments: :obj:`bool` 

11 :param skip_comments: If the input is a string, ignore all CSS comments. 

12 :returns: An iterator yielding :term:`component values`. 

13 

14 """ 

15 # Accept ASCII-only byte strings on Python 2, with implicit conversion. 

16 if isinstance(input, str): 

17 input = parse_component_value_list(input, skip_comments) 

18 return iter(input) 

19 

20 

21def _next_significant(tokens): 

22 """Return the next significant (neither whitespace or comment) token. 

23 

24 :type tokens: :term:`iterator` 

25 :param tokens: An iterator yielding :term:`component values`. 

26 :returns: A :term:`component value`, or :obj:`None`. 

27 

28 """ 

29 for token in tokens: 

30 if token.type not in ('whitespace', 'comment'): 

31 return token 

32 

33 

34def parse_one_component_value(input, skip_comments=False): 

35 """Parse a single :diagram:`component value`. 

36 

37 This is used e.g. for an attribute value 

38 referred to by ``attr(foo length)``. 

39 

40 :type input: :obj:`str` or :term:`iterable` 

41 :param input: A string or an iterable of :term:`component values`. 

42 :type skip_comments: :obj:`bool` 

43 :param skip_comments: If the input is a string, ignore all CSS comments. 

44 :returns: 

45 A :term:`component value` (that is neither whitespace or comment), 

46 or a :class:`~tinycss2.ast.ParseError`. 

47 

48 """ 

49 tokens = _to_token_iterator(input, skip_comments) 

50 first = _next_significant(tokens) 

51 second = _next_significant(tokens) 

52 if first is None: 

53 return ParseError(1, 1, 'empty', 'Input is empty') 

54 if second is not None: 

55 return ParseError( 

56 second.source_line, second.source_column, 'extra-input', 

57 'Got more than one token') 

58 else: 

59 return first 

60 

61 

62def parse_one_declaration(input, skip_comments=False): 

63 """Parse a single :diagram:`declaration`. 

64 

65 This is used e.g. for a declaration in an `@supports 

66 <https://drafts.csswg.org/css-conditional/#at-supports>`_ test. 

67 

68 :type input: :obj:`str` or :term:`iterable` 

69 :param input: A string or an iterable of :term:`component values`. 

70 :type skip_comments: :obj:`bool` 

71 :param skip_comments: If the input is a string, ignore all CSS comments. 

72 :returns: 

73 A :class:`~tinycss2.ast.Declaration` 

74 or :class:`~tinycss2.ast.ParseError`. 

75 

76 Any whitespace or comment before the ``:`` colon is dropped. 

77 

78 """ 

79 tokens = _to_token_iterator(input, skip_comments) 

80 first_token = _next_significant(tokens) 

81 if first_token is None: 

82 return ParseError(1, 1, 'empty', 'Input is empty') 

83 return _parse_declaration(first_token, tokens) 

84 

85 

86def _parse_declaration(first_token, tokens): 

87 """Parse a declaration. 

88 

89 Consume :obj:`tokens` until the end of the declaration or the first error. 

90 

91 :type first_token: :term:`component value` 

92 :param first_token: The first component value of the rule. 

93 :type tokens: :term:`iterator` 

94 :param tokens: An iterator yielding :term:`component values`. 

95 :returns: 

96 A :class:`~tinycss2.ast.Declaration` 

97 or :class:`~tinycss2.ast.ParseError`. 

98 

99 """ 

100 name = first_token 

101 if name.type != 'ident': 

102 return ParseError(name.source_line, name.source_column, 'invalid', 

103 'Expected <ident> for declaration name, got %s.' 

104 % name.type) 

105 

106 colon = _next_significant(tokens) 

107 if colon is None: 

108 return ParseError(name.source_line, name.source_column, 'invalid', 

109 "Expected ':' after declaration name, got EOF") 

110 elif colon != ':': 

111 return ParseError(colon.source_line, colon.source_column, 'invalid', 

112 "Expected ':' after declaration name, got %s." 

113 % colon.type) 

114 

115 value = [] 

116 state = 'value' 

117 for i, token in enumerate(tokens): 

118 if state == 'value' and token == '!': 

119 state = 'bang' 

120 bang_position = i 

121 elif state == 'bang' and token.type == 'ident' \ 

122 and token.lower_value == 'important': 

123 state = 'important' 

124 elif token.type not in ('whitespace', 'comment'): 

125 state = 'value' 

126 value.append(token) 

127 

128 if state == 'important': 

129 del value[bang_position:] 

130 

131 return Declaration(name.source_line, name.source_column, name.value, 

132 name.lower_value, value, state == 'important') 

133 

134 

135def _consume_declaration_in_list(first_token, tokens): 

136 """Like :func:`_parse_declaration`, but stop at the first ``;``.""" 

137 other_declaration_tokens = [] 

138 for token in tokens: 

139 if token == ';': 

140 break 

141 other_declaration_tokens.append(token) 

142 return _parse_declaration(first_token, iter(other_declaration_tokens)) 

143 

144 

145def parse_declaration_list(input, skip_comments=False, skip_whitespace=False): 

146 """Parse a :diagram:`declaration list` (which may also contain at-rules). 

147 

148 This is used e.g. for the :attr:`~tinycss2.ast.QualifiedRule.content` 

149 of a style rule or ``@page`` rule, 

150 or for the ``style`` attribute of an HTML element. 

151 

152 In contexts that don’t expect any at-rule, 

153 all :class:`~tinycss2.ast.AtRule` objects 

154 should simply be rejected as invalid. 

155 

156 :type input: :obj:`str` or :term:`iterable` 

157 :param input: A string or an iterable of :term:`component values`. 

158 :type skip_comments: :obj:`bool` 

159 :param skip_comments: 

160 Ignore CSS comments at the top-level of the list. 

161 If the input is a string, ignore all comments. 

162 :type skip_whitespace: :obj:`bool` 

163 :param skip_whitespace: 

164 Ignore whitespace at the top-level of the list. 

165 Whitespace is still preserved 

166 in the :attr:`~tinycss2.ast.Declaration.value` of declarations 

167 and the :attr:`~tinycss2.ast.AtRule.prelude` 

168 and :attr:`~tinycss2.ast.AtRule.content` of at-rules. 

169 :returns: 

170 A list of 

171 :class:`~tinycss2.ast.Declaration`, 

172 :class:`~tinycss2.ast.AtRule`, 

173 :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false), 

174 :class:`~tinycss2.ast.WhitespaceToken` 

175 (if ``skip_whitespace`` is false), 

176 and :class:`~tinycss2.ast.ParseError` objects 

177 

178 """ 

179 tokens = _to_token_iterator(input, skip_comments) 

180 result = [] 

181 for token in tokens: 

182 if token.type == 'whitespace': 

183 if not skip_whitespace: 

184 result.append(token) 

185 elif token.type == 'comment': 

186 if not skip_comments: 

187 result.append(token) 

188 elif token.type == 'at-keyword': 

189 result.append(_consume_at_rule(token, tokens)) 

190 elif token != ';': 

191 result.append(_consume_declaration_in_list(token, tokens)) 

192 return result 

193 

194 

195def parse_one_rule(input, skip_comments=False): 

196 """Parse a single :diagram:`qualified rule` or :diagram:`at-rule`. 

197 

198 This would be used e.g. by `insertRule() 

199 <https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule>`_ 

200 in an implementation of CSSOM. 

201 

202 :type input: :obj:`str` or :term:`iterable` 

203 :param input: A string or an iterable of :term:`component values`. 

204 :type skip_comments: :obj:`bool` 

205 :param skip_comments: 

206 If the input is a string, ignore all CSS comments. 

207 :returns: 

208 A :class:`~tinycss2.ast.QualifiedRule`, 

209 :class:`~tinycss2.ast.AtRule`, 

210 or :class:`~tinycss2.ast.ParseError` objects. 

211 

212 Any whitespace or comment before or after the rule is dropped. 

213 

214 """ 

215 tokens = _to_token_iterator(input, skip_comments) 

216 first = _next_significant(tokens) 

217 if first is None: 

218 return ParseError(1, 1, 'empty', 'Input is empty') 

219 

220 rule = _consume_rule(first, tokens) 

221 next = _next_significant(tokens) 

222 if next is not None: 

223 return ParseError( 

224 next.source_line, next.source_column, 'extra-input', 

225 'Expected a single rule, got %s after the first rule.' % next.type) 

226 return rule 

227 

228 

229def parse_rule_list(input, skip_comments=False, skip_whitespace=False): 

230 """Parse a non-top-level :diagram:`rule list`. 

231 

232 This is used for parsing the :attr:`~tinycss2.ast.AtRule.content` 

233 of nested rules like ``@media``. 

234 This differs from :func:`parse_stylesheet` in that 

235 top-level ``<!--`` and ``-->`` tokens are not ignored. 

236 

237 :type input: :obj:`str` or :term:`iterable` 

238 :param input: A string or an iterable of :term:`component values`. 

239 :type skip_comments: :obj:`bool` 

240 :param skip_comments: 

241 Ignore CSS comments at the top-level of the list. 

242 If the input is a string, ignore all comments. 

243 :type skip_whitespace: :obj:`bool` 

244 :param skip_whitespace: 

245 Ignore whitespace at the top-level of the list. 

246 Whitespace is still preserved 

247 in the :attr:`~tinycss2.ast.QualifiedRule.prelude` 

248 and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules. 

249 :returns: 

250 A list of 

251 :class:`~tinycss2.ast.QualifiedRule`, 

252 :class:`~tinycss2.ast.AtRule`, 

253 :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false), 

254 :class:`~tinycss2.ast.WhitespaceToken` 

255 (if ``skip_whitespace`` is false), 

256 and :class:`~tinycss2.ast.ParseError` objects. 

257 

258 """ 

259 tokens = _to_token_iterator(input, skip_comments) 

260 result = [] 

261 for token in tokens: 

262 if token.type == 'whitespace': 

263 if not skip_whitespace: 

264 result.append(token) 

265 elif token.type == 'comment': 

266 if not skip_comments: 

267 result.append(token) 

268 else: 

269 result.append(_consume_rule(token, tokens)) 

270 return result 

271 

272 

273def parse_stylesheet(input, skip_comments=False, skip_whitespace=False): 

274 """Parse :diagram:`stylesheet` from text. 

275 

276 This is used e.g. for a ``<style>`` HTML element. 

277 

278 This differs from :func:`parse_rule_list` in that 

279 top-level ``<!--`` and ``-->`` tokens are ignored. 

280 This is a legacy quirk for the ``<style>`` HTML element. 

281 

282 :type input: :obj:`str` or :term:`iterable` 

283 :param input: A string or an iterable of :term:`component values`. 

284 :type skip_comments: :obj:`bool` 

285 :param skip_comments: 

286 Ignore CSS comments at the top-level of the stylesheet. 

287 If the input is a string, ignore all comments. 

288 :type skip_whitespace: :obj:`bool` 

289 :param skip_whitespace: 

290 Ignore whitespace at the top-level of the stylesheet. 

291 Whitespace is still preserved 

292 in the :attr:`~tinycss2.ast.QualifiedRule.prelude` 

293 and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules. 

294 :returns: 

295 A list of 

296 :class:`~tinycss2.ast.QualifiedRule`, 

297 :class:`~tinycss2.ast.AtRule`, 

298 :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false), 

299 :class:`~tinycss2.ast.WhitespaceToken` 

300 (if ``skip_whitespace`` is false), 

301 and :class:`~tinycss2.ast.ParseError` objects. 

302 

303 """ 

304 tokens = _to_token_iterator(input, skip_comments) 

305 result = [] 

306 for token in tokens: 

307 if token.type == 'whitespace': 

308 if not skip_whitespace: 

309 result.append(token) 

310 elif token.type == 'comment': 

311 if not skip_comments: 

312 result.append(token) 

313 elif token not in ('<!--', '-->'): 

314 result.append(_consume_rule(token, tokens)) 

315 return result 

316 

317 

318def _consume_rule(first_token, tokens): 

319 """Parse a qualified rule or at-rule. 

320 

321 Consume just enough of :obj:`tokens` for this rule. 

322 

323 :type first_token: :term:`component value` 

324 :param first_token: The first component value of the rule. 

325 :type tokens: :term:`iterator` 

326 :param tokens: An iterator yielding :term:`component values`. 

327 :returns: 

328 A :class:`~tinycss2.ast.QualifiedRule`, 

329 :class:`~tinycss2.ast.AtRule`, 

330 or :class:`~tinycss2.ast.ParseError`. 

331 

332 """ 

333 if first_token.type == 'at-keyword': 

334 return _consume_at_rule(first_token, tokens) 

335 if first_token.type == '{} block': 

336 prelude = [] 

337 block = first_token 

338 else: 

339 prelude = [first_token] 

340 for token in tokens: 

341 if token.type == '{} block': 

342 block = token 

343 break 

344 prelude.append(token) 

345 else: 

346 return ParseError( 

347 prelude[-1].source_line, prelude[-1].source_column, 'invalid', 

348 'EOF reached before {} block for a qualified rule.') 

349 return QualifiedRule(first_token.source_line, first_token.source_column, 

350 prelude, block.content) 

351 

352 

353def _consume_at_rule(at_keyword, tokens): 

354 """Parse an at-rule. 

355 

356 Consume just enough of :obj:`tokens` for this rule. 

357 

358 :type at_keyword: :class:`AtKeywordToken` 

359 :param at_keyword: The at-rule keyword token starting this rule. 

360 :type tokens: :term:`iterator` 

361 :param tokens: An iterator yielding :term:`component values`. 

362 :returns: 

363 A :class:`~tinycss2.ast.QualifiedRule`, 

364 or :class:`~tinycss2.ast.ParseError`. 

365 

366 """ 

367 prelude = [] 

368 content = None 

369 for token in tokens: 

370 if token.type == '{} block': 

371 content = token.content 

372 break 

373 elif token == ';': 

374 break 

375 prelude.append(token) 

376 return AtRule(at_keyword.source_line, at_keyword.source_column, 

377 at_keyword.value, at_keyword.lower_value, prelude, content)