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

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

288 statements  

1""" 

2 

3Data structures for the CSS abstract syntax tree. 

4 

5""" 

6 

7 

8from webencodings import ascii_lower 

9 

10from .serializer import _serialize_to, serialize_identifier, serialize_name 

11 

12 

13class Node: 

14 """Every node type inherits from this class, 

15 which is never instantiated directly. 

16 

17 .. attribute:: type 

18 

19 Each child class has a :attr:`type` class attribute 

20 with a unique string value. 

21 This allows checking for the node type with code like: 

22 

23 .. code-block:: python 

24 

25 if node.type == 'whitespace': 

26 

27 instead of the more verbose: 

28 

29 .. code-block:: python 

30 

31 from tinycss2.ast import WhitespaceToken 

32 if isinstance(node, WhitespaceToken): 

33 

34 Every node also has these attributes and methods, 

35 which are not repeated for brevity: 

36 

37 .. attribute:: source_line 

38 

39 The line number of the start of the node in the CSS source. 

40 Starts at 1. 

41 

42 .. attribute:: source_column 

43 

44 The column number within :attr:`source_line` of the start of the node 

45 in the CSS source. 

46 Starts at 1. 

47 

48 .. automethod:: serialize 

49 

50 """ 

51 __slots__ = ['source_column', 'source_line'] 

52 

53 def __init__(self, source_line, source_column): 

54 self.source_line = source_line 

55 self.source_column = source_column 

56 

57 def __repr__(self): 

58 return self.repr_format.format(self=self) 

59 

60 def serialize(self): 

61 """Serialize this node to CSS syntax and return a Unicode string.""" 

62 chunks = [] 

63 self._serialize_to(chunks.append) 

64 return ''.join(chunks) 

65 

66 def _serialize_to(self, write): 

67 """Serialize this node to CSS syntax, writing chunks as Unicode string 

68 by calling the provided :obj:`write` callback. 

69 

70 """ 

71 raise NotImplementedError # pragma: no cover 

72 

73 

74class ParseError(Node): 

75 """A syntax error of some sort. May occur anywhere in the tree. 

76 

77 Syntax errors are not fatal in the parser 

78 to allow for different error handling behaviors. 

79 For example, an error in a Selector list makes the whole rule invalid, 

80 but an error in a Media Query list only replaces one comma-separated query 

81 with ``not all``. 

82 

83 .. autoattribute:: type 

84 

85 .. attribute:: kind 

86 

87 Machine-readable string indicating the type of error. 

88 Example: ``'bad-url'``. 

89 

90 .. attribute:: message 

91 

92 Human-readable explanation of the error, as a string. 

93 Could be translated, expanded to include details, etc. 

94 

95 """ 

96 __slots__ = ['kind', 'message'] 

97 type = 'error' 

98 repr_format = '<{self.__class__.__name__} {self.kind}>' 

99 

100 def __init__(self, line, column, kind, message): 

101 Node.__init__(self, line, column) 

102 self.kind = kind 

103 self.message = message 

104 

105 def _serialize_to(self, write): 

106 if self.kind == 'bad-string': 

107 write('"[bad string]\n') 

108 elif self.kind == 'bad-url': 

109 write('url([bad url])') 

110 elif self.kind in ')]}': 

111 write(self.kind) 

112 elif self.kind in ('eof-in-string', 'eof-in-url'): 

113 pass 

114 else: # pragma: no cover 

115 raise TypeError('Can not serialize %r' % self) 

116 

117 

118class Comment(Node): 

119 """A CSS comment. 

120 

121 Comments can be ignored by passing ``skip_comments=True`` 

122 to functions such as :func:`~tinycss2.parse_component_value_list`. 

123 

124 .. autoattribute:: type 

125 

126 .. attribute:: value 

127 

128 The content of the comment, between ``/*`` and ``*/``, as a string. 

129 

130 """ 

131 __slots__ = ['value'] 

132 type = 'comment' 

133 repr_format = '<{self.__class__.__name__} {self.value}>' 

134 

135 def __init__(self, line, column, value): 

136 Node.__init__(self, line, column) 

137 self.value = value 

138 

139 def _serialize_to(self, write): 

140 write('/*') 

141 write(self.value) 

142 write('*/') 

143 

144 

145class WhitespaceToken(Node): 

146 """A :diagram:`whitespace-token`. 

147 

148 .. autoattribute:: type 

149 

150 .. attribute:: value 

151 

152 The whitespace sequence, as a string, as in the original CSS source. 

153 

154 

155 """ 

