ast.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. """
  2. Data structures for the CSS abstract syntax tree.
  3. """
  4. from webencodings import ascii_lower
  5. from .serializer import _serialize_to, serialize_identifier, serialize_name
  6. class Node:
  7. """Every node type inherits from this class,
  8. which is never instantiated directly.
  9. .. attribute:: type
  10. Each child class has a :attr:`type` class attribute
  11. with a unique string value.
  12. This allows checking for the node type with code like:
  13. .. code-block:: python
  14. if node.type == 'whitespace':
  15. instead of the more verbose:
  16. .. code-block:: python
  17. from tinycss2.ast import WhitespaceToken
  18. if isinstance(node, WhitespaceToken):
  19. Every node also has these attributes and methods,
  20. which are not repeated for brevity:
  21. .. attribute:: source_line
  22. The line number of the start of the node in the CSS source.
  23. Starts at 1.
  24. .. attribute:: source_column
  25. The column number within :attr:`source_line` of the start of the node
  26. in the CSS source.
  27. Starts at 1.
  28. .. automethod:: serialize
  29. """
  30. __slots__ = ['source_line', 'source_column']
  31. def __init__(self, source_line, source_column):
  32. self.source_line = source_line
  33. self.source_column = source_column
  34. def __repr__(self):
  35. return self.repr_format.format(self=self)
  36. def serialize(self):
  37. """Serialize this node to CSS syntax and return a Unicode string."""
  38. chunks = []
  39. self._serialize_to(chunks.append)
  40. return ''.join(chunks)
  41. def _serialize_to(self, write):
  42. """Serialize this node to CSS syntax, writing chunks as Unicode string
  43. by calling the provided :obj:`write` callback.
  44. """
  45. raise NotImplementedError # pragma: no cover
  46. class ParseError(Node):
  47. """A syntax error of some sort. May occur anywhere in the tree.
  48. Syntax errors are not fatal in the parser
  49. to allow for different error handling behaviors.
  50. For example, an error in a Selector list makes the whole rule invalid,
  51. but an error in a Media Query list only replaces one comma-separated query
  52. with ``not all``.
  53. .. autoattribute:: type
  54. .. attribute:: kind
  55. Machine-readable string indicating the type of error.
  56. Example: ``'bad-url'``.
  57. .. attribute:: message
  58. Human-readable explanation of the error, as a string.
  59. Could be translated, expanded to include details, etc.
  60. """
  61. __slots__ = ['kind', 'message']
  62. type = 'error'
  63. repr_format = '<{self.__class__.__name__} {self.kind}>'
  64. def __init__(self, line, column, kind, message):
  65. Node.__init__(self, line, column)
  66. self.kind = kind
  67. self.message = message
  68. def _serialize_to(self, write):
  69. if self.kind == 'bad-string':
  70. write('"[bad string]\n')
  71. elif self.kind == 'bad-url':
  72. write('url([bad url])')
  73. elif self.kind in ')]}':
  74. write(self.kind)
  75. elif self.kind in ('eof-in-string', 'eof-in-url'):
  76. pass
  77. else: # pragma: no cover
  78. raise TypeError('Can not serialize %r' % self)
  79. class Comment(Node):
  80. """A CSS comment.
  81. Comments can be ignored by passing ``skip_comments=True``
  82. to functions such as :func:`~tinycss2.parse_component_value_list`.
  83. .. autoattribute:: type
  84. .. attribute:: value
  85. The content of the comment, between ``/*`` and ``*/``, as a string.
  86. """
  87. __slots__ = ['value']
  88. type = 'comment'
  89. repr_format = '<{self.__class__.__name__} {self.value}>'
  90. def __init__(self, line, column, value):
  91. Node.__init__(self, line, column)
  92. self.value = value
  93. def _serialize_to(self, write):
  94. write('/*')
  95. write(self.value)
  96. write('*/')
  97. class WhitespaceToken(Node):
  98. """A :diagram:`whitespace-token`.
  99. .. autoattribute:: type
  100. .. attribute:: value
  101. The whitespace sequence, as a string, as in the original CSS source.
  102. """
  103. __slots__ = ['value']
  104. type = 'whitespace'
  105. repr_format = '<{self.__class__.__name__}>'
  106. def __init__(self, line, column, value):
  107. Node.__init__(self, line, column)
  108. self.value = value
  109. def _serialize_to(self, write):
  110. write(self.value)
  111. class LiteralToken(Node):
  112. r"""Token that represents one or more characters as in the CSS source.
  113. .. autoattribute:: type
  114. .. attribute:: value
  115. A string of one to four characters.
  116. Instances compare equal to their :attr:`value`,
  117. so that these are equivalent:
  118. .. code-block:: python
  119. if node == ';':
  120. if node.type == 'literal' and node.value == ';':
  121. This regroups what `the specification`_ defines as separate token types:
  122. .. _the specification: https://drafts.csswg.org/css-syntax-3/
  123. * *<colon-token>* ``:``
  124. * *<semicolon-token>* ``;``
  125. * *<comma-token>* ``,``
  126. * *<cdc-token>* ``-->``
  127. * *<cdo-token>* ``<!--``
  128. * *<include-match-token>* ``~=``
  129. * *<dash-match-token>* ``|=``
  130. * *<prefix-match-token>* ``^=``
  131. * *<suffix-match-token>* ``$=``
  132. * *<substring-match-token>* ``*=``
  133. * *<column-token>* ``||``
  134. * *<delim-token>* (a single ASCII character not part of any another token)
  135. """
  136. __slots__ = ['value']
  137. type = 'literal'
  138. repr_format = '<{self.__class__.__name__} {self.value}>'
  139. def __init__(self, line, column, value):
  140. Node.__init__(self, line, column)
  141. self.value = value
  142. def __eq__(self, other):
  143. return self.value == other or self is other
  144. def __ne__(self, other):
  145. return not self == other
  146. def _serialize_to(self, write):
  147. write(self.value)
  148. class IdentToken(Node):
  149. """An :diagram:`ident-token`.
  150. .. autoattribute:: type
  151. .. attribute:: value
  152. The unescaped value, as a Unicode string.
  153. .. attribute:: lower_value
  154. Same as :attr:`value` but normalized to *ASCII lower case*,
  155. see :func:`~webencodings.ascii_lower`.
  156. This is the value to use when comparing to a CSS keyword.
  157. """
  158. __slots__ = ['value', 'lower_value']
  159. type = 'ident'
  160. repr_format = '<{self.__class__.__name__} {self.value}>'
  161. def __init__(self, line, column, value):
  162. Node.__init__(self, line, column)
  163. self.value = value
  164. try:
  165. self.lower_value = ascii_lower(value)
  166. except UnicodeEncodeError:
  167. self.lower_value = value
  168. def _serialize_to(self, write):
  169. write(serialize_identifier(self.value))
  170. class AtKeywordToken(Node):
  171. """An :diagram:`at-keyword-token`.
  172. .. code-block:: text
  173. '@' <value>
  174. .. autoattribute:: type
  175. .. attribute:: value
  176. The unescaped value, as a Unicode string, without the preceding ``@``.
  177. .. attribute:: lower_value
  178. Same as :attr:`value` but normalized to *ASCII lower case*,
  179. see :func:`~webencodings.ascii_lower`.
  180. This is the value to use when comparing to a CSS at-keyword.
  181. .. code-block:: python
  182. if node.type == 'at-keyword' and node.lower_value == 'import':
  183. """
  184. __slots__ = ['value', 'lower_value']
  185. type = 'at-keyword'
  186. repr_format = '<{self.__class__.__name__} @{self.value}>'
  187. def __init__(self, line, column, value):
  188. Node.__init__(self, line, column)
  189. self.value = value
  190. try:
  191. self.lower_value = ascii_lower(value)
  192. except UnicodeEncodeError:
  193. self.lower_value = value
  194. def _serialize_to(self, write):
  195. write('@')
  196. write(serialize_identifier(self.value))
  197. class HashToken(Node):
  198. r"""A :diagram:`hash-token`.
  199. .. code-block:: text
  200. '#' <value>
  201. .. autoattribute:: type
  202. .. attribute:: value
  203. The unescaped value, as a Unicode string, without the preceding ``#``.
  204. .. attribute:: is_identifier
  205. A boolean, true if the CSS source for this token
  206. was ``#`` followed by a valid identifier.
  207. (Only such hash tokens are valid ID selectors.)
  208. """
  209. __slots__ = ['value', 'is_identifier']
  210. type = 'hash'
  211. repr_format = '<{self.__class__.__name__} #{self.value}>'
  212. def __init__(self, line, column, value, is_identifier):
  213. Node.__init__(self, line, column)
  214. self.value = value
  215. self.is_identifier = is_identifier
  216. def _serialize_to(self, write):
  217. write('#')
  218. if self.is_identifier:
  219. write(serialize_identifier(self.value))
  220. else:
  221. write(serialize_name(self.value))
  222. class StringToken(Node):
  223. """A :diagram:`string-token`.
  224. .. code-block:: text
  225. '"' <value> '"'
  226. .. autoattribute:: type
  227. .. attribute:: value
  228. The unescaped value, as a Unicode string, without the quotes.
  229. """
  230. __slots__ = ['value', 'representation']
  231. type = 'string'
  232. repr_format = '<{self.__class__.__name__} {self.representation}>'
  233. def __init__(self, line, column, value, representation):
  234. Node.__init__(self, line, column)
  235. self.value = value
  236. self.representation = representation
  237. def _serialize_to(self, write):
  238. write(self.representation)
  239. class URLToken(Node):
  240. """An :diagram:`url-token`.
  241. .. code-block:: text
  242. 'url(' <value> ')'
  243. .. autoattribute:: type
  244. .. attribute:: value
  245. The unescaped URL, as a Unicode string, without the ``url(`` and ``)``
  246. markers.
  247. """
  248. __slots__ = ['value', 'representation']
  249. type = 'url'
  250. repr_format = '<{self.__class__.__name__} {self.representation}>'
  251. def __init__(self, line, column, value, representation):
  252. Node.__init__(self, line, column)
  253. self.value = value
  254. self.representation = representation
  255. def _serialize_to(self, write):
  256. write(self.representation)
  257. class UnicodeRangeToken(Node):
  258. """A :diagram:`unicode-range-token`.
  259. .. autoattribute:: type
  260. .. attribute:: start
  261. The start of the range, as an integer between 0 and 1114111.
  262. .. attribute:: end
  263. The end of the range, as an integer between 0 and 1114111.
  264. Same as :attr:`start` if the source only specified one value.
  265. """
  266. __slots__ = ['start', 'end']
  267. type = 'unicode-range'
  268. repr_format = '<{self.__class__.__name__} {self.start} {self.end}>'
  269. def __init__(self, line, column, start, end):
  270. Node.__init__(self, line, column)
  271. self.start = start
  272. self.end = end
  273. def _serialize_to(self, write):
  274. if self.end == self.start:
  275. write('U+%X' % self.start)
  276. else:
  277. write('U+%X-%X' % (self.start, self.end))
  278. class NumberToken(Node):
  279. """A :diagram:`number-token`.
  280. .. autoattribute:: type
  281. .. attribute:: value
  282. The numeric value as a :class:`float`.
  283. .. attribute:: int_value
  284. The numeric value as an :class:`int`
  285. if :attr:`is_integer` is true, :obj:`None` otherwise.
  286. .. attribute:: is_integer
  287. Whether the token was syntactically an integer, as a boolean.
  288. .. attribute:: representation
  289. The CSS representation of the value, as a Unicode string.
  290. """
  291. __slots__ = ['value', 'int_value', 'is_integer', 'representation']
  292. type = 'number'
  293. repr_format = '<{self.__class__.__name__} {self.representation}>'
  294. def __init__(self, line, column, value, int_value, representation):
  295. Node.__init__(self, line, column)
  296. self.value = value
  297. self.int_value = int_value
  298. self.is_integer = int_value is not None
  299. self.representation = representation
  300. def _serialize_to(self, write):
  301. write(self.representation)
  302. class PercentageToken(Node):
  303. """A :diagram:`percentage-token`.
  304. .. code-block:: text
  305. <representation> '%'
  306. .. autoattribute:: type
  307. .. attribute:: value
  308. The value numeric as a :class:`float`.
  309. .. attribute:: int_value
  310. The numeric value as an :class:`int`
  311. if the token was syntactically an integer,
  312. or :obj:`None`.
  313. .. attribute:: is_integer
  314. Whether the token’s value was syntactically an integer, as a boolean.
  315. .. attribute:: representation
  316. The CSS representation of the value without the unit,
  317. as a Unicode string.
  318. """
  319. __slots__ = ['value', 'int_value', 'is_integer', 'representation']
  320. type = 'percentage'
  321. repr_format = '<{self.__class__.__name__} {self.representation}%>'
  322. def __init__(self, line, column, value, int_value, representation):
  323. Node.__init__(self, line, column)
  324. self.value = value
  325. self.int_value = int_value
  326. self.is_integer = int_value is not None
  327. self.representation = representation
  328. def _serialize_to(self, write):
  329. write(self.representation)
  330. write('%')
  331. class DimensionToken(Node):
  332. """A :diagram:`dimension-token`.
  333. .. code-block:: text
  334. <representation> <unit>
  335. .. autoattribute:: type
  336. .. attribute:: value
  337. The value numeric as a :class:`float`.
  338. .. attribute:: int_value
  339. The numeric value as an :class:`int`
  340. if the token was syntactically an integer,
  341. or :obj:`None`.
  342. .. attribute:: is_integer
  343. Whether the token’s value was syntactically an integer, as a boolean.
  344. .. attribute:: representation
  345. The CSS representation of the value without the unit,
  346. as a Unicode string.
  347. .. attribute:: unit
  348. The unescaped unit, as a Unicode string.
  349. .. attribute:: lower_unit
  350. Same as :attr:`unit` but normalized to *ASCII lower case*,
  351. see :func:`~webencodings.ascii_lower`.
  352. This is the value to use when comparing to a CSS unit.
  353. .. code-block:: python
  354. if node.type == 'dimension' and node.lower_unit == 'px':
  355. """
  356. __slots__ = ['value', 'int_value', 'is_integer', 'representation',
  357. 'unit', 'lower_unit']
  358. type = 'dimension'
  359. repr_format = ('<{self.__class__.__name__} '
  360. '{self.representation}{self.unit}>')
  361. def __init__(self, line, column, value, int_value, representation, unit):
  362. Node.__init__(self, line, column)
  363. self.value = value
  364. self.int_value = int_value
  365. self.is_integer = int_value is not None
  366. self.representation = representation
  367. self.unit = unit
  368. self.lower_unit = ascii_lower(unit)
  369. def _serialize_to(self, write):
  370. write(self.representation)
  371. # Disambiguate with scientific notation
  372. unit = self.unit
  373. if unit in ('e', 'E') or unit.startswith(('e-', 'E-')):
  374. write('\\65 ')
  375. write(serialize_name(unit[1:]))
  376. else:
  377. write(serialize_identifier(unit))
  378. class ParenthesesBlock(Node):
  379. """A :diagram:`()-block`.
  380. .. code-block:: text
  381. '(' <content> ')'
  382. .. autoattribute:: type
  383. .. attribute:: content
  384. The content of the block, as list of :term:`component values`.
  385. The ``(`` and ``)`` markers themselves are not represented in the list.
  386. """
  387. __slots__ = ['content']
  388. type = '() block'
  389. repr_format = '<{self.__class__.__name__} ( … )>'
  390. def __init__(self, line, column, content):
  391. Node.__init__(self, line, column)
  392. self.content = content
  393. def _serialize_to(self, write):
  394. write('(')
  395. _serialize_to(self.content, write)
  396. write(')')
  397. class SquareBracketsBlock(Node):
  398. """A :diagram:`[]-block`.
  399. .. code-block:: text
  400. '[' <content> ']'
  401. .. autoattribute:: type
  402. .. attribute:: content
  403. The content of the block, as list of :term:`component values`.
  404. The ``[`` and ``]`` markers themselves are not represented in the list.
  405. """
  406. __slots__ = ['content']
  407. type = '[] block'
  408. repr_format = '<{self.__class__.__name__} [ … ]>'
  409. def __init__(self, line, column, content):
  410. Node.__init__(self, line, column)
  411. self.content = content
  412. def _serialize_to(self, write):
  413. write('[')
  414. _serialize_to(self.content, write)
  415. write(']')
  416. class CurlyBracketsBlock(Node):
  417. """A :diagram:`{}-block`.
  418. .. code-block:: text
  419. '{' <content> '}'
  420. .. autoattribute:: type
  421. .. attribute:: content
  422. The content of the block, as list of :term:`component values`.
  423. The ``[`` and ``]`` markers themselves are not represented in the list.
  424. """
  425. __slots__ = ['content']
  426. type = '{} block'
  427. repr_format = '<{self.__class__.__name__} {{ … }}>'
  428. def __init__(self, line, column, content):
  429. Node.__init__(self, line, column)
  430. self.content = content
  431. def _serialize_to(self, write):
  432. write('{')
  433. _serialize_to(self.content, write)
  434. write('}')
  435. class FunctionBlock(Node):
  436. """A :diagram:`function-block`.
  437. .. code-block:: text
  438. <name> '(' <arguments> ')'
  439. .. autoattribute:: type
  440. .. attribute:: name
  441. The unescaped name of the function, as a Unicode string.
  442. .. attribute:: lower_name
  443. Same as :attr:`name` but normalized to *ASCII lower case*,
  444. see :func:`~webencodings.ascii_lower`.
  445. This is the value to use when comparing to a CSS function name.
  446. .. attribute:: arguments
  447. The arguments of the function, as list of :term:`component values`.
  448. The ``(`` and ``)`` markers themselves are not represented in the list.
  449. Commas are not special, but represented as :obj:`LiteralToken` objects
  450. in the list.
  451. """
  452. __slots__ = ['name', 'lower_name', 'arguments']
  453. type = 'function'
  454. repr_format = '<{self.__class__.__name__} {self.name}( … )>'
  455. def __init__(self, line, column, name, arguments):
  456. Node.__init__(self, line, column)
  457. self.name = name
  458. self.lower_name = ascii_lower(name)
  459. self.arguments = arguments
  460. def _serialize_to(self, write):
  461. write(serialize_identifier(self.name))
  462. write('(')
  463. _serialize_to(self.arguments, write)
  464. function = self
  465. while isinstance(function, FunctionBlock) and function.arguments:
  466. eof_in_string = (
  467. isinstance(function.arguments[-1], ParseError) and
  468. function.arguments[-1].kind == 'eof-in-string')
  469. if eof_in_string:
  470. return
  471. function = function.arguments[-1]
  472. write(')')
  473. class Declaration(Node):
  474. """A (property or descriptor) :diagram:`declaration`.
  475. .. code-block:: text
  476. <name> ':' <value>
  477. <name> ':' <value> '!important'
  478. .. autoattribute:: type
  479. .. attribute:: name
  480. The unescaped name, as a Unicode string.
  481. .. attribute:: lower_name
  482. Same as :attr:`name` but normalized to *ASCII lower case*,
  483. see :func:`~webencodings.ascii_lower`.
  484. This is the value to use when comparing to
  485. a CSS property or descriptor name.
  486. .. code-block:: python
  487. if node.type == 'declaration' and node.lower_name == 'color':
  488. .. attribute:: value
  489. The declaration value as a list of :term:`component values`:
  490. anything between ``:`` and
  491. the end of the declaration, or ``!important``.
  492. .. attribute:: important
  493. A boolean, true if the declaration had an ``!important`` marker.
  494. It is up to the consumer to reject declarations that do not accept
  495. this flag, such as non-property descriptor declarations.
  496. """
  497. __slots__ = ['name', 'lower_name', 'value', 'important']
  498. type = 'declaration'
  499. repr_format = '<{self.__class__.__name__} {self.name}: …>'
  500. def __init__(self, line, column, name, lower_name, value, important):
  501. Node.__init__(self, line, column)
  502. self.name = name
  503. self.lower_name = lower_name
  504. self.value = value
  505. self.important = important
  506. def _serialize_to(self, write):
  507. write(serialize_identifier(self.name))
  508. write(':')
  509. _serialize_to(self.value, write)
  510. if self.important:
  511. write('!important')
  512. class QualifiedRule(Node):
  513. """A :diagram:`qualified rule`.
  514. .. code-block:: text
  515. <prelude> '{' <content> '}'
  516. The interpretation of qualified rules depend on their context.
  517. At the top-level of a stylesheet
  518. or in a conditional rule such as ``@media``,
  519. they are **style rules** where the :attr:`prelude` is Selectors list
  520. and the :attr:`content` is a list of property declarations.
  521. .. autoattribute:: type
  522. .. attribute:: prelude
  523. The rule’s prelude, the part before the {} block,
  524. as a list of :term:`component values`.
  525. .. attribute:: content
  526. The rule’s content, the part inside the {} block,
  527. as a list of :term:`component values`.
  528. """
  529. __slots__ = ['prelude', 'content']
  530. type = 'qualified-rule'
  531. repr_format = ('<{self.__class__.__name__} '
  532. '… {{ … }}>')
  533. def __init__(self, line, column, prelude, content):
  534. Node.__init__(self, line, column)
  535. self.prelude = prelude
  536. self.content = content
  537. def _serialize_to(self, write):
  538. _serialize_to(self.prelude, write)
  539. write('{')
  540. _serialize_to(self.content, write)
  541. write('}')
  542. class AtRule(Node):
  543. """An :diagram:`at-rule`.
  544. .. code-block:: text
  545. @<at_keyword> <prelude> '{' <content> '}'
  546. @<at_keyword> <prelude> ';'
  547. The interpretation of at-rules depend on their at-keyword
  548. as well as their context.
  549. Most types of at-rules (ie. at-keyword values)
  550. are only allowed in some context,
  551. and must either end with a {} block or a semicolon.
  552. .. autoattribute:: type
  553. .. attribute:: at_keyword
  554. The unescaped value of the rule’s at-keyword,
  555. without the ``@`` symbol, as a Unicode string.
  556. .. attribute:: lower_at_keyword
  557. Same as :attr:`at_keyword` but normalized to *ASCII lower case*,
  558. see :func:`~webencodings.ascii_lower`.
  559. This is the value to use when comparing to a CSS at-keyword.
  560. .. code-block:: python
  561. if node.type == 'at-rule' and node.lower_at_keyword == 'import':
  562. .. attribute:: prelude
  563. The rule’s prelude, the part before the {} block or semicolon,
  564. as a list of :term:`component values`.
  565. .. attribute:: content
  566. The rule’s content, if any.
  567. The block’s content as a list of :term:`component values`
  568. for at-rules with a {} block,
  569. or :obj:`None` for at-rules ending with a semicolon.
  570. """
  571. __slots__ = ['at_keyword', 'lower_at_keyword', 'prelude', 'content']
  572. type = 'at-rule'
  573. repr_format = ('<{self.__class__.__name__} '
  574. '@{self.at_keyword} … {{ … }}>')
  575. def __init__(self, line, column,
  576. at_keyword, lower_at_keyword, prelude, content):
  577. Node.__init__(self, line, column)
  578. self.at_keyword = at_keyword
  579. self.lower_at_keyword = lower_at_keyword
  580. self.prelude = prelude
  581. self.content = content
  582. def _serialize_to(self, write):
  583. write('@')
  584. write(serialize_identifier(self.at_keyword))
  585. _serialize_to(self.prelude, write)
  586. if self.content is None:
  587. write(';')
  588. else:
  589. write('{')
  590. _serialize_to(self.content, write)
  591. write('}')