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

282 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 self.lower_unit = ascii_lower(unit) 

563 

564 def _serialize_to(self, write): 

565 write(self.representation) 

566 # Disambiguate with scientific notation 

567 unit = self.unit 

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

569 write('\\65 ') 

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

571 else: 

572 write(serialize_identifier(unit)) 

573 

574 

575class ParenthesesBlock(Node): 

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

577 

578 .. code-block:: text 

579 

580 '(' <content> ')' 

581 

582 .. autoattribute:: type 

583 

584 .. attribute:: content 

585 

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

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

588 

589 """ 

590 __slots__ = ['content'] 

591 type = '() block' 

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

593 

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

595 Node.__init__(self, line, column) 

596 self.content = content 

597 

598 def _serialize_to(self, write): 

599 write('(') 

600 _serialize_to(self.content, write) 

601 write(')') 

602 

603 

604class SquareBracketsBlock(Node): 

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

606 

607 .. code-block:: text 

608 

609 '[' <content> ']' 

610 

611 .. autoattribute:: type 

612 

613 .. attribute:: content 

614 

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

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

617 

618 """ 

619 __slots__ = ['content'] 

620 type = '[] block' 

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

622 

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

624 Node.__init__(self, line, column) 

625 self.content = content 

626 

627 def _serialize_to(self, write): 

628 write('[') 

629 _serialize_to(self.content, write) 

630 write(']') 

631 

632 

633class CurlyBracketsBlock(Node): 

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

635 

636 .. code-block:: text 

637 

638 '{' <content> '}' 

639 

640 .. autoattribute:: type 

641 

642 .. attribute:: content 

643 

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

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

646 

647 """ 

648 __slots__ = ['content'] 

649 type = '{} block' 

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

651 

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

653 Node.__init__(self, line, column) 

654 self.content = content 

655 

656 def _serialize_to(self, write): 

657 write('{') 

658 _serialize_to(self.content, write) 

659 write('}') 

660 

661 

662class FunctionBlock(Node): 

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

664 

665 .. code-block:: text 

666 

667 <name> '(' <arguments> ')' 

668 

669 .. autoattribute:: type 

670 

671 .. attribute:: name 

672 

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

674 

675 .. attribute:: lower_name 

676 

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

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

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

680 

681 .. attribute:: arguments 

682 

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

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

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

686 in the list. 

687 

688 """ 

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

690 type = 'function' 

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

692 

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

694 Node.__init__(self, line, column) 

695 self.name = name 

696 self.lower_name = ascii_lower(name) 

697 self.arguments = arguments 

698 

699 def _serialize_to(self, write): 

700 write(serialize_identifier(self.name)) 

701 write('(') 

702 _serialize_to(self.arguments, write) 

703 function = self 

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

705 eof_in_string = ( 

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

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

708 if eof_in_string: 

709 return 

710 function = function.arguments[-1] 

711 write(')') 

712 

713 

714class Declaration(Node): 

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

716 

717 .. code-block:: text 

718 

719 <name> ':' <value> 

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

721 

722 .. autoattribute:: type 

723 

724 .. attribute:: name 

725 

726 The unescaped name, as a Unicode string. 

727 

728 .. attribute:: lower_name 

729 

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

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

732 This is the value to use when comparing to 

733 a CSS property or descriptor name. 

734 

735 .. code-block:: python 

736 

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

738 

739 .. attribute:: value 

740 

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

742 anything between ``:`` and 

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

744 

745 .. attribute:: important 

746 

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

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

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

750 

751 """ 

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

753 type = 'declaration' 

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

755 

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

757 Node.__init__(self, line, column) 

758 self.name = name 

759 self.lower_name = lower_name 

760 self.value = value 

761 self.important = important 

762 

763 def _serialize_to(self, write): 

764 write(serialize_identifier(self.name)) 

765 write(':') 

766 _serialize_to(self.value, write) 

767 if self.important: 

768 write('!important') 

769 

770 

771class QualifiedRule(Node): 

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

773 

774 .. code-block:: text 

775 

776 <prelude> '{' <content> '}' 

777 

778 The interpretation of qualified rules depend on their context. 

779 At the top-level of a stylesheet 

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

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

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

783 

784 .. autoattribute:: type 

785 

786 .. attribute:: prelude 

787 

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

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

790 

791 .. attribute:: content 

792 

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

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

795 

796 """ 

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

798 type = 'qualified-rule' 

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

800 '… {{ … }}>') 

801 

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

803 Node.__init__(self, line, column) 

804 self.prelude = prelude 

805 self.content = content 

806 

807 def _serialize_to(self, write): 

808 _serialize_to(self.prelude, write) 

809 write('{') 

810 _serialize_to(self.content, write) 

811 write('}') 

812 

813 

814class AtRule(Node): 

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

816 

817 .. code-block:: text 

818 

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

820 @<at_keyword> <prelude> ';' 

821 

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

823 as well as their context. 

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

825 are only allowed in some context, 

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

827 

828 .. autoattribute:: type 

829 

830 .. attribute:: at_keyword 

831 

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

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

834 

835 .. attribute:: lower_at_keyword 

836 

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

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

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

840 

841 .. code-block:: python 

842 

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

844 

845 .. attribute:: prelude 

846 

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

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

849 

850 .. attribute:: content 

851 

852 The rule’s content, if any. 

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

854 for at-rules with a {} block, 

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

856 

857 """ 

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

859 type = 'at-rule' 

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

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

862 

863 def __init__(self, line, column, 

864 at_keyword, lower_at_keyword, prelude, content): 

865 Node.__init__(self, line, column) 

866 self.at_keyword = at_keyword 

867 self.lower_at_keyword = lower_at_keyword 

868 self.prelude = prelude 

869 self.content = content 

870 

871 def _serialize_to(self, write): 

872 write('@') 

873 write(serialize_identifier(self.at_keyword)) 

874 _serialize_to(self.prelude, write) 

875 if self.content is None: 

876 write(';') 

877 else: 

878 write('{') 

879 _serialize_to(self.content, write) 

880 write('}')