Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tinycss2/ast.py: 46%
281 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
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_line', 'source_column']
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__ = ['value', 'lower_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__ = ['value', 'lower_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__ = ['value', 'is_identifier']
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__ = ['value', 'representation']
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__ = ['value', 'representation']
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 `unicode-range token <https://www.w3.org/TR/css-syntax-3/#urange>`_.
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__ = ['start', 'end']
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__ = ['value', 'int_value', 'is_integer', 'representation']
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__ = ['value', 'int_value', 'is_integer', 'representation']
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__ = ['value', 'int_value', 'is_integer', 'representation',
544 'unit', 'lower_unit']
545 type = 'dimension'
546 repr_format = ('<{self.__class__.__name__} '
547 '{self.representation}{self.unit}>')
549 def __init__(self, line, column, value, int_value, representation, unit):
550 Node.__init__(self, line, column)
551 self.value = value
552 self.int_value = int_value
553 self.is_integer = int_value is not None
554 self.representation = representation
555 self.unit = unit
556 self.lower_unit = ascii_lower(unit)
558 def _serialize_to(self, write):
559 write(self.representation)
560 # Disambiguate with scientific notation
561 unit = self.unit
562 if unit in ('e', 'E') or unit.startswith(('e-', 'E-')):
563 write('\\65 ')
564 write(serialize_name(unit[1:]))
565 else:
566 write(serialize_identifier(unit))
569class ParenthesesBlock(Node):
570 """A :diagram:`()-block`.
572 .. code-block:: text
574 '(' <content> ')'
576 .. autoattribute:: type
578 .. attribute:: content
580 The content of the block, as list of :term:`component values`.
581 The ``(`` and ``)`` markers themselves are not represented in the list.
583 """
584 __slots__ = ['content']
585 type = '() block'
586 repr_format = '<{self.__class__.__name__} ( … )>'
588 def __init__(self, line, column, content):
589 Node.__init__(self, line, column)
590 self.content = content
592 def _serialize_to(self, write):
593 write('(')
594 _serialize_to(self.content, write)
595 write(')')
598class SquareBracketsBlock(Node):
599 """A :diagram:`[]-block`.
601 .. code-block:: text
603 '[' <content> ']'
605 .. autoattribute:: type
607 .. attribute:: content
609 The content of the block, as list of :term:`component values`.
610 The ``[`` and ``]`` markers themselves are not represented in the list.
612 """
613 __slots__ = ['content']
614 type = '[] block'
615 repr_format = '<{self.__class__.__name__} [ … ]>'
617 def __init__(self, line, column, content):
618 Node.__init__(self, line, column)
619 self.content = content
621 def _serialize_to(self, write):
622 write('[')
623 _serialize_to(self.content, write)
624 write(']')
627class CurlyBracketsBlock(Node):
628 """A :diagram:`{}-block`.
630 .. code-block:: text
632 '{' <content> '}'
634 .. autoattribute:: type
636 .. attribute:: content
638 The content of the block, as list of :term:`component values`.
639 The ``[`` and ``]`` markers themselves are not represented in the list.
641 """
642 __slots__ = ['content']
643 type = '{} block'
644 repr_format = '<{self.__class__.__name__} {{ … }}>'
646 def __init__(self, line, column, content):
647 Node.__init__(self, line, column)
648 self.content = content
650 def _serialize_to(self, write):
651 write('{')
652 _serialize_to(self.content, write)
653 write('}')
656class FunctionBlock(Node):
657 """A :diagram:`function-block`.
659 .. code-block:: text
661 <name> '(' <arguments> ')'
663 .. autoattribute:: type
665 .. attribute:: name
667 The unescaped name of the function, as a Unicode string.
669 .. attribute:: lower_name
671 Same as :attr:`name` but normalized to *ASCII lower case*,
672 see :func:`~webencodings.ascii_lower`.
673 This is the value to use when comparing to a CSS function name.
675 .. attribute:: arguments
677 The arguments of the function, as list of :term:`component values`.
678 The ``(`` and ``)`` markers themselves are not represented in the list.
679 Commas are not special, but represented as :obj:`LiteralToken` objects
680 in the list.
682 """
683 __slots__ = ['name', 'lower_name', 'arguments']
684 type = 'function'
685 repr_format = '<{self.__class__.__name__} {self.name}( … )>'
687 def __init__(self, line, column, name, arguments):
688 Node.__init__(self, line, column)
689 self.name = name
690 self.lower_name = ascii_lower(name)
691 self.arguments = arguments
693 def _serialize_to(self, write):
694 write(serialize_identifier(self.name))
695 write('(')
696 _serialize_to(self.arguments, write)
697 function = self
698 while isinstance(function, FunctionBlock) and function.arguments:
699 eof_in_string = (
700 isinstance(function.arguments[-1], ParseError) and
701 function.arguments[-1].kind == 'eof-in-string')
702 if eof_in_string:
703 return
704 function = function.arguments[-1]
705 write(')')
708class Declaration(Node):
709 """A (property or descriptor) :diagram:`declaration`.
711 .. code-block:: text
713 <name> ':' <value>
714 <name> ':' <value> '!important'
716 .. autoattribute:: type
718 .. attribute:: name
720 The unescaped name, as a Unicode string.
722 .. attribute:: lower_name
724 Same as :attr:`name` but normalized to *ASCII lower case*,
725 see :func:`~webencodings.ascii_lower`.
726 This is the value to use when comparing to
727 a CSS property or descriptor name.
729 .. code-block:: python
731 if node.type == 'declaration' and node.lower_name == 'color':
733 .. attribute:: value
735 The declaration value as a list of :term:`component values`:
736 anything between ``:`` and
737 the end of the declaration, or ``!important``.
739 .. attribute:: important
741 A boolean, true if the declaration had an ``!important`` marker.
742 It is up to the consumer to reject declarations that do not accept
743 this flag, such as non-property descriptor declarations.
745 """
746 __slots__ = ['name', 'lower_name', 'value', 'important']
747 type = 'declaration'
748 repr_format = '<{self.__class__.__name__} {self.name}: …>'
750 def __init__(self, line, column, name, lower_name, value, important):
751 Node.__init__(self, line, column)
752 self.name = name
753 self.lower_name = lower_name
754 self.value = value
755 self.important = important
757 def _serialize_to(self, write):
758 write(serialize_identifier(self.name))
759 write(':')
760 _serialize_to(self.value, write)
761 if self.important:
762 write('!important')
765class QualifiedRule(Node):
766 """A :diagram:`qualified rule`.
768 .. code-block:: text
770 <prelude> '{' <content> '}'
772 The interpretation of qualified rules depend on their context.
773 At the top-level of a stylesheet
774 or in a conditional rule such as ``@media``,
775 they are **style rules** where the :attr:`prelude` is Selectors list
776 and the :attr:`content` is a list of property declarations.
778 .. autoattribute:: type
780 .. attribute:: prelude
782 The rule’s prelude, the part before the {} block,
783 as a list of :term:`component values`.
785 .. attribute:: content
787 The rule’s content, the part inside the {} block,
788 as a list of :term:`component values`.
790 """
791 __slots__ = ['prelude', 'content']
792 type = 'qualified-rule'
793 repr_format = ('<{self.__class__.__name__} '
794 '… {{ … }}>')
796 def __init__(self, line, column, prelude, content):
797 Node.__init__(self, line, column)
798 self.prelude = prelude
799 self.content = content
801 def _serialize_to(self, write):
802 _serialize_to(self.prelude, write)
803 write('{')
804 _serialize_to(self.content, write)
805 write('}')
808class AtRule(Node):
809 """An :diagram:`at-rule`.
811 .. code-block:: text
813 @<at_keyword> <prelude> '{' <content> '}'
814 @<at_keyword> <prelude> ';'
816 The interpretation of at-rules depend on their at-keyword
817 as well as their context.
818 Most types of at-rules (ie. at-keyword values)
819 are only allowed in some context,
820 and must either end with a {} block or a semicolon.
822 .. autoattribute:: type
824 .. attribute:: at_keyword
826 The unescaped value of the rule’s at-keyword,
827 without the ``@`` symbol, as a Unicode string.
829 .. attribute:: lower_at_keyword
831 Same as :attr:`at_keyword` but normalized to *ASCII lower case*,
832 see :func:`~webencodings.ascii_lower`.
833 This is the value to use when comparing to a CSS at-keyword.
835 .. code-block:: python
837 if node.type == 'at-rule' and node.lower_at_keyword == 'import':
839 .. attribute:: prelude
841 The rule’s prelude, the part before the {} block or semicolon,
842 as a list of :term:`component values`.
844 .. attribute:: content
846 The rule’s content, if any.
847 The block’s content as a list of :term:`component values`
848 for at-rules with a {} block,
849 or :obj:`None` for at-rules ending with a semicolon.
851 """
852 __slots__ = ['at_keyword', 'lower_at_keyword', 'prelude', 'content']
853 type = 'at-rule'
854 repr_format = ('<{self.__class__.__name__} '
855 '@{self.at_keyword} … {{ … }}>')
857 def __init__(self, line, column,
858 at_keyword, lower_at_keyword, prelude, content):
859 Node.__init__(self, line, column)
860 self.at_keyword = at_keyword
861 self.lower_at_keyword = lower_at_keyword
862 self.prelude = prelude
863 self.content = content
865 def _serialize_to(self, write):
866 write('@')
867 write(serialize_identifier(self.at_keyword))
868 _serialize_to(self.prelude, write)
869 if self.content is None:
870 write(';')
871 else:
872 write('{')
873 _serialize_to(self.content, write)
874 write('}')