| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874 |
- """
- Data structures for the CSS abstract syntax tree.
- """
- from webencodings import ascii_lower
- from .serializer import _serialize_to, serialize_identifier, serialize_name
- class Node:
- """Every node type inherits from this class,
- which is never instantiated directly.
- .. attribute:: type
- Each child class has a :attr:`type` class attribute
- with a unique string value.
- This allows checking for the node type with code like:
- .. code-block:: python
- if node.type == 'whitespace':
- instead of the more verbose:
- .. code-block:: python
- from tinycss2.ast import WhitespaceToken
- if isinstance(node, WhitespaceToken):
- Every node also has these attributes and methods,
- which are not repeated for brevity:
- .. attribute:: source_line
- The line number of the start of the node in the CSS source.
- Starts at 1.
- .. attribute:: source_column
- The column number within :attr:`source_line` of the start of the node
- in the CSS source.
- Starts at 1.
- .. automethod:: serialize
- """
- __slots__ = ['source_line', 'source_column']
- def __init__(self, source_line, source_column):
- self.source_line = source_line
- self.source_column = source_column
- def __repr__(self):
- return self.repr_format.format(self=self)
- def serialize(self):
- """Serialize this node to CSS syntax and return a Unicode string."""
- chunks = []
- self._serialize_to(chunks.append)
- return ''.join(chunks)
- def _serialize_to(self, write):
- """Serialize this node to CSS syntax, writing chunks as Unicode string
- by calling the provided :obj:`write` callback.
- """
- raise NotImplementedError # pragma: no cover
- class ParseError(Node):
- """A syntax error of some sort. May occur anywhere in the tree.
- Syntax errors are not fatal in the parser
- to allow for different error handling behaviors.
- For example, an error in a Selector list makes the whole rule invalid,
- but an error in a Media Query list only replaces one comma-separated query
- with ``not all``.
- .. autoattribute:: type
- .. attribute:: kind
- Machine-readable string indicating the type of error.
- Example: ``'bad-url'``.
- .. attribute:: message
- Human-readable explanation of the error, as a string.
- Could be translated, expanded to include details, etc.
- """
- __slots__ = ['kind', 'message']
- type = 'error'
- repr_format = '<{self.__class__.__name__} {self.kind}>'
- def __init__(self, line, column, kind, message):
- Node.__init__(self, line, column)
- self.kind = kind
- self.message = message
- def _serialize_to(self, write):
- if self.kind == 'bad-string':
- write('"[bad string]\n')
- elif self.kind == 'bad-url':
- write('url([bad url])')
- elif self.kind in ')]}':
- write(self.kind)
- elif self.kind in ('eof-in-string', 'eof-in-url'):
- pass
- else: # pragma: no cover
- raise TypeError('Can not serialize %r' % self)
- class Comment(Node):
- """A CSS comment.
- Comments can be ignored by passing ``skip_comments=True``
- to functions such as :func:`~tinycss2.parse_component_value_list`.
- .. autoattribute:: type
- .. attribute:: value
- The content of the comment, between ``/*`` and ``*/``, as a string.
- """
- __slots__ = ['value']
- type = 'comment'
- repr_format = '<{self.__class__.__name__} {self.value}>'
- def __init__(self, line, column, value):
- Node.__init__(self, line, column)
- self.value = value
- def _serialize_to(self, write):
- write('/*')
- write(self.value)
- write('*/')
- class WhitespaceToken(Node):
- """A :diagram:`whitespace-token`.
- .. autoattribute:: type
- .. attribute:: value
- The whitespace sequence, as a string, as in the original CSS source.
- """
- __slots__ = ['value']
- type = 'whitespace'
- repr_format = '<{self.__class__.__name__}>'
- def __init__(self, line, column, value):
- Node.__init__(self, line, column)
- self.value = value
- def _serialize_to(self, write):
- write(self.value)
- class LiteralToken(Node):
- r"""Token that represents one or more characters as in the CSS source.
- .. autoattribute:: type
- .. attribute:: value
- A string of one to four characters.
- Instances compare equal to their :attr:`value`,
- so that these are equivalent:
- .. code-block:: python
- if node == ';':
- if node.type == 'literal' and node.value == ';':
- This regroups what `the specification`_ defines as separate token types:
- .. _the specification: https://drafts.csswg.org/css-syntax-3/
- * *<colon-token>* ``:``
- * *<semicolon-token>* ``;``
- * *<comma-token>* ``,``
- * *<cdc-token>* ``-->``
- * *<cdo-token>* ``<!--``
- * *<include-match-token>* ``~=``
- * *<dash-match-token>* ``|=``
- * *<prefix-match-token>* ``^=``
- * *<suffix-match-token>* ``$=``
- * *<substring-match-token>* ``*=``
- * *<column-token>* ``||``
- * *<delim-token>* (a single ASCII character not part of any another token)
- """
- __slots__ = ['value']
- type = 'literal'
- repr_format = '<{self.__class__.__name__} {self.value}>'
- def __init__(self, line, column, value):
- Node.__init__(self, line, column)
- self.value = value
- def __eq__(self, other):
- return self.value == other or self is other
- def __ne__(self, other):
- return not self == other
- def _serialize_to(self, write):
- write(self.value)
- class IdentToken(Node):
- """An :diagram:`ident-token`.
- .. autoattribute:: type
- .. attribute:: value
- The unescaped value, as a Unicode string.
- .. attribute:: lower_value
- Same as :attr:`value` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to a CSS keyword.
- """
- __slots__ = ['value', 'lower_value']
- type = 'ident'
- repr_format = '<{self.__class__.__name__} {self.value}>'
- def __init__(self, line, column, value):
- Node.__init__(self, line, column)
- self.value = value
- try:
- self.lower_value = ascii_lower(value)
- except UnicodeEncodeError:
- self.lower_value = value
- def _serialize_to(self, write):
- write(serialize_identifier(self.value))
- class AtKeywordToken(Node):
- """An :diagram:`at-keyword-token`.
- .. code-block:: text
- '@' <value>
- .. autoattribute:: type
- .. attribute:: value
- The unescaped value, as a Unicode string, without the preceding ``@``.
- .. attribute:: lower_value
- Same as :attr:`value` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to a CSS at-keyword.
- .. code-block:: python
- if node.type == 'at-keyword' and node.lower_value == 'import':
- """
- __slots__ = ['value', 'lower_value']
- type = 'at-keyword'
- repr_format = '<{self.__class__.__name__} @{self.value}>'
- def __init__(self, line, column, value):
- Node.__init__(self, line, column)
- self.value = value
- try:
- self.lower_value = ascii_lower(value)
- except UnicodeEncodeError:
- self.lower_value = value
- def _serialize_to(self, write):
- write('@')
- write(serialize_identifier(self.value))
- class HashToken(Node):
- r"""A :diagram:`hash-token`.
- .. code-block:: text
- '#' <value>
- .. autoattribute:: type
- .. attribute:: value
- The unescaped value, as a Unicode string, without the preceding ``#``.
- .. attribute:: is_identifier
- A boolean, true if the CSS source for this token
- was ``#`` followed by a valid identifier.
- (Only such hash tokens are valid ID selectors.)
- """
- __slots__ = ['value', 'is_identifier']
- type = 'hash'
- repr_format = '<{self.__class__.__name__} #{self.value}>'
- def __init__(self, line, column, value, is_identifier):
- Node.__init__(self, line, column)
- self.value = value
- self.is_identifier = is_identifier
- def _serialize_to(self, write):
- write('#')
- if self.is_identifier:
- write(serialize_identifier(self.value))
- else:
- write(serialize_name(self.value))
- class StringToken(Node):
- """A :diagram:`string-token`.
- .. code-block:: text
- '"' <value> '"'
- .. autoattribute:: type
- .. attribute:: value
- The unescaped value, as a Unicode string, without the quotes.
- """
- __slots__ = ['value', 'representation']
- type = 'string'
- repr_format = '<{self.__class__.__name__} {self.representation}>'
- def __init__(self, line, column, value, representation):
- Node.__init__(self, line, column)
- self.value = value
- self.representation = representation
- def _serialize_to(self, write):
- write(self.representation)
- class URLToken(Node):
- """An :diagram:`url-token`.
- .. code-block:: text
- 'url(' <value> ')'
- .. autoattribute:: type
- .. attribute:: value
- The unescaped URL, as a Unicode string, without the ``url(`` and ``)``
- markers.
- """
- __slots__ = ['value', 'representation']
- type = 'url'
- repr_format = '<{self.__class__.__name__} {self.representation}>'
- def __init__(self, line, column, value, representation):
- Node.__init__(self, line, column)
- self.value = value
- self.representation = representation
- def _serialize_to(self, write):
- write(self.representation)
- class UnicodeRangeToken(Node):
- """A :diagram:`unicode-range-token`.
- .. autoattribute:: type
- .. attribute:: start
- The start of the range, as an integer between 0 and 1114111.
- .. attribute:: end
- The end of the range, as an integer between 0 and 1114111.
- Same as :attr:`start` if the source only specified one value.
- """
- __slots__ = ['start', 'end']
- type = 'unicode-range'
- repr_format = '<{self.__class__.__name__} {self.start} {self.end}>'
- def __init__(self, line, column, start, end):
- Node.__init__(self, line, column)
- self.start = start
- self.end = end
- def _serialize_to(self, write):
- if self.end == self.start:
- write('U+%X' % self.start)
- else:
- write('U+%X-%X' % (self.start, self.end))
- class NumberToken(Node):
- """A :diagram:`number-token`.
- .. autoattribute:: type
- .. attribute:: value
- The numeric value as a :class:`float`.
- .. attribute:: int_value
- The numeric value as an :class:`int`
- if :attr:`is_integer` is true, :obj:`None` otherwise.
- .. attribute:: is_integer
- Whether the token was syntactically an integer, as a boolean.
- .. attribute:: representation
- The CSS representation of the value, as a Unicode string.
- """
- __slots__ = ['value', 'int_value', 'is_integer', 'representation']
- type = 'number'
- repr_format = '<{self.__class__.__name__} {self.representation}>'
- def __init__(self, line, column, value, int_value, representation):
- Node.__init__(self, line, column)
- self.value = value
- self.int_value = int_value
- self.is_integer = int_value is not None
- self.representation = representation
- def _serialize_to(self, write):
- write(self.representation)
- class PercentageToken(Node):
- """A :diagram:`percentage-token`.
- .. code-block:: text
- <representation> '%'
- .. autoattribute:: type
- .. attribute:: value
- The value numeric as a :class:`float`.
- .. attribute:: int_value
- The numeric value as an :class:`int`
- if the token was syntactically an integer,
- or :obj:`None`.
- .. attribute:: is_integer
- Whether the token’s value was syntactically an integer, as a boolean.
- .. attribute:: representation
- The CSS representation of the value without the unit,
- as a Unicode string.
- """
- __slots__ = ['value', 'int_value', 'is_integer', 'representation']
- type = 'percentage'
- repr_format = '<{self.__class__.__name__} {self.representation}%>'
- def __init__(self, line, column, value, int_value, representation):
- Node.__init__(self, line, column)
- self.value = value
- self.int_value = int_value
- self.is_integer = int_value is not None
- self.representation = representation
- def _serialize_to(self, write):
- write(self.representation)
- write('%')
- class DimensionToken(Node):
- """A :diagram:`dimension-token`.
- .. code-block:: text
- <representation> <unit>
- .. autoattribute:: type
- .. attribute:: value
- The value numeric as a :class:`float`.
- .. attribute:: int_value
- The numeric value as an :class:`int`
- if the token was syntactically an integer,
- or :obj:`None`.
- .. attribute:: is_integer
- Whether the token’s value was syntactically an integer, as a boolean.
- .. attribute:: representation
- The CSS representation of the value without the unit,
- as a Unicode string.
- .. attribute:: unit
- The unescaped unit, as a Unicode string.
- .. attribute:: lower_unit
- Same as :attr:`unit` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to a CSS unit.
- .. code-block:: python
- if node.type == 'dimension' and node.lower_unit == 'px':
- """
- __slots__ = ['value', 'int_value', 'is_integer', 'representation',
- 'unit', 'lower_unit']
- type = 'dimension'
- repr_format = ('<{self.__class__.__name__} '
- '{self.representation}{self.unit}>')
- def __init__(self, line, column, value, int_value, representation, unit):
- Node.__init__(self, line, column)
- self.value = value
- self.int_value = int_value
- self.is_integer = int_value is not None
- self.representation = representation
- self.unit = unit
- self.lower_unit = ascii_lower(unit)
- def _serialize_to(self, write):
- write(self.representation)
- # Disambiguate with scientific notation
- unit = self.unit
- if unit in ('e', 'E') or unit.startswith(('e-', 'E-')):
- write('\\65 ')
- write(serialize_name(unit[1:]))
- else:
- write(serialize_identifier(unit))
- class ParenthesesBlock(Node):
- """A :diagram:`()-block`.
- .. code-block:: text
- '(' <content> ')'
- .. autoattribute:: type
- .. attribute:: content
- The content of the block, as list of :term:`component values`.
- The ``(`` and ``)`` markers themselves are not represented in the list.
- """
- __slots__ = ['content']
- type = '() block'
- repr_format = '<{self.__class__.__name__} ( … )>'
- def __init__(self, line, column, content):
- Node.__init__(self, line, column)
- self.content = content
- def _serialize_to(self, write):
- write('(')
- _serialize_to(self.content, write)
- write(')')
- class SquareBracketsBlock(Node):
- """A :diagram:`[]-block`.
- .. code-block:: text
- '[' <content> ']'
- .. autoattribute:: type
- .. attribute:: content
- The content of the block, as list of :term:`component values`.
- The ``[`` and ``]`` markers themselves are not represented in the list.
- """
- __slots__ = ['content']
- type = '[] block'
- repr_format = '<{self.__class__.__name__} [ … ]>'
- def __init__(self, line, column, content):
- Node.__init__(self, line, column)
- self.content = content
- def _serialize_to(self, write):
- write('[')
- _serialize_to(self.content, write)
- write(']')
- class CurlyBracketsBlock(Node):
- """A :diagram:`{}-block`.
- .. code-block:: text
- '{' <content> '}'
- .. autoattribute:: type
- .. attribute:: content
- The content of the block, as list of :term:`component values`.
- The ``[`` and ``]`` markers themselves are not represented in the list.
- """
- __slots__ = ['content']
- type = '{} block'
- repr_format = '<{self.__class__.__name__} {{ … }}>'
- def __init__(self, line, column, content):
- Node.__init__(self, line, column)
- self.content = content
- def _serialize_to(self, write):
- write('{')
- _serialize_to(self.content, write)
- write('}')
- class FunctionBlock(Node):
- """A :diagram:`function-block`.
- .. code-block:: text
- <name> '(' <arguments> ')'
- .. autoattribute:: type
- .. attribute:: name
- The unescaped name of the function, as a Unicode string.
- .. attribute:: lower_name
- Same as :attr:`name` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to a CSS function name.
- .. attribute:: arguments
- The arguments of the function, as list of :term:`component values`.
- The ``(`` and ``)`` markers themselves are not represented in the list.
- Commas are not special, but represented as :obj:`LiteralToken` objects
- in the list.
- """
- __slots__ = ['name', 'lower_name', 'arguments']
- type = 'function'
- repr_format = '<{self.__class__.__name__} {self.name}( … )>'
- def __init__(self, line, column, name, arguments):
- Node.__init__(self, line, column)
- self.name = name
- self.lower_name = ascii_lower(name)
- self.arguments = arguments
- def _serialize_to(self, write):
- write(serialize_identifier(self.name))
- write('(')
- _serialize_to(self.arguments, write)
- function = self
- while isinstance(function, FunctionBlock) and function.arguments:
- eof_in_string = (
- isinstance(function.arguments[-1], ParseError) and
- function.arguments[-1].kind == 'eof-in-string')
- if eof_in_string:
- return
- function = function.arguments[-1]
- write(')')
- class Declaration(Node):
- """A (property or descriptor) :diagram:`declaration`.
- .. code-block:: text
- <name> ':' <value>
- <name> ':' <value> '!important'
- .. autoattribute:: type
- .. attribute:: name
- The unescaped name, as a Unicode string.
- .. attribute:: lower_name
- Same as :attr:`name` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to
- a CSS property or descriptor name.
- .. code-block:: python
- if node.type == 'declaration' and node.lower_name == 'color':
- .. attribute:: value
- The declaration value as a list of :term:`component values`:
- anything between ``:`` and
- the end of the declaration, or ``!important``.
- .. attribute:: important
- A boolean, true if the declaration had an ``!important`` marker.
- It is up to the consumer to reject declarations that do not accept
- this flag, such as non-property descriptor declarations.
- """
- __slots__ = ['name', 'lower_name', 'value', 'important']
- type = 'declaration'
- repr_format = '<{self.__class__.__name__} {self.name}: …>'
- def __init__(self, line, column, name, lower_name, value, important):
- Node.__init__(self, line, column)
- self.name = name
- self.lower_name = lower_name
- self.value = value
- self.important = important
- def _serialize_to(self, write):
- write(serialize_identifier(self.name))
- write(':')
- _serialize_to(self.value, write)
- if self.important:
- write('!important')
- class QualifiedRule(Node):
- """A :diagram:`qualified rule`.
- .. code-block:: text
- <prelude> '{' <content> '}'
- The interpretation of qualified rules depend on their context.
- At the top-level of a stylesheet
- or in a conditional rule such as ``@media``,
- they are **style rules** where the :attr:`prelude` is Selectors list
- and the :attr:`content` is a list of property declarations.
- .. autoattribute:: type
- .. attribute:: prelude
- The rule’s prelude, the part before the {} block,
- as a list of :term:`component values`.
- .. attribute:: content
- The rule’s content, the part inside the {} block,
- as a list of :term:`component values`.
- """
- __slots__ = ['prelude', 'content']
- type = 'qualified-rule'
- repr_format = ('<{self.__class__.__name__} '
- '… {{ … }}>')
- def __init__(self, line, column, prelude, content):
- Node.__init__(self, line, column)
- self.prelude = prelude
- self.content = content
- def _serialize_to(self, write):
- _serialize_to(self.prelude, write)
- write('{')
- _serialize_to(self.content, write)
- write('}')
- class AtRule(Node):
- """An :diagram:`at-rule`.
- .. code-block:: text
- @<at_keyword> <prelude> '{' <content> '}'
- @<at_keyword> <prelude> ';'
- The interpretation of at-rules depend on their at-keyword
- as well as their context.
- Most types of at-rules (ie. at-keyword values)
- are only allowed in some context,
- and must either end with a {} block or a semicolon.
- .. autoattribute:: type
- .. attribute:: at_keyword
- The unescaped value of the rule’s at-keyword,
- without the ``@`` symbol, as a Unicode string.
- .. attribute:: lower_at_keyword
- Same as :attr:`at_keyword` but normalized to *ASCII lower case*,
- see :func:`~webencodings.ascii_lower`.
- This is the value to use when comparing to a CSS at-keyword.
- .. code-block:: python
- if node.type == 'at-rule' and node.lower_at_keyword == 'import':
- .. attribute:: prelude
- The rule’s prelude, the part before the {} block or semicolon,
- as a list of :term:`component values`.
- .. attribute:: content
- The rule’s content, if any.
- The block’s content as a list of :term:`component values`
- for at-rules with a {} block,
- or :obj:`None` for at-rules ending with a semicolon.
- """
- __slots__ = ['at_keyword', 'lower_at_keyword', 'prelude', 'content']
- type = 'at-rule'
- repr_format = ('<{self.__class__.__name__} '
- '@{self.at_keyword} … {{ … }}>')
- def __init__(self, line, column,
- at_keyword, lower_at_keyword, prelude, content):
- Node.__init__(self, line, column)
- self.at_keyword = at_keyword
- self.lower_at_keyword = lower_at_keyword
- self.prelude = prelude
- self.content = content
- def _serialize_to(self, write):
- write('@')
- write(serialize_identifier(self.at_keyword))
- _serialize_to(self.prelude, write)
- if self.content is None:
- write(';')
- else:
- write('{')
- _serialize_to(self.content, write)
- write('}')
|