156 __slots__ = ['value'] 

157 type = 'whitespace' 

158 repr_format = '<{self.__class__.__name__}>' 

159 

160 def __init__(self, line, column, value): 

161 Node.__init__(self, line, column) 

162 self.value = value 

163 

164 def _serialize_to(self, write): 

165 write(self.value) 

166 

167 

168class LiteralToken(Node): 

169 r"""Token that represents one or more characters as in the CSS source. 

170 

171 .. autoattribute:: type 

172 

173 .. attribute:: value 

174 

175 A string of one to four characters. 

176 

177 Instances compare equal to their :attr:`value`, 

178 so that these are equivalent: 

179 

180 .. code-block:: python 

181 

182 if node == ';': 

183 if node.type == 'literal' and node.value == ';': 

184 

185 This regroups what `the specification`_ defines as separate token types: 

186 

187 .. _the specification: https://drafts.csswg.org/css-syntax-3/ 

188 

189 * *<colon-token>* ``:`` 

190 * *<semicolon-token>* ``;`` 

191 * *<comma-token>* ``,`` 

192 * *<cdc-token>* ``-->`` 

193 * *<cdo-token>* ``<!--`` 

194 * *<include-match-token>* ``~=`` 

195 * *<dash-match-token>* ``|=`` 

196 * *<prefix-match-token>* ``^=`` 

197 * *<suffix-match-token>* ``$=`` 

198 * *<substring-match-token>* ``*=`` 

199 * *<column-token>* ``||`` 

200 * *<delim-token>* (a single ASCII character not part of any another token) 

201 

202 """ 

203 __slots__ = ['value'] 

204 type = 'literal' 

205 repr_format = '<{self.__class__.__name__} {self.value}>' 

206 

207 def __init__(self, line, column, value): 

208 Node.__init__(self, line, column) 

209 self.value = value 

210 

211 def __eq__(self, other): 

212 return self.value == other or self is other 

213 

214 def __ne__(self, other): 

215 return not self == other 

216 

217 def _serialize_to(self, write): 

218 write(self.value) 

219 

220 

221class IdentToken(Node): 

222 """An :diagram:`ident-token`. 

223 

224 .. autoattribute:: type 

225 

226 .. attribute:: value 

227 

228 The unescaped value, as a Unicode string. 

229 

230 .. attribute:: lower_value 

231 

232 Same as :attr:`value` but normalized to *ASCII lower case*, 

233 see :func:`~webencodings.ascii_lower`. 

234 This is the value to use when comparing to a CSS keyword. 

235 

236 """ 

237 __slots__ = ['lower_value', 'value'] 

238 type = 'ident' 

239 repr_format = '<{self.__class__.__name__} {self.value}>' 

240 

241 def __init__(self, line, column, value): 

242 Node.__init__(self, line, column) 

243 self.value = value 

244 try: 

245 self.lower_value = ascii_lower(value) 

246 except UnicodeEncodeError: 

247 self.lower_value = value 

248 

249 def _serialize_to(self, write): 

250 write(serialize_identifier(self.value)) 

251 

252 

253class AtKeywordToken(Node): 

254 """An :diagram:`at-keyword-token`. 

255 

256 .. code-block:: text 

257 

258 '@' <value> 

259 

260 .. autoattribute:: type 

261 

262 .. attribute:: value 

263 

264 The unescaped value, as a Unicode string, without the preceding ``@``. 

265 

266 .. attribute:: lower_value 

267 

268 Same as :attr:`value` but normalized to *ASCII lower case*, 

269 see :func:`~webencodings.ascii_lower`. 

270 This is the value to use when comparing to a CSS at-keyword. 

271 

272 .. code-block:: python 

273 

274 if node.type == 'at-keyword' and node.lower_value == 'import': 

275 

276 """ 

277 __slots__ = ['lower_value', 'value'] 

278 type = 'at-keyword' 

279 repr_format = '<{self.__class__.__name__} @{self.value}>' 

280 

281 def __init__(self, line, column, value): 

282 Node.__init__(self, line, column) 

283 self.value = value 

284 try: 

285 self.lower_value = ascii_lower(value) 

286 except UnicodeEncodeError: 

287 self.lower_value = value 

288 

289 def _serialize_to(self, write): 

290 write('@') 

291 write(serialize_identifier(self.value)) 

292 

293 

294class HashToken(Node): 

