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 self.lower_unit = ascii_lower(unit)
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))
575class ParenthesesBlock(Node):
576 """A :diagram:`()-block`.
578 .. code-block:: text
580 '(' <content> ')'
582 .. autoattribute:: type
584 .. attribute:: content
586 The content of the block, as list of :term:`component values`.
587 The ``(`` and ``)`` markers themselves are not represented in the list.
589 """
590 __slots__ = ['content']
591 type = '() block'
592 repr_format = '<{self.__class__.__name__} ( … )>'
594 def __init__(self, line, column, content):
595 Node.__init__(self, line, column)
596 self.content = content
598 def _serialize_to(self, write):
599 write('(')
600 _serialize_to(self.content, write)
601 write(')')
604class SquareBracketsBlock(Node):
605 """A :diagram:`[]-block`.
607 .. code-block:: text
609 '[' <content> ']'
611 .. autoattribute:: type
613 .. attribute:: content
615 The content of the block, as list of :term:`component values`.
616 The ``[`` and ``]`` markers themselves are not represented in the list.
618 """
619 __slots__ = ['content']
620 type = '[] block'
621 repr_format = '<{self.__class__.__name__} [ … ]>'
623 def __init__(self, line, column, content):
624 Node.__init__(self, line, column)
625 self.content = content
627 def _serialize_to(self, write):
628 write('[')
629 _serialize_to(self.content, write)
630 write(']')
633class CurlyBracketsBlock(Node):
634 """A :diagram:`{}-block`.
636 .. code-block:: text
638 '{' <content> '}'
640 .. autoattribute:: type
642 .. attribute:: content
644 The content of the block, as list of :term:`component values`.
645 The ``[`` and ``]`` markers themselves are not represented in the list.
647 """
648 __slots__ = ['content']
649 type = '{} block'
650 repr_format = '<{self.__class__.__name__} {{ … }}>'
652 def __init__(self, line, column, content):
653 Node.__init__(self, line, column)
654 self.content = content
656 def _serialize_to(self, write):
657 write('{')
658 _serialize_to(self.content, write)
659 write('}')
662class FunctionBlock(Node):
663 """A :diagram:`function-block`.
665 .. code-block:: text
667 <name> '(' <arguments> ')'
669 .. autoattribute:: type
671 .. attribute:: name
673 The unescaped name of the function, as a Unicode string.
675 .. attribute:: lower_name
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.
681 .. attribute:: arguments
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.
688 """
689 __slots__ = ['arguments', 'lower_name', 'name']
690 type = 'function'
691 repr_format = '<{self.__class__.__name__} {self.name}( … )>'
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
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(')')
714class Declaration(Node):
715 """A (property or descriptor) :diagram:`declaration`.
717 .. code-block:: text
719 <name> ':' <value>
720 <name> ':' <value> '!important'
722 .. autoattribute:: type
724 .. attribute:: name
726 The unescaped name, as a Unicode string.
728 .. attribute:: lower_name
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.
735 .. code-block:: python
737 if node.type == 'declaration' and node.lower_name == 'color':
739 .. attribute:: value
741 The declaration value as a list of :term:`component values`:
742 anything between ``:`` and
743 the end of the declaration, or ``!important``.
745 .. attribute:: important
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.
751 """
752 __slots__ = ['important', 'lower_name', 'name', 'value']
753 type = 'declaration'
754 repr_format = '<{self.__class__.__name__} {self.name}: …>'
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
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')
771class QualifiedRule(Node):
772 """A :diagram:`qualified rule`.
774 .. code-block:: text
776 <prelude> '{' <content> '}'
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.
784 .. autoattribute:: type
786 .. attribute:: prelude
788 The rule’s prelude, the part before the {} block,
789 as a list of :term:`component values`.
791 .. attribute:: content
793 The rule’s content, the part inside the {} block,
794 as a list of :term:`component values`.
796 """
797 __slots__ = ['content', 'prelude']
798 type = 'qualified-rule'
799 repr_format = ('<{self.__class__.__name__} '
800 '… {{ … }}>')
802 def __init__(self, line, column, prelude, content):
803 Node.__init__(self, line, column)
804 self.prelude = prelude
805 self.content = content
807 def _serialize_to(self, write):
808 _serialize_to(self.prelude, write)
809 write('{')
810 _serialize_to(self.content, write)
811 write('}')
814class AtRule(Node):
815 """An :diagram:`at-rule`.
817 .. code-block:: text
819 @<at_keyword> <prelude> '{' <content> '}'
820 @<at_keyword> <prelude> ';'
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.
828 .. autoattribute:: type
830 .. attribute:: at_keyword
832 The unescaped value of the rule’s at-keyword,
833 without the ``@`` symbol, as a Unicode string.
835 .. attribute:: lower_at_keyword
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.
841 .. code-block:: python
843 if node.type == 'at-rule' and node.lower_at_keyword == 'import':
845 .. attribute:: prelude
847 The rule’s prelude, the part before the {} block or semicolon,
848 as a list of :term:`component values`.
850 .. attribute:: content
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.
857 """
858 __slots__ = ['at_keyword', 'content', 'lower_at_keyword', 'prelude']
859 type = 'at-rule'
860 repr_format = ('<{self.__class__.__name__} '
861 '@{self.at_keyword} … {{ … }}>')
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
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('}')