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
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
1"""
3Data structures for the CSS abstract syntax tree.
5"""
8from webencodings import ascii_lower
10from .serializer import _serialize_to, serialize_identifier, serialize_name
13class Node:
14 """Every node type inherits from this class,
15 which is never instantiated directly.
17 .. attribute:: type
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:
23 .. code-block:: python
25 if node.type == 'whitespace':
27 instead of the more verbose:
29 .. code-block:: python
31 from tinycss2.ast import WhitespaceToken
32 if isinstance(node, WhitespaceToken):
34 Every node also has these attributes and methods,
35 which are not repeated for brevity:
37 .. attribute:: source_line
39 The line number of the start of the node in the CSS source.
40 Starts at 1.
42 .. attribute:: source_column
44 The column number within :attr:`source_line` of the start of the node
45 in the CSS source.
46 Starts at 1.
48 .. automethod:: serialize
50 """
51 __slots__ = ['source_column', 'source_line']
53 def __init__(self, source_line, source_column):
54 self.source_line = source_line
55 self.source_column = source_column
57 def __repr__(self):
58 return self.repr_format.format(self=self)
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)
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.
70 """
71 raise NotImplementedError # pragma: no cover
74class ParseError(Node):
75 """A syntax error of some sort. May occur anywhere in the tree.
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``.
83 .. autoattribute:: type
85 .. attribute:: kind
87 Machine-readable string indicating the type of error.
88 Example: ``'bad-url'``.
90 .. attribute:: message
92 Human-readable explanation of the error, as a string.
93 Could be translated, expanded to include details, etc.
95 """
96 __slots__ = ['kind', 'message']
97 type = 'error'
98 repr_format = '<{self.__class__.__name__} {self.kind}>'
100 def __init__(self, line, column, kind, message):
101 Node.__init__(self, line, column)
102 self.kind = kind
103 self.message = message
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)
118class Comment(Node):
119 """A CSS comment.
121 Comments can be ignored by passing ``skip_comments=True``
122 to functions such as :func:`~tinycss2.parse_component_value_list`.
124 .. autoattribute:: type
126 .. attribute:: value
128 The content of the comment, between ``/*`` and ``*/``, as a string.
130 """
131 __slots__ = ['value']
132 type = 'comment'
133 repr_format = '<{self.__class__.__name__} {self.value}>'
135 def __init__(self, line, column, value):
136 Node.__init__(self, line, column)
137 self.value = value
139 def _serialize_to(self, write):
140 write('/*')
141 write(self.value)
142 write('*/')
145class WhitespaceToken(Node):
146 """A :diagram:`whitespace-token`.
148 .. autoattribute:: type
150 .. attribute:: value
152 The whitespace sequence, as a string, as in the original CSS source.
155 """
156 __slots__ = ['value']
157 type = 'whitespace'
158 repr_format = '<{self.__class__.__name__}>'
160 def __init__(self, line, column, value):
161 Node.__init__(self, line, column)
162 self.value = value
164 def _serialize_to(self, write):
165 write(self.value)
168class LiteralToken(Node):
169 r"""Token that represents one or more characters as in the CSS source.
171 .. autoattribute:: type
173 .. attribute:: value
175 A string of one to four characters.
177 Instances compare equal to their :attr:`value`,
178 so that these are equivalent:
180 .. code-block:: python
182 if node == ';':
183 if node.type == 'literal' and node.value == ';':
185 This regroups what `the specification`_ defines as separate token types:
187 .. _the specification: https://drafts.csswg.org/css-syntax-3/
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)
202 """
203 __slots__ = ['value']
204 type = 'literal'
205 repr_format = '<{self.__class__.__name__} {self.value}>'
207 def __init__(self, line, column, value):
208 Node.__init__(self, line, column)
209 self.value = value
211 def __eq__(self, other):
212 return self.value == other or self is other
214 def __ne__(self, other):
215 return not self == other
217 def _serialize_to(self, write):
218 write(self.value)
221class IdentToken(Node):
222 """An :diagram:`ident-token`.
224 .. autoattribute:: type
226 .. attribute:: value
228 The unescaped value, as a Unicode string.
230 .. attribute:: lower_value
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.
236 """
237 __slots__ = ['lower_value', 'value']
238 type = 'ident'
239 repr_format = '<{self.__class__.__name__} {self.value}>'
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
249 def _serialize_to(self, write):
250 write(serialize_identifier(self.value))
253class AtKeywordToken(Node):
254 """An :diagram:`at-keyword-token`.
256 .. code-block:: text
258 '@' <value>
260 .. autoattribute:: type
262 .. attribute:: value
264 The unescaped value, as a Unicode string, without the preceding ``@``.
266 .. attribute:: lower_value
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.
272 .. code-block:: python
274 if node.type == 'at-keyword' and node.lower_value == 'import':
276 """
277 __slots__ = ['lower_value', 'value']
278 type = 'at-keyword'
279 repr_format = '<{self.__class__.__name__} @{self.value}>'
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
289 def _serialize_to(self, write):
290 write('@')
291 write(serialize_identifier(self.value))
294class HashToken(Node):
295 r"""A :diagram:`hash-token`.
297 .. code-block:: text
299 '#' <value>
301 .. autoattribute:: type
303 .. attribute:: value
305 The unescaped value, as a Unicode string, without the preceding ``#``.
307 .. attribute:: is_identifier
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.)
313 """
314 __slots__ = ['is_identifier', 'value']
315 type = 'hash'
316 repr_format = '<{self.__class__.__name__} #{self.value}>'
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
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))
331class StringToken(Node):
332 """A :diagram:`string-token`.
334 .. code-block:: text
336 '"' <value> '"'
338 .. autoattribute:: type
340 .. attribute:: value
342 The unescaped value, as a Unicode string, without the quotes.
344 """
345 __slots__ = ['representation', 'value']
346 type = 'string'
347 repr_format = '<{self.__class__.__name__} {self.representation}>'
349 def __init__(self, line, column, value, representation):
350 Node.__init__(self, line, column)
351 self.value = value
352 self.representation = representation
354 def _serialize_to(self, write):
355 write(self.representation)
358class URLToken(Node):
359 """An :diagram:`url-token`.
361 .. code-block:: text
363 'url(' <value> ')'
365 .. autoattribute:: type
367 .. attribute:: value
369 The unescaped URL, as a Unicode string, without the ``url(`` and ``)``
370 markers.
372 """
373 __slots__ = ['representation', 'value']
374 type = 'url'
375 repr_format = '<{self.__class__.__name__} {self.representation}>'
377 def __init__(self, line, column, value, representation):
378 Node.__init__(self, line, column)
379 self.value = value
380 self.representation = representation
382 def _serialize_to(self, write):
383 write(self.representation)
386class UnicodeRangeToken(Node):
387 """A :diagram:`unicode-range-token`.
389 .. autoattribute:: type
391 .. attribute:: start
393 The start of the range, as an integer between 0 and 1114111.
395 .. attribute:: end
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.
400 """
401 __slots__ = ['end', 'start']
402 type = 'unicode-range'
403 repr_format = '<{self.__class__.__name__} {self.start} {self.end}>'
405 def __init__(self, line, column, start, end):
406 Node.__init__(self, line, column)
407 self.start = start
408 self.end = end
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))
417class NumberToken(Node):
418 """A :diagram:`number-token`.
420 .. autoattribute:: type
422 .. attribute:: value
424 The numeric value as a :class:`float`.
426 .. attribute:: int_value
428 The numeric value as an :class:`int`
429 if :attr:`is_integer` is true, :obj:`None` otherwise.
431 .. attribute:: is_integer
433 Whether the token was syntactically an integer, as a boolean.
435 .. attribute:: representation
437 The CSS representation of the value, as a Unicode string.
439 """
440 __slots__ = ['int_value', 'is_integer', 'representation', 'value']
441 type = 'number'
442 repr_format = '<{self.__class__.__name__} {self.representation}>'
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
451 def _serialize_to(self, write):
452 write(self.representation)
455class PercentageToken(Node):
456 """A :diagram:`percentage-token`.
458 .. code-block:: text
460 <representation> '%'
462 .. autoattribute:: type
464 .. attribute:: value
466 The value numeric as a :class:`float`.
468 .. attribute:: int_value
470 The numeric value as an :class:`int`
471 if the token was syntactically an integer,
472 or :obj:`None`.
474 .. attribute:: is_integer
476 Whether the token’s value was syntactically an integer, as a boolean.
478 .. attribute:: representation
480 The CSS representation of the value without the unit,
481 as a Unicode string.
483 """
484 __slots__ = ['int_value', 'is_integer', 'representation', 'value']
485 type = 'percentage'
486 repr_format = '<{self.__class__.__name__} {self.representation}%>'
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
495 def _serialize_to(self, write):
496 write(self.representation)
497 write('%')
500class DimensionToken(Node):
501 """A :diagram:`dimension-token`.
503 .. code-block:: text
505 <representation> <unit>
507 .. autoattribute:: type
509 .. attribute:: value
511 The value numeric as a :class:`float`.
513 .. attribute:: int_value
515 The numeric value as an :class:`int`
516 if the token was syntactically an integer,
517 or :obj:`None`.
519 .. attribute:: is_integer
521 Whether the token’s value was syntactically an integer, as a boolean.
523 .. attribute:: representation
525 The CSS representation of the value without the unit,
526 as a Unicode string.
528 .. attribute:: unit
530 The unescaped unit, as a Unicode string.
532 .. attribute:: lower_unit
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.
538 .. code-block:: python
540 if node.type == 'dimension' and node.lower_unit == 'px':
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}>')
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
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))
578class ParenthesesBlock(Node):
579 """A :diagram:`()-block`.
581 .. code-block:: text
583 '(' <content> ')'
585 .. autoattribute:: type
587 .. attribute:: content
589 The content of the block, as list of :term:`component values`.
590 The ``(`` and ``)`` markers themselves are not represented in the list.
592 """
593 __slots__ = ['content']
594 type = '() block'
595 repr_format = '<{self.__class__.__name__} ( … )>'
597 def __init__(self, line, column, content):
598 Node.__init__(self, line, column)
599 self.content = content
601 def _serialize_to(self, write):
602 write('(')
603 _serialize_to(self.content, write)
604 write(')')
607class SquareBracketsBlock(Node):
608 """A :diagram:`[]-block`.
610 .. code-block:: text
612 '[' <content> ']'
614 .. autoattribute:: type
616 .. attribute:: content
618 The content of the block, as list of :term:`component values`.
619 The ``[`` and ``]`` markers themselves are not represented in the list.
621 """
622 __slots__ = ['content']
623 type = '[] block'
624 repr_format = '<{self.__class__.__name__} [ … ]>'
626 def __init__(self, line, column, content):
627 Node.__init__(self, line, column)
628 self.content = content
630 def _serialize_to(self, write):
631 write('[')
632 _serialize_to(self.content, write)
633 write(']')
636class CurlyBracketsBlock(Node):
637 """A :diagram:`{}-block`.
639 .. code-block:: text
641 '{' <content> '}'
643 .. autoattribute:: type
645 .. attribute:: content
647 The content of the block, as list of :term:`component values`.
648 The ``[`` and ``]`` markers themselves are not represented in the list.
650 """
651 __slots__ = ['content']
652 type = '{} block'
653 repr_format = '<{self.__class__.__name__} {{ … }}>'
655 def __init__(self, line, column, content):
656 Node.__init__(self, line, column)
657 self.content = content
659 def _serialize_to(self, write):
660 write('{')
661 _serialize_to(self.content, write)
662 write('}')
665class FunctionBlock(Node):
666 """A :diagram:`function-block`.
668 .. code-block:: text
670 <name> '(' <arguments> ')'
672 .. autoattribute:: type
674 .. attribute:: name
676 The unescaped name of the function, as a Unicode string.
678 .. attribute:: lower_name
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.
684 .. attribute:: arguments
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.
691 """
692 __slots__ = ['arguments', 'lower_name', 'name']
693 type = 'function'
694 repr_format = '<{self.__class__.__name__} {self.name}( … )>'
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
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(')')
720class Declaration(Node):
721 """A (property or descriptor) :diagram:`declaration`.
723 .. code-block:: text
725 <name> ':' <value>
726 <name> ':' <value> '!important'
728 .. autoattribute:: type
730 .. attribute:: name
732 The unescaped name, as a Unicode string.
734 .. attribute:: lower_name
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.
741 .. code-block:: python
743 if node.type == 'declaration' and node.lower_name == 'color':
745 .. attribute:: value
747 The declaration value as a list of :term:`component values`:
748 anything between ``:`` and
749 the end of the declaration, or ``!important``.
751 .. attribute:: important
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.
757 """
758 __slots__ = ['important', 'lower_name', 'name', 'value']
759 type = 'declaration'
760 repr_format = '<{self.__class__.__name__} {self.name}: …>'
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
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')
777class QualifiedRule(Node):
778 """A :diagram:`qualified rule`.
780 .. code-block:: text
782 <prelude> '{' <content> '}'
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.
790 .. autoattribute:: type
792 .. attribute:: prelude
794 The rule’s prelude, the part before the {} block,
795 as a list of :term:`component values`.
797 .. attribute:: content
799 The rule’s content, the part inside the {} block,
800 as a list of :term:`component values`.
802 """
803 __slots__ = ['content', 'prelude']
804 type = 'qualified-rule'
805 repr_format = ('<{self.__class__.__name__} '
806 '… {{ … }}>')
808 def __init__(self, line, column, prelude, content):
809 Node.__init__(self, line, column)
810 self.prelude = prelude
811 self.content = content
813 def _serialize_to(self, write):
814 _serialize_to(self.prelude, write)
815 write('{')
816 _serialize_to(self.content, write)
817 write('}')
820class AtRule(Node):
821 """An :diagram:`at-rule`.
823 .. code-block:: text
825 @<at_keyword> <prelude> '{' <content> '}'
826 @<at_keyword> <prelude> ';'
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.
834 .. autoattribute:: type
836 .. attribute:: at_keyword
838 The unescaped value of the rule’s at-keyword,
839 without the ``@`` symbol, as a Unicode string.
841 .. attribute:: lower_at_keyword
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.
847 .. code-block:: python
849 if node.type == 'at-rule' and node.lower_at_keyword == 'import':
851 .. attribute:: prelude
853 The rule’s prelude, the part before the {} block or semicolon,
854 as a list of :term:`component values`.
856 .. attribute:: content
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.
863 """
864 __slots__ = ['at_keyword', 'content', 'lower_at_keyword', 'prelude']
865 type = 'at-rule'
866 repr_format = ('<{self.__class__.__name__} '
867 '@{self.at_keyword} … {{ … }}>')
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
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('}')