295 r"""A :diagram:`hash-token`. 

296 

297 .. code-block:: text 

298 

299 '#' <value> 

300 

301 .. autoattribute:: type 

302 

303 .. attribute:: value 

304 

305 The unescaped value, as a Unicode string, without the preceding ``#``. 

306 

307 .. attribute:: is_identifier 

308 

309 A boolean, true if the CSS source for this token 

310 was ``#`` followed by a valid identifier. 

311 (Only such hash tokens are valid ID selectors.) 

312 

313 """ 

314 __slots__ = ['is_identifier', 'value'] 

315 type = 'hash' 

316 repr_format = '<{self.__class__.__name__} #{self.value}>' 

317 

318 def __init__(self, line, column, value, is_identifier): 

319 Node.__init__(self, line, column) 

320 self.value = value 

321 self.is_identifier = is_identifier 

322 

323 def _serialize_to(self, write): 

324 write('#') 

325 if self.is_identifier: 

326 write(serialize_identifier(self.value)) 

327 else: 

328 write(serialize_name(self.value)) 

329 

330 

331class StringToken(Node): 

332 """A :diagram:`string-token`. 

333 

334 .. code-block:: text 

335 

336 '"' <value> '"' 

337 

338 .. autoattribute:: type 

339 

340 .. attribute:: value 

341 

342 The unescaped value, as a Unicode string, without the quotes. 

343 

344 """ 

345 __slots__ = ['representation', 'value'] 

346 type = 'string' 

347 repr_format = '<{self.__class__.__name__} {self.representation}>' 

348 

349 def __init__(self, line, column, value, representation): 

350 Node.__init__(self, line, column) 

351 self.value = value 

352 self.representation = representation 

353 

354 def _serialize_to(self, write): 

355 write(self.representation) 

356 

357 

358class URLToken(Node): 

359 """An :diagram:`url-token`. 

360 

361 .. code-block:: text 

362 

363 'url(' <value> ')' 

364 

365 .. autoattribute:: type 

366 

367 .. attribute:: value 

368 

369 The unescaped URL, as a Unicode string, without the ``url(`` and ``)`` 

370 markers. 

371 

372 """ 

373 __slots__ = ['representation', 'value'] 

374 type = 'url' 

375 repr_format = '<{self.__class__.__name__} {self.representation}>' 

376 

377 def __init__(self, line, column, value, representation): 

378 Node.__init__(self, line, column) 

379 self.value = value 

380 self.representation = representation 

381 

382 def _serialize_to(self, write): 

383 write(self.representation) 

384 

385 

386class UnicodeRangeToken(Node): 

387 """A :diagram:`unicode-range-token`. 

388 

389 .. autoattribute:: type 

390 

391 .. attribute:: start 

392 

393 The start of the range, as an integer between 0 and 1114111. 

394 

395 .. attribute:: end 

396 

397 The end of the range, as an integer between 0 and 1114111. 

398 Same as :attr:`start` if the source only specified one value. 

399 

400 """ 

401 __slots__ = ['end', 'start'] 

402 type = 'unicode-range' 

403 repr_format = '<{self.__class__.__name__} {self.start} {self.end}>' 

404 

405 def __init__(self, line, column, start, end): 

406 Node.__init__(self, line, column) 

407 self.start = start 

408 self.end = end 

409 

410 def _serialize_to(self, write): 

411 if self.end == self.start: 

412 write('U+%X' % self.start) 

413 else: 

414 write('U+%X-%X' % (self.start, self.end)) 

415 

416 

417class NumberToken(Node): 

418 """A :diagram:`number-token`. 

419 

420 .. autoattribute:: type 

421 

422 .. attribute:: value 

423 

424 The numeric value as a :class:`float`. 

425 

426 .. attribute:: int_value 

427 

428 The numeric value as an :class:`int` 

429 if :attr:`is_integer` is true, :obj:`None` otherwise. 

430 

431 .. attribute:: is_integer 

432 

433 Whether the token was syntactically an integer, as a boolean. 

434 

435 .. attribute:: representation 

436 

437 The CSS representation of the value, as a Unicode string. 

438 

439 """ 

440 __slots__ = ['int_value', 'is_integer', 'representation', 'value'] 

441 type = 'number' 

442 repr_format = '<{self.__class__.__name__} {self.representation}>' 

443 

444 def __init__(self, line, column, value, int_value, representation): 

445 Node.__init__(self, line, column) 

446 self.value = value 

447 self.int_value = int_value 

448 self.is_integer = int_value is not None 

449 self.representation = representation 

450 

451 def _serialize_to(self, write): 

452 write(self.representation) 

453 

454 

455class PercentageToken(Node): 

456 """A :diagram:`percentage-token`. 

457 

458 .. code-block:: text 

459 

460 <representation> '%' 

461 

462 .. autoattribute:: type 

463 

464 .. attribute:: value 

465 

466 The value numeric as a :class:`float`. 

467 

468 .. attribute:: int_value 

469 

470 The numeric value as an :class:`int` 

471 if the token was syntactically an integer, 

472 or :obj:`None`. 

473 

474 .. attribute:: is_integer 

475 

476 Whether the token’s value was syntactically an integer, as a boolean. 

477 

478 .. attribute:: representation 

479 

480 The CSS representation of the value without the unit, 

481 as a Unicode string. 

482 

483 """ 

484 __slots__ = ['int_value', 'is_integer', 'representation', 'value'] 

485 type = 'percentage' 

486 repr_format = '<{self.__class__.__name__} {self.representation}%>' 

487 

488 def __init__(self, line, column, value, int_value, representation): 

489 Node.__init__(self, line, column) 

490 self.value = value 

491 self.int_value = int_value 

492 self.is_integer = int_value is not None 

493 self.representation = representation 

494 

495 def _serialize_to(self, write): 

496 write(self.representation) 

497 write('%') 

498 

499 

500class DimensionToken(Node): 

501 """A :diagram:`dimension-token`. 

502 

503 .. code-block:: text 

504 

505 <representation> <unit> 

506 

507 .. autoattribute:: type 

508 

509 .. attribute:: value 

510 

511 The value numeric as a :class:`float`. 

512 

513 .. attribute:: int_value 

514 

515 The numeric value as an :class:`int` 

516 if the token was syntactically an integer, 

517 or :obj:`None`. 

518 

519 .. attribute:: is_integer 

520 

521 Whether the token’s value was syntactically an integer, as a boolean. 

522 

523 .. attribute:: representation 

524 

525 The CSS representation of the value without the unit, 

526 as a Unicode string. 

527 

528 .. attribute:: unit 

529 

530 The unescaped unit, as a Unicode string. 

531 

532 .. attribute:: lower_unit 

533 

534 Same as :attr:`unit` but normalized to *ASCII lower case*, 

535 see :func:`~webencodings.ascii_lower`. 

536 This is the value to use when comparing to a CSS unit. 

537 

538 .. code-block:: python 

539 

540 if node.type == 'dimension' and node.lower_unit == 'px': 

541 

542 """ 

543 __slots__ = [ 

544 'int_value', 

545 'is_integer', 

546 'lower_unit', 

547 'representation', 

548 'unit', 

549 'value', 

550 ] 

551 type = 'dimension' 

552 repr_format = ('<{self.__class__.__name__} ' 

553 '{self.representation}{self.unit}>') 

554 

555 def __init__(self, line, column, value, int_value, representation, unit): 

556 Node.__init__(self, line, column) 

557 self.value = value 

558 self.int_value = int_value 

559 self.is_integer = int_value is not None 

560 self.representation = representation 

561 self.unit = unit 

562 try: 

563 self.lower_unit = ascii_lower(unit) 

564 except UnicodeEncodeError: 

565 self.lower_unit = unit 

566 

567 def _serialize_to(self, write): 

568 write(self.representation) 

569 # Disambiguate with scientific notation 

570 unit = self.unit 

571 if unit in ('e', 'E') or unit.startswith(('e-', 'E-')): 

572 write('\\65 ') 

573 write(serialize_name(unit[1:])) 

574 else: 

575 write(serialize_identifier(unit)) 

576 

577 

578class ParenthesesBlock(Node): 

579 """A :diagram:`()-block`. 

580 

581 .. code-block:: text 

582 

583 '(' <content> ')' 

584 

585 .. autoattribute:: type 

586 

587 .. attribute:: content 

588 

589 The content of the block, as list of :term:`component values`. 

590 The ``(`` and ``)`` markers themselves are not represented in the list. 

591 

592 """ 

593 __slots__ = ['content'] 

594 type = '() block' 

595 repr_format = '<{self.__class__.__name__} ( … )>' 

596 

597 def __init__(self, line, column, content): 

598 Node.__init__(self, line, column) 

599 self.content = content 

600 

601 def _serialize_to(self, write): 

602 write('(') 

603 _serialize_to(self.content, write) 

604 write(')') 

605 

606 

607class SquareBracketsBlock(Node): 

608 """A :diagram:`[]-block`. 

609 

610 .. code-block:: text 

611 

612 '[' <content> ']' 

613 

614 .. autoattribute:: type 

615 

616 .. attribute:: content 

617 

618 The content of the block, as list of :term:`component values`. 

619 The ``[`` and ``]`` markers themselves are not represented in the list. 

620 

621 """ 

622 __slots__ = ['content'] 

623 type = '[] block' 

624 repr_format = '<{self.__class__.__name__} [ … ]>' 

625 

626 def __init__(self, line, column, content): 

627 Node.__init__(self, line, column) 

628 self.content = content 

629 

630 def _serialize_to(self, write): 

631 write('[') 

632 _serialize_to(self.content, write) 

633 write(']') 

634 

635 

636class CurlyBracketsBlock(Node): 

637 """A :diagram:`{}-block`. 

638 

639 .. code-block:: text 

640 

641 '{' <content> '}' 

642 

643 .. autoattribute:: type 

644 

645 .. attribute:: content 

646 

647 The content of the block, as list of :term:`component values`. 

648 The ``[`` and ``]`` markers themselves are not represented in the list. 

649 

650 """ 

651 __slots__ = ['content'] 

652 type = '{} block' 

653 repr_format = '<{self.__class__.__name__} {{ … }}>' 

654 

655 def __init__(self, line, column, content): 

656 Node.__init__(self, line, column) 

657 self.content = content 

658 

659 def _serialize_to(self, write): 

660 write('{') 

661 _serialize_to(self.content, write) 

662 write('}') 

663 

664 

665class FunctionBlock(Node): 

666 """A :diagram:`function-block`. 

667 

668 .. code-block:: text 

669 

670 <name> '(' <arguments> ')' 

671 

672 .. autoattribute:: type 

673 

674 .. attribute:: name 

675 

676 The unescaped name of the function, as a Unicode string. 

677 

678 .. attribute:: lower_name 

679 

680 Same as :attr:`name` but normalized to *ASCII lower case*, 

681 see :func:`~webencodings.ascii_lower`. 

682 This is the value to use when comparing to a CSS function name. 

683 

684 .. attribute:: arguments 

685 

686 The arguments of the function, as list of :term:`component values`. 

687 The ``(`` and ``)`` markers themselves are not represented in the list. 

688 Commas are not special, but represented as :obj:`LiteralToken` objects 

689 in the list. 

690 

691 """ 

692 __slots__ = ['arguments', 'lower_name', 'name'] 

693 type = 'function' 

694 repr_format = '<{self.__class__.__name__} {self.name}( … )>' 

695 

696 def __init__(self, line, column, name, arguments): 

697 Node.__init__(self, line, column) 

698 self.name = name 

699 try: 

700 self.lower_name = ascii_lower(name) 

701 except UnicodeEncodeError: 

702 self.lower_name = name 

703 self.arguments = arguments 

704 

705 def _serialize_to(self, write): 

706 write(serialize_identifier(self.name)) 

707 write('(') 

708 _serialize_to(self.arguments, write) 

709 function = self 

710 while isinstance(function, FunctionBlock) and function.arguments: 

711 eof_in_string = ( 

712 isinstance(function.arguments[-1], ParseError) and 

713 function.arguments[-1].kind == 'eof-in-string') 

714 if eof_in_string: 

715 return 

716 function = function.arguments[-1] 

717 write(')') 

718 

719 

720class Declaration(Node): 

721 """A (property or descriptor) :diagram:`declaration`. 

722 

723 .. code-block:: text 

724 

725 <name> ':' <value> 

726 <name> ':' <value> '!important' 

727 

728 .. autoattribute:: type 

729 

730 .. attribute:: name 

731 

732 The unescaped name, as a Unicode string. 

733 

734 .. attribute:: lower_name 

735 

736 Same as :attr:`name` but normalized to *ASCII lower case*, 

737 see :func:`~webencodings.ascii_lower`. 

738 This is the value to use when comparing to 

739 a CSS property or descriptor name. 

740 

741 .. code-block:: python 

742 

743 if node.type == 'declaration' and node.lower_name == 'color': 

744 

745 .. attribute:: value 

746 

747 The declaration value as a list of :term:`component values`: 

748 anything between ``:`` and 

749 the end of the declaration, or ``!important``. 

750 

751 .. attribute:: important 

752 

753 A boolean, true if the declaration had an ``!important`` marker. 

754 It is up to the consumer to reject declarations that do not accept 

755 this flag, such as non-property descriptor declarations. 

756 

757 """ 

758 __slots__ = ['important', 'lower_name', 'name', 'value'] 

759 type = 'declaration' 

760 repr_format = '<{self.__class__.__name__} {self.name}: …>' 

761 

762 def __init__(self, line, column, name, lower_name, value, important): 

763 Node.__init__(self, line, column) 

764 self.name = name 

765 self.lower_name = lower_name 

766 self.value = value 

767 self.important = important 

768 

769 def _serialize_to(self, write): 

770 write(serialize_identifier(self.name)) 

771 write(':') 

772 _serialize_to(self.value, write) 

773 if self.important: 

774 write('!important') 

775 

776 

777class QualifiedRule(Node): 

778 """A :diagram:`qualified rule`. 

779 

780 .. code-block:: text 

781 

782 <prelude> '{' <content> '}' 

783 

784 The interpretation of qualified rules depend on their context. 

785 At the top-level of a stylesheet 

786 or in a conditional rule such as ``@media``, 

787 they are **style rules** where the :attr:`prelude` is Selectors list 

788 and the :attr:`content` is a list of property declarations. 

789 

790 .. autoattribute:: type 

791 

792 .. attribute:: prelude 

793 

794 The rule’s prelude, the part before the {} block, 

795 as a list of :term:`component values`. 

796 

797 .. attribute:: content 

798 

799 The rule’s content, the part inside the {} block, 

800 as a list of :term:`component values`. 

801 

802 """ 

803 __slots__ = ['content', 'prelude'] 

804 type = 'qualified-rule' 

805 repr_format = ('<{self.__class__.__name__} ' 

806 '… {{ … }}>') 

807 

808 def __init__(self, line, column, prelude, content): 

809 Node.__init__(self, line, column) 

810 self.prelude = prelude 

811 self.content = content 

812 

813 def _serialize_to(self, write): 

814 _serialize_to(self.prelude, write) 

815 write('{') 

816 _serialize_to(self.content, write) 

817 write('}') 

818 

819 

820class AtRule(Node): 

821 """An :diagram:`at-rule`. 

822 

823 .. code-block:: text 

824 

825 @<at_keyword> <prelude> '{' <content> '}' 

826 @<at_keyword> <prelude> ';' 

827 

828 The interpretation of at-rules depend on their at-keyword 

829 as well as their context. 

830 Most types of at-rules (ie. at-keyword values) 

831 are only allowed in some context, 

832 and must either end with a {} block or a semicolon. 

833 

834 .. autoattribute:: type 

835 

836 .. attribute:: at_keyword 

837 

838 The unescaped value of the rule’s at-keyword, 

839 without the ``@`` symbol, as a Unicode string. 

840 

841 .. attribute:: lower_at_keyword 

842 

843 Same as :attr:`at_keyword` but normalized to *ASCII lower case*, 

844 see :func:`~webencodings.ascii_lower`. 

845 This is the value to use when comparing to a CSS at-keyword. 

846 

847 .. code-block:: python 

848 

849 if node.type == 'at-rule' and node.lower_at_keyword == 'import': 

850 

851 .. attribute:: prelude 

852 

853 The rule’s prelude, the part before the {} block or semicolon, 

854 as a list of :term:`component values`. 

855 

856 .. attribute:: content 

857 

858 The rule’s content, if any. 

859 The block’s content as a list of :term:`component values` 

860 for at-rules with a {} block, 

861 or :obj:`None` for at-rules ending with a semicolon. 

862 

863 """ 

864 __slots__ = ['at_keyword', 'content', 'lower_at_keyword', 'prelude'] 

865 type = 'at-rule' 

866 repr_format = ('<{self.__class__.__name__} ' 

867 '@{self.at_keyword} … {{ … }}>') 

868 

869 def __init__(self, line, column, 

870 at_keyword, lower_at_keyword, prelude, content): 

871 Node.__init__(self, line, column) 

872 self.at_keyword = at_keyword 

873 self.lower_at_keyword = lower_at_keyword 

874 self.prelude = prelude 

875 self.content = content 

876 

877 def _serialize_to(self, write): 

878 write('@') 

879 write(serialize_identifier(self.at_keyword)) 

880 _serialize_to(self.prelude, write) 

881 if self.content is None: 

882 write(';') 

883 else: 

884 write('{') 

885 _serialize_to(self.content, write) 

886 write('}')