Import python-tinycss2_0.6.1.orig.tar.gz

[dgit import orig python-tinycss2_0.6.1.orig.tar.gz]
This commit is contained in:
Michael Fladischer 2018-07-15 12:08:24 +02:00
commit 33bf61345e
45 changed files with 9569 additions and 0 deletions

10
.coveragerc Normal file
View File

@ -0,0 +1,10 @@
[run]
branch = True
[report]
exclude_lines =
pragma: no cover
def __repr__
raise NotImplementedError
omit =
.*

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.pyc
*.egg-info
.cache
.coverage
.eggs
/htmlcov
/dist
/build

30
.travis.yml Normal file
View File

@ -0,0 +1,30 @@
language: python
sudo: false
git:
submodules: false
matrix:
include:
- os: linux
python: 2.7
- os: linux
python: 3.3
- os: linux
python: 3.4
- os: linux
python: 3.5
- os: linux
python: 3.6
- os: osx
language: generic
env: PYTHON_VERSION=3
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install python3; fi
install:
- pip$PYTHON_VERSION install --upgrade -e.[test]
script:
- python$PYTHON_VERSION setup.py test

83
CHANGES Normal file
View File

@ -0,0 +1,83 @@
tinycss2 changelog
==================
Version 0.6.1
-------------
Released on 2017-10-02.
* Update documentation.
Version 0.6.0
-------------
Released on 2017-08-16.
* Don't allow identifiers starting with two dashes.
* Don't use Tox for tests.
* Follow semantic versioning.
Version 0.5
-----------
Released on 2014-08-19.
* Update for spec changes.
* Add a :attr:`~tinycss2.ast.WhitespaceToken.value` attribute
to :class:`~tinycss2.ast.WhitespaceToken`.
* **Breaking change**: CSS comments are now preserved
as :class:`~tinycss2.ast.Comment` objects by default.
Pass ``skip_comments=True`` to parsing functions to get the old behavior.
* **Breaking change**: Top-level comments and whitespace are now preserved
when parsing a stylesheet, rule list, or declaration list.
Pass ``skip_comments=True`` and ``skip_whitespace=True``
to get the old behavior.
* Test on Python 3.4 and PyPy3.
* Set up continous integration on Travis-CI.
Version 0.4
-----------
Released on 2014-01-04.
* Fix :class:`~tinycss2.ast.HashToken` starting with a non-ASCII character.
* Fix :func:`repr` on AST nodes.
Version 0.3
-----------
Released on 2013-12-27.
* Document all the things!
* Add :ref:`serialization`
* Merge :func:`tinycss2.color3.parse_color_string` behavior into
:func:`~tinycss2.color3.parse_color`.
* Fix and test parsing form bytes and tokenization of <unicode-range>.
Version 0.2
-----------
Released on 2013-09-02.
Add parsing for <An+B>,
as in ``:nth-child()`` and related Selectors pseudo-classes.
Version 0.1
-----------
Released on 2013-08-31.
First PyPI release. Contains:
* Decoding from bytes (``@charset``, etc.)
* Tokenization
* Parsing for "generic" rules and declarations
* Parsing for CSS Color Level 3
* Tests for all of the above, except for decoding from bytes.

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright (c) 2013 by Simon Sapin.
Some rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
include CHANGES
include LICENSE
include docs/*
include tinycss2/css-parsing-tests/*

41
PKG-INFO Normal file
View File

@ -0,0 +1,41 @@
Metadata-Version: 1.1
Name: tinycss2
Version: 0.6.1
Summary: Low-level CSS parser for Python
Home-page: UNKNOWN
Author: Simon Sapin
Author-email: simon.sapin@exyr.org
License: BSD
Description-Content-Type: UNKNOWN
Description: tinycss2: Low-level CSS parser for Python
#################################################
tinycss2 is a rewrite of tinycss_ with a simpler API,
based on the more recent `CSS Syntax Level 3`_ specification.
.. _tinycss: http://pythonhosted.org/tinycss/
.. _CSS Syntax Level 3: http://dev.w3.org/csswg/css-syntax-3/
* BSD licensed
* For Python 2.7 or 3.3+ (tested on CPython)
* Latest documentation: http://tinycss2.readthedocs.io/
* Source code and issue tracker: https://github.com/Kozea/tinycss2
* PyPI releases: https://pypi.python.org/pypi/tinycss2/
* Continuous integration: |travis|
.. |travis| image:: https://travis-ci.org/Kozea/tinycss2.svg?branch=master
:target: https://travis-ci.org/Kozea/tinycss2
:alt: https://travis-ci.org/Kozea/tinycss2
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Text Processing

19
README.rst Normal file
View File

@ -0,0 +1,19 @@
tinycss2: Low-level CSS parser for Python
#################################################
tinycss2 is a rewrite of tinycss_ with a simpler API,
based on the more recent `CSS Syntax Level 3`_ specification.
.. _tinycss: http://pythonhosted.org/tinycss/
.. _CSS Syntax Level 3: http://dev.w3.org/csswg/css-syntax-3/
* BSD licensed
* For Python 2.7 or 3.3+ (tested on CPython)
* Latest documentation: http://tinycss2.readthedocs.io/
* Source code and issue tracker: https://github.com/Kozea/tinycss2
* PyPI releases: https://pypi.python.org/pypi/tinycss2/
* Continuous integration: |travis|
.. |travis| image:: https://travis-ci.org/Kozea/tinycss2.svg?branch=master
:target: https://travis-ci.org/Kozea/tinycss2
:alt: https://travis-ci.org/Kozea/tinycss2

4
TODO Normal file
View File

@ -0,0 +1,4 @@
Test preserve_comments=True
Test declaration corner cases: top-level ! and ;
… once that is all figured out in the spec

84
docs/conf.py Normal file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# tinycss2 documentation build configuration file.
import codecs
import re
import sys
from os import path
sys.path.append(path.dirname(path.abspath(__file__)))
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'css_diagram_role']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'tinycss2'
copyright = '2013-2017, Simon Sapin'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = re.search("VERSION = '([^']+)'", codecs.open(
path.join(path.dirname(path.dirname(__file__)), 'tinycss2', '__init__.py'),
encoding='utf-8',
).read().strip()).group(1)
# The short X.Y version.
version = '.'.join(release.split('.')[:2])
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Output file base name for HTML help builder.
htmlhelp_basename = 'tinycss2doc'
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'tinycss2', 'tinycss2 Documentation',
['Simon Sapin'], 1)
]
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'tinycss2', 'tinycss2 Documentation',
'Simon Sapin', 'tinycss2', 'One line description of project.',
'Miscellaneous'),
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'py2': ('http://docs.python.org/2', None),
'py3': ('http://docs.python.org/3', None),
'webencodings': ('http://pythonhosted.org/webencodings/', None)}

21
docs/css_diagram_role.py Normal file
View File

@ -0,0 +1,21 @@
# coding: utf8
"""
A Sphinx extension adding a 'css' role creating links to
the specs railroad diagrams.
"""
from docutils import nodes
def role_fn(_name, rawtext, text, lineno, inliner, options={}, content=()):
ref = 'http://dev.w3.org/csswg/css-syntax-3/#%s-diagram' % text.replace(
' ', '-')
if text.endswith(('-token', '-block')):
text = '<%s>' % text
ref = nodes.reference(rawtext, text, refuri=ref, **options)
return [ref], []
def setup(app):
app.add_role_to_domain('py', 'diagram', role_fn)

157
docs/index.rst Normal file
View File

@ -0,0 +1,157 @@
:tocdepth: 3
.. include:: ../README.rst
Installation
============
Installing tinycss2 with pip_ should Just Work::
pip install tinycss2
This will also automatically install tinycss2s only dependency, webencodings_.
tinycss2 and webencodings both only contain Python code and should work on any
Python implementation, although theyre only tested on CPython.
.. _pip: http://pip-installer.org/
.. _webencodings: http://pythonhosted.org/webencodings/
.. _parsing:
Parsing
=======
tinycss2 is “low-level” in that it doesnt parse all of CSS:
it doesnt know about the syntax of any specific properties or at-rules.
Instead, it provides a set of functions that can be composed
to support exactly the parts of CSS youre interested in,
including new or non-standard rules or properties,
without modifying tinycss or having a complex hook/plugin system.
In many cases, parts of the parsed values
(such as the :attr:`~tinycss2.ast.AtRule.content`
of a :class:`~tinycss2.ast.AtRule`)
is given as :term:`component values` that can be parsed further
with other functions.
.. module:: tinycss2
.. autofunction:: parse_stylesheet_bytes
.. autofunction:: parse_stylesheet
.. autofunction:: parse_rule_list
.. autofunction:: parse_one_rule
.. autofunction:: parse_declaration_list
.. autofunction:: parse_one_declaration
.. autofunction:: parse_component_value_list
.. autofunction:: parse_one_component_value
.. _serialization:
Serialization
=============
In addition to each nodes a :meth:`~tinycss2.ast.Node.serialize` method,
some serialization-related functions are available:
.. autofunction:: serialize
.. autofunction:: serialize_identifier
.. module:: tinycss2.color3
Color
=====
.. autofunction:: parse_color
.. autoclass:: RGBA
.. module:: tinycss2.nth
<An+B>
======
.. autofunction:: parse_nth
.. module:: tinycss2.ast
AST nodes
=========
Various parsing functions return a **node** or a list of nodes.
Some types of nodes contain nested nodes which may in turn contain more nodes,
forming together an **abstract syntax tree**.
Although you typically dont need to import it,
the :mod:`tinycss2.ast` module defines a class for every type of node.
.. autoclass:: Node()
.. autoclass:: QualifiedRule()
.. autoclass:: AtRule()
.. autoclass:: Declaration()
Component values
----------------
.. autoclass:: ParseError()
.. autoclass:: Comment()
.. autoclass:: WhitespaceToken()
.. autoclass:: LiteralToken()
.. autoclass:: IdentToken()
.. autoclass:: AtKeywordToken()
.. autoclass:: HashToken()
.. autoclass:: StringToken()
.. autoclass:: URLToken()
.. autoclass:: UnicodeRangeToken()
.. autoclass:: NumberToken()
.. autoclass:: PercentageToken()
.. autoclass:: DimensionToken()
.. autoclass:: ParenthesesBlock()
.. autoclass:: SquareBracketsBlock()
.. autoclass:: CurlyBracketsBlock()
.. autoclass:: FunctionBlock()
Glossary
========
.. currentmodule:: tinycss2.ast
.. glossary::
String
In this documentation “a string” means an Unicode string:
:func:`unicode <py2:unicode>` on Python 2.x and
:class:`py3:str` on Python 3.x.
On 2.x,
a byte string (:func:`str <py2:str>`) that only contains ASCII bytes
is also accepted and implicitly decoded.
Component value
Component values
A :class:`ParseError`,
:class:`WhitespaceToken`,
:class:`LiteralToken`,
:class:`IdentToken`,
:class:`AtKeywordToken`,
:class:`HashToken`,
:class:`StringToken`,
:class:`URLToken`,
:class:`NumberToken`,
:class:`PercentageToken`,
:class:`DimensionToken`,
:class:`UnicodeRangeToken`,
:class:`ParenthesesBlock`,
:class:`SquareBracketsBlock`,
:class:`CurlyBracketsBlock`,
:class:`FunctionBlock`,
or :class:`Comment`
object.
.. currentmodule:: tinycss2
.. include:: ../CHANGES

14
setup.cfg Normal file
View File

@ -0,0 +1,14 @@
[aliases]
test = pytest
[bdist_wheel]
universal = 1
[tool:pytest]
addopts = --cov=tinycss2 --flake8 --isort tinycss2/test.py
norecursedirs = dist .cache .git build *.egg-info .eggs venv
[egg_info]
tag_build =
tag_date = 0

55
setup.py Executable file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# coding: utf8
import codecs
import os.path
import re
import sys
from setuptools import setup
VERSION = re.search("VERSION = '([^']+)'", codecs.open(
os.path.join(os.path.dirname(__file__), 'tinycss2', '__init__.py'),
encoding='utf-8',
).read().strip()).group(1)
README = codecs.open(
os.path.join(os.path.dirname(__file__), 'README.rst'),
encoding='utf-8',
).read()
needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
pytest_runner = ['pytest-runner'] if needs_pytest else []
setup(
name='tinycss2',
version=VERSION,
description='Low-level CSS parser for Python',
long_description=README,
license='BSD',
author='Simon Sapin',
author_email='simon.sapin@exyr.org',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Text Processing',
],
packages=['tinycss2'],
install_requires=['webencodings>=0.4'],
package_data={'tinycss2': ['css-parsing-tests/*']},
setup_requires=pytest_runner,
test_suite='tinycss2.test',
tests_require=[
'pytest-runner', 'pytest-cov', 'pytest-flake8', 'pytest-isort'],
extras_require={'test': [
'pytest-runner', 'pytest-cov', 'pytest-flake8', 'pytest-isort']},
)

View File

@ -0,0 +1,41 @@
Metadata-Version: 1.1
Name: tinycss2
Version: 0.6.1
Summary: Low-level CSS parser for Python
Home-page: UNKNOWN
Author: Simon Sapin
Author-email: simon.sapin@exyr.org
License: BSD
Description-Content-Type: UNKNOWN
Description: tinycss2: Low-level CSS parser for Python
#################################################
tinycss2 is a rewrite of tinycss_ with a simpler API,
based on the more recent `CSS Syntax Level 3`_ specification.
.. _tinycss: http://pythonhosted.org/tinycss/
.. _CSS Syntax Level 3: http://dev.w3.org/csswg/css-syntax-3/
* BSD licensed
* For Python 2.7 or 3.3+ (tested on CPython)
* Latest documentation: http://tinycss2.readthedocs.io/
* Source code and issue tracker: https://github.com/Kozea/tinycss2
* PyPI releases: https://pypi.python.org/pypi/tinycss2/
* Continuous integration: |travis|
.. |travis| image:: https://travis-ci.org/Kozea/tinycss2.svg?branch=master
:target: https://travis-ci.org/Kozea/tinycss2
:alt: https://travis-ci.org/Kozea/tinycss2
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Text Processing

View File

@ -0,0 +1,44 @@
.coveragerc
.gitignore
.travis.yml
CHANGES
LICENSE
MANIFEST.in
README.rst
TODO
setup.cfg
setup.py
docs/conf.py
docs/css_diagram_role.py
docs/index.rst
tinycss2/__init__.py
tinycss2/_compat.py
tinycss2/ast.py
tinycss2/bytes.py
tinycss2/color3.py
tinycss2/nth.py
tinycss2/parser.py
tinycss2/serializer.py
tinycss2/test.py
tinycss2/tokenizer.py
tinycss2.egg-info/PKG-INFO
tinycss2.egg-info/SOURCES.txt
tinycss2.egg-info/dependency_links.txt
tinycss2.egg-info/requires.txt
tinycss2.egg-info/top_level.txt
tinycss2/css-parsing-tests/An+B.json
tinycss2/css-parsing-tests/LICENSE
tinycss2/css-parsing-tests/README.rst
tinycss2/css-parsing-tests/color3.json
tinycss2/css-parsing-tests/color3_hsl.json
tinycss2/css-parsing-tests/color3_keywords.json
tinycss2/css-parsing-tests/component_value_list.json
tinycss2/css-parsing-tests/declaration_list.json
tinycss2/css-parsing-tests/make_color3_hsl.py
tinycss2/css-parsing-tests/make_color3_keywords.py
tinycss2/css-parsing-tests/one_component_value.json
tinycss2/css-parsing-tests/one_declaration.json
tinycss2/css-parsing-tests/one_rule.json
tinycss2/css-parsing-tests/rule_list.json
tinycss2/css-parsing-tests/stylesheet.json
tinycss2/css-parsing-tests/stylesheet_bytes.json

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,7 @@
webencodings>=0.4
[test]
pytest-runner
pytest-cov
pytest-flake8
pytest-isort

View File

@ -0,0 +1 @@
tinycss2

9
tinycss2/__init__.py Normal file
View File

@ -0,0 +1,9 @@
VERSION = '0.6.1'
from .tokenizer import parse_component_value_list # noqa
from .parser import ( # noqa
parse_one_component_value, parse_one_declaration, parse_declaration_list,
parse_one_rule, parse_rule_list, parse_stylesheet)
from .bytes import parse_stylesheet_bytes # noqa
from .serializer import serialize, serialize_identifier # noqa

6
tinycss2/_compat.py Normal file
View File

@ -0,0 +1,6 @@
if str is bytes: # pragma: no cover
unichr = unichr # noqa
basestring = basestring # noqa
else: # pragma: no cover
unichr = chr # noqa
basestring = str # noqa

871
tinycss2/ast.py Normal file
View File

@ -0,0 +1,871 @@
# coding: utf8
"""
Data structures for the CSS abstract syntax tree.
"""
from __future__ import unicode_literals
from webencodings import ascii_lower
from .serializer import (_serialize_to, serialize_identifier, serialize_name,
serialize_string_value)
class Node(object):
"""Every node type inherits from this class,
which is never instantiated directly.
.. attribute:: type
Each child class has a :attr:`type` class attribute
with an 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
if str is bytes: # pragma: no cover
def __repr__(self):
return self.repr_format.format(self=self).encode('utf8')
else: # pragma: no cover
def __repr__(self):
return self.repr_format.format(self=self)
def serialize(self):
"""Serialize this node to CSS syntax and return an 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)
else: # pragma: no cover
raise TypeError('Can not serialize %r' % self)
class Comment(Node):
"""A CSS comment.
.. code-block:: text
'/*' <value> '*/'
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: http://dev.w3.org/csswg/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 an 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
self.lower_value = ascii_lower(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 an 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
self.lower_value = ascii_lower(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 an 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 an Unicode string, without the quotes.
"""
__slots__ = ['value']
type = 'string'
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(serialize_string_value(self.value))
write('"')
class URLToken(Node):
"""An :diagram:`url-token`.
.. code-block:: text
'url("' <value> '")'
.. autoattribute:: type
.. attribute:: value
The unescaped URL, as an Unicode string,
without the ``url(`` and ``)`` markers or the optional quotes.
"""
__slots__ = ['value']
type = 'url'
repr_format = '<{self.__class__.__name__} url({self.value})>'
def __init__(self, line, column, value):
Node.__init__(self, line, column)
self.value = value
def _serialize_to(self, write):
write('url("')
write(serialize_string_value(self.value))
write('")')
class UnicodeRangeToken(Node):
"""An :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:`numer-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 an 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 tokens value was syntactically an integer, as a boolean.
.. attribute:: representation
The CSS representation of the value without the unit,
as an 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 tokens value was syntactically an integer, as a boolean.
.. attribute:: representation
The CSS representation of the value without the unit,
as an Unicode string.
.. attribute:: unit
The unescaped unit, as an 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 an 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)
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 an Unicode string.
.. autoattribute:: type
.. 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 rules prelude, the part before the {} block,
as a list of :term:`component values`.
.. attribute:: content
The rules 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 rules at-keyword,
without the ``@`` symbol, as an 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 rules prelude, the part before the {} block or semicolon,
as a list of :term:`component values`.
.. attribute:: content
The rules content, if any.
The blocks 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('}')

112
tinycss2/bytes.py Normal file
View File

@ -0,0 +1,112 @@
from webencodings import UTF8, decode, lookup
from .parser import parse_stylesheet
def decode_stylesheet_bytes(css_bytes, protocol_encoding=None,
environment_encoding=None):
"""Determine the character encoding of a CSS stylesheet and decode it.
This is based on the presence of a ,
an ``@charset`` rule,
and encoding meta-information.
:param css_bytes: A byte string.
:param protocol_encoding:
The encoding label, if any, defined by HTTP or equivalent protocol.
(e.g. via the ``charset`` parameter of the ``Content-Type`` header.)
:param environment_encoding:
A :class:`webencodings.Encoding` object
for the `environment encoding
<http://www.w3.org/TR/css-syntax/#environment-encoding>`_,
if any.
:returns:
A 2-tuple of a decoded Unicode string
and the :class:`webencodings.Encoding` object that was used.
"""
# http://dev.w3.org/csswg/css-syntax/#the-input-byte-stream
if protocol_encoding:
fallback = lookup(protocol_encoding)
if fallback:
return decode(css_bytes, fallback)
if css_bytes.startswith(b'@charset "'):
# 10 is len(b'@charset "')
# 100 is arbitrary so that no encoding label is more than 100-10 bytes.
end_quote = css_bytes.find(b'"', 10, 100)
if end_quote != -1 and css_bytes.startswith(b'";', end_quote):
fallback = lookup(css_bytes[10:end_quote].decode('latin1'))
if fallback:
if fallback.name in ('utf-16be', 'utf-16le'):
return decode(css_bytes, UTF8)
return decode(css_bytes, fallback)
if environment_encoding:
return decode(css_bytes, environment_encoding)
return decode(css_bytes, UTF8)
def parse_stylesheet_bytes(css_bytes, protocol_encoding=None,
environment_encoding=None,
skip_comments=False, skip_whitespace=False):
"""Parse :diagram:`stylesheet` from bytes,
determining the character encoding as web browsers do.
This is used when reading a file or fetching an URL.
The character encoding is determined from the initial bytes
(a :abbr:`BOM (Byte Order Mark)` or an ``@charset`` rule)
as well as the parameters.
The ultimate fallback is UTF-8.
:param css_bytes: A byte string.
:param protocol_encoding:
A string.
The encoding label, if any, defined by HTTP or equivalent protocol.
(e.g. via the ``charset`` parameter of the ``Content-Type`` header.)
:param environment_encoding:
A :class:`webencodings.Encoding` object
for the `environment encoding`_,
if any.
:param skip_comments:
Ignore CSS comments at the top-level of the stylesheet.
If the input is a string, ignore all comments.
:param skip_whitespace:
Ignore whitespace at the top-level of the stylesheet.
Whitespace is still preserved
in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
:returns:
A ``(rules, encoding)`` tuple.
* :obj:`rules` is a list of
:class:`~tinycss2.ast.QualifiedRule`,
:class:`~tinycss2.ast.AtRule`,
:class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
:class:`~tinycss2.ast.WhitespaceToken`
(if ``skip_whitespace`` is false),
and :class:`~tinycss2.ast.ParseError` objects.
* :obj:`encoding` is the :class:`webencodings.Encoding` object
that was used.
If ``rules`` contains an ``@import`` rule, this is
the `environment encoding`_ for the imported stylesheet.
.. _environment encoding:
http://www.w3.org/TR/css-syntax/#environment-encoding
.. code-block:: python
response = urlopen('http://example.net/foo.css')
rules, encoding = parse_stylesheet_bytes(
css_bytes=response.read(),
# Python 3.x
protocol_encoding=response.info().get_content_type().get_param('charset'),
# Python 2.x
protocol_encoding=response.info().gettype().getparam('charset'),
)
for rule in rules:
...
"""
css_unicode, encoding = decode_stylesheet_bytes(
css_bytes, protocol_encoding, environment_encoding)
stylesheet = parse_stylesheet(css_unicode, skip_comments, skip_whitespace)
return stylesheet, encoding

367
tinycss2/color3.py Normal file
View File

@ -0,0 +1,367 @@
from __future__ import division
import collections
import re
from ._compat import basestring
from .parser import parse_one_component_value
class RGBA(collections.namedtuple('RGBA', ['red', 'green', 'blue', 'alpha'])):
"""An RGBA color.
A tuple of four floats in the 0..1 range: ``(red, green, blue, alpha)``.
.. attribute:: red
Convenience access to the red channel. Same as ``rgba[0]``.
.. attribute:: green
Convenience access to the green channel. Same as ``rgba[1]``.
.. attribute:: blue
Convenience access to the blue channel. Same as ``rgba[2]``.
.. attribute:: alpha
Convenience access to the alpha channel. Same as ``rgba[3]``.
"""
def parse_color(input):
"""Parse a color value as defined in `CSS Color Level 3
<http://www.w3.org/TR/css3-color/>`_.
:param input:
A :term:`string`, or a single :term:`component value`.
:returns:
* :obj:`None` if the input is not a valid color value.
(No exception is raised.)
* The string ``'currentColor'`` for the *currentColor* keyword
* Or a :class:`RGBA` object for every other values
(including keywords, HSL and HSLA.)
The alpha channel is clipped to [0, 1]
but red, green, or blue can be out of range
(eg. ``rgb(-10%, 120%, 0%)`` is represented as
``(-0.1, 1.2, 0, 1)``.)
"""
if isinstance(input, basestring):
token = parse_one_component_value(input, skip_comments=True)
else:
token = input
if token.type == 'ident':
return _COLOR_KEYWORDS.get(token.lower_value)
elif token.type == 'hash':
for multiplier, regexp in _HASH_REGEXPS:
match = regexp(token.value)
if match:
r, g, b = [int(group * multiplier, 16) / 255
for group in match.groups()]
return RGBA(r, g, b, 1.)
elif token.type == 'function':
args = _parse_comma_separated(token.arguments)
if args:
name = token.lower_name
if name == 'rgb':
return _parse_rgb(args, alpha=1.)
elif name == 'rgba':
alpha = _parse_alpha(args[3:])
if alpha is not None:
return _parse_rgb(args[:3], alpha)
elif name == 'hsl':
return _parse_hsl(args, alpha=1.)
elif name == 'hsla':
alpha = _parse_alpha(args[3:])
if alpha is not None:
return _parse_hsl(args[:3], alpha)
def _parse_alpha(args):
"""
If args is a list of a single INTEGER or NUMBER token,
retur its value clipped to the 0..1 range
Otherwise, return None.
"""
if len(args) == 1 and args[0].type == 'number':
return min(1, max(0, args[0].value))
def _parse_rgb(args, alpha):
"""
If args is a list of 3 INTEGER tokens or 3 PERCENTAGE tokens,
return RGB values as a tuple of 3 floats in 0..1.
Otherwise, return None.
"""
types = [arg.type for arg in args]
if (types == ['number', 'number', 'number'] and
all(a.is_integer for a in args)):
r, g, b = [arg.int_value / 255 for arg in args[:3]]
return RGBA(r, g, b, alpha)
elif types == ['percentage', 'percentage', 'percentage']:
r, g, b = [arg.value / 100 for arg in args[:3]]
return RGBA(r, g, b, alpha)
def _parse_hsl(args, alpha):
"""
If args is a list of 1 INTEGER token and 2 PERCENTAGE tokens,
return RGB values as a tuple of 3 floats in 0..1.
Otherwise, return None.
"""
types = [arg.type for arg in args]
if types == ['number', 'percentage', 'percentage'] and args[0].is_integer:
r, g, b = _hsl_to_rgb(args[0].int_value, args[1].value, args[2].value)
return RGBA(r, g, b, alpha)
def _hsl_to_rgb(hue, saturation, lightness):
"""
:param hue: degrees
:param saturation: percentage
:param lightness: percentage
:returns: (r, g, b) as floats in the 0..1 range
"""
hue = (hue / 360) % 1
saturation = min(1, max(0, saturation / 100))
lightness = min(1, max(0, lightness / 100))
# Translated from ABC: http://www.w3.org/TR/css3-color/#hsl-color
def hue_to_rgb(m1, m2, h):
if h < 0:
h += 1
if h > 1:
h -= 1
if h * 6 < 1:
return m1 + (m2 - m1) * h * 6
if h * 2 < 1:
return m2
if h * 3 < 2:
return m1 + (m2 - m1) * (2 / 3 - h) * 6
return m1
if lightness <= 0.5:
m2 = lightness * (saturation + 1)
else:
m2 = lightness + saturation - lightness * saturation
m1 = lightness * 2 - m2
return (
hue_to_rgb(m1, m2, hue + 1 / 3),
hue_to_rgb(m1, m2, hue),
hue_to_rgb(m1, m2, hue - 1 / 3),
)
def _parse_comma_separated(tokens):
"""Parse a list of tokens (typically the content of a function token)
as arguments made of a single token each, separated by mandatory commas,
with optional white space around each argument.
return the argument list without commas or white space;
or None if the function token content do not match the description above.
"""
tokens = [token for token in tokens
if token.type not in ('whitespace', 'comment')]
if not tokens:
return []
if len(tokens) % 2 == 1 and all(token == ',' for token in tokens[1::2]):
return tokens[::2]
_HASH_REGEXPS = (
(2, re.compile('^([\da-f])([\da-f])([\da-f])$', re.I).match),
(1, re.compile('^([\da-f]{2})([\da-f]{2})([\da-f]{2})$', re.I).match),
)
# (r, g, b) in 0..255
_BASIC_COLOR_KEYWORDS = [
('black', (0, 0, 0)),
('silver', (192, 192, 192)),
('gray', (128, 128, 128)),
('white', (255, 255, 255)),
('maroon', (128, 0, 0)),
('red', (255, 0, 0)),
('purple', (128, 0, 128)),
('fuchsia', (255, 0, 255)),
('green', (0, 128, 0)),
('lime', (0, 255, 0)),
('olive', (128, 128, 0)),
('yellow', (255, 255, 0)),
('navy', (0, 0, 128)),
('blue', (0, 0, 255)),
('teal', (0, 128, 128)),
('aqua', (0, 255, 255)),
]
# (r, g, b) in 0..255
_EXTENDED_COLOR_KEYWORDS = [
('aliceblue', (240, 248, 255)),
('antiquewhite', (250, 235, 215)),
('aqua', (0, 255, 255)),
('aquamarine', (127, 255, 212)),
('azure', (240, 255, 255)),
('beige', (245, 245, 220)),
('bisque', (255, 228, 196)),
('black', (0, 0, 0)),
('blanchedalmond', (255, 235, 205)),
('blue', (0, 0, 255)),
('blueviolet', (138, 43, 226)),
('brown', (165, 42, 42)),
('burlywood', (222, 184, 135)),
('cadetblue', (95, 158, 160)),
('chartreuse', (127, 255, 0)),
('chocolate', (210, 105, 30)),
('coral', (255, 127, 80)),
('cornflowerblue', (100, 149, 237)),
('cornsilk', (255, 248, 220)),
('crimson', (220, 20, 60)),
('cyan', (0, 255, 255)),
('darkblue', (0, 0, 139)),
('darkcyan', (0, 139, 139)),
('darkgoldenrod', (184, 134, 11)),
('darkgray', (169, 169, 169)),
('darkgreen', (0, 100, 0)),
('darkgrey', (169, 169, 169)),
('darkkhaki', (189, 183, 107)),
('darkmagenta', (139, 0, 139)),
('darkolivegreen', (85, 107, 47)),
('darkorange', (255, 140, 0)),
('darkorchid', (153, 50, 204)),
('darkred', (139, 0, 0)),
('darksalmon', (233, 150, 122)),
('darkseagreen', (143, 188, 143)),
('darkslateblue', (72, 61, 139)),
('darkslategray', (47, 79, 79)),
('darkslategrey', (47, 79, 79)),
('darkturquoise', (0, 206, 209)),
('darkviolet', (148, 0, 211)),
('deeppink', (255, 20, 147)),
('deepskyblue', (0, 191, 255)),
('dimgray', (105, 105, 105)),
('dimgrey', (105, 105, 105)),
('dodgerblue', (30, 144, 255)),
('firebrick', (178, 34, 34)),
('floralwhite', (255, 250, 240)),
('forestgreen', (34, 139, 34)),
('fuchsia', (255, 0, 255)),
('gainsboro', (220, 220, 220)),
('ghostwhite', (248, 248, 255)),
('gold', (255, 215, 0)),
('goldenrod', (218, 165, 32)),
('gray', (128, 128, 128)),
('green', (0, 128, 0)),
('greenyellow', (173, 255, 47)),
('grey', (128, 128, 128)),
('honeydew', (240, 255, 240)),
('hotpink', (255, 105, 180)),
('indianred', (205, 92, 92)),
('indigo', (75, 0, 130)),
('ivory', (255, 255, 240)),
('khaki', (240, 230, 140)),
('lavender', (230, 230, 250)),
('lavenderblush', (255, 240, 245)),
('lawngreen', (124, 252, 0)),
('lemonchiffon', (255, 250, 205)),
('lightblue', (173, 216, 230)),
('lightcoral', (240, 128, 128)),
('lightcyan', (224, 255, 255)),
('lightgoldenrodyellow', (250, 250, 210)),
('lightgray', (211, 211, 211)),
('lightgreen', (144, 238, 144)),
('lightgrey', (211, 211, 211)),
('lightpink', (255, 182, 193)),
('lightsalmon', (255, 160, 122)),
('lightseagreen', (32, 178, 170)),
('lightskyblue', (135, 206, 250)),
('lightslategray', (119, 136, 153)),
('lightslategrey', (119, 136, 153)),
('lightsteelblue', (176, 196, 222)),
('lightyellow', (255, 255, 224)),
('lime', (0, 255, 0)),
('limegreen', (50, 205, 50)),
('linen', (250, 240, 230)),
('magenta', (255, 0, 255)),
('maroon', (128, 0, 0)),
('mediumaquamarine', (102, 205, 170)),
('mediumblue', (0, 0, 205)),
('mediumorchid', (186, 85, 211)),
('mediumpurple', (147, 112, 219)),
('mediumseagreen', (60, 179, 113)),
('mediumslateblue', (123, 104, 238)),
('mediumspringgreen', (0, 250, 154)),
('mediumturquoise', (72, 209, 204)),
('mediumvioletred', (199, 21, 133)),
('midnightblue', (25, 25, 112)),
('mintcream', (245, 255, 250)),
('mistyrose', (255, 228, 225)),
('moccasin', (255, 228, 181)),
('navajowhite', (255, 222, 173)),
('navy', (0, 0, 128)),
('oldlace', (253, 245, 230)),
('olive', (128, 128, 0)),
('olivedrab', (107, 142, 35)),
('orange', (255, 165, 0)),
('orangered', (255, 69, 0)),
('orchid', (218, 112, 214)),
('palegoldenrod', (238, 232, 170)),
('palegreen', (152, 251, 152)),
('paleturquoise', (175, 238, 238)),
('palevioletred', (219, 112, 147)),
('papayawhip', (255, 239, 213)),
('peachpuff', (255, 218, 185)),
('peru', (205, 133, 63)),
('pink', (255, 192, 203)),
('plum', (221, 160, 221)),
('powderblue', (176, 224, 230)),
('purple', (128, 0, 128)),
('red', (255, 0, 0)),
('rosybrown', (188, 143, 143)),
('royalblue', (65, 105, 225)),
('saddlebrown', (139, 69, 19)),
('salmon', (250, 128, 114)),
('sandybrown', (244, 164, 96)),
('seagreen', (46, 139, 87)),
('seashell', (255, 245, 238)),
('sienna', (160, 82, 45)),
('silver', (192, 192, 192)),
('skyblue', (135, 206, 235)),
('slateblue', (106, 90, 205)),
('slategray', (112, 128, 144)),
('slategrey', (112, 128, 144)),
('snow', (255, 250, 250)),
('springgreen', (0, 255, 127)),
('steelblue', (70, 130, 180)),
('tan', (210, 180, 140)),
('teal', (0, 128, 128)),
('thistle', (216, 191, 216)),
('tomato', (255, 99, 71)),
('turquoise', (64, 224, 208)),
('violet', (238, 130, 238)),
('wheat', (245, 222, 179)),
('white', (255, 255, 255)),
('whitesmoke', (245, 245, 245)),
('yellow', (255, 255, 0)),
('yellowgreen', (154, 205, 50)),
]
# (r, g, b, a) in 0..1 or a string marker
_SPECIAL_COLOR_KEYWORDS = {
'currentcolor': 'currentColor',
'transparent': RGBA(0., 0., 0., 0.),
}
# RGBA namedtuples of (r, g, b, a) in 0..1 or a string marker
_COLOR_KEYWORDS = _SPECIAL_COLOR_KEYWORDS.copy()
_COLOR_KEYWORDS.update(
# 255 maps to 1, 0 to 0, the rest is linear.
(keyword, RGBA(r / 255., g / 255., b / 255., 1.))
for keyword, (r, g, b) in _BASIC_COLOR_KEYWORDS + _EXTENDED_COLOR_KEYWORDS)

View File

@ -0,0 +1,156 @@
[
"", null,
" \n", null,
"odd", [2, 1],
"even", [2, 0],
"ödd", null,
"éven", null,
" /**/\t OdD /**/\n", [2, 1],
" /**/\t EveN /**/\n", [2, 0],
"3", [0, 3],
"+2 ", [0, 2],
" -14 ", [0, -14],
"+ 2 ", null,
"- 14 ", null,
"3.1", null,
"3N", [3, 0],
"+2N ", [2, 0],
" -14n ", [-14, 0],
"+ 2N ", null,
"- 14N ", null,
"3.1N", null,
"3 n", null,
" N", [1, 0],
" +n", [1, 0],
" -n", [-1, 0],
"+ n", null,
"- n", null,
"3N+1", [3, 1],
"+2n+1 ", [2, 1],
" -14n+1 ", [-14, 1],
"+ 2N+1 ", null,
"- 14n+1 ", null,
"3.1n+1", null,
"3 n+1", null,
" n+1", [1, 1],
" +N+1", [1, 1],
" -n+1", [-1, 1],
"+ N+1", null,
"- N+1", null,
"3n-1", [3, -1],
"+2N-1 ", [2, -1],
" -14n-1 ", [-14, -1],
"+ 2N-1 ", null,
"- 14N-1 ", null,
"3.1n-1", null,
"3 n-1", null,
"3n-1foo", null,
" n-1", [1, -1],
" +n-1", [1, -1],
" -n-1", [-1, -1],
"+ n-1", null,
"- n-1", null,
" +n-1foo", null,
" -n-1foo", null,
"3N +1", [3, 1],
"+2N +1 ", [2, 1],
" -14n +1 ", [-14, 1],
"+ 2N +1 ", null,
"- 14n +1 ", null,
"3.1N +1", null,
"3 n +1", null,
"3n foo", null,
"3n + foo", null,
" n +1", [1, 1],
" +N +1", [1, 1],
" -n +1", [-1, 1],
"+ n +1", null,
"- N +1", null,
"3N -1", [3, -1],
"+2n -1 ", [2, -1],
" -14n -1 ", [-14, -1],
"+ 2n -1 ", null,
"- 14N -1 ", null,
"3.1N -1", null,
"3 N -1", null,
" N -1", [1, -1],
" +N -1", [1, -1],
" -n -1", [-1, -1],
"+ n -1", null,
"- n -1", null,
"3n+ 1", [3, 1],
"+2n+ 1 ", [2, 1],
" -14n+ 1 ", [-14, 1],
"+ 2n+ 1 ", null,
"- 14N+ 1 ", null,
"3.1n+ 1", null,
"3 N+ 1", null,
" N+ 1", [1, 1],
" +N+ 1", [1, 1],
" -N+ 1", [-1, 1],
"+ n+ 1", null,
"- N+ 1", null,
"3n- 1", [3, -1],
"+2N- 1 ", [2, -1],
" -14N- 1 ", [-14, -1],
"+ 2N- 1 ", null,
"- 14n- 1 ", null,
"3.1n- 1", null,
"3 n- 1", null,
" N- 1", [1, -1],
" +N- 1", [1, -1],
" -n- 1", [-1, -1],
"+ n- 1", null,
"- N- 1", null,
"3N + 1", [3, 1],
"+2N + 1 ", [2, 1],
" -14n + 1 ", [-14, 1],
"+ 2n + 1 ", null,
"- 14N + 1 ", null,
"3.1n + 1", null,
"3 N + 1", null,
" n + 1", [1, 1],
" +n + 1", [1, 1],
" -N + 1", [-1, 1],
"+ N + 1", null,
"- N + 1", null,
"3N - 1", [3, -1],
"+2n - 1 ", [2, -1],
" -14n - 1 ", [-14, -1],
"+ 2N - 1 ", null,
"- 14N - 1 ", null,
"3.1N - 1", null,
"3 n - 1", null,
" N - 1", [1, -1],
" +n - 1", [1, -1],
" -n - 1", [-1, -1],
"+ N - 1", null,
"- N - 1", null
]

View File

@ -0,0 +1,8 @@
Written in 2013 by Simon Sapin.
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this work to the public domain worldwide.
This work is distributed without any warranty.
See the CC0 Public Domain Dedication:
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1,301 @@
CSS parsing tests
#################
This repository contains implementation-independent test for CSS parsers,
based on the 2013 draft of the `CSS Syntax Level 3`_ specification.
.. _CSS Syntax Level 3: http://dev.w3.org/csswg/css-syntax-3/
The upstream repository for these tests is at
https://github.com/SimonSapin/css-parsing-tests
Projects using this
===================
CSS parsers using these tests:
* `tinycss2 <https://github.com/SimonSapin/tinycss2>`_ (Python)
* `rust-cssparser <https://github.com/mozilla-servo/rust-cssparser>`_
(Rust, used in `Servo <https://github.com/mozilla/servo/>`_)
* `Crass <https://github.com/rgrove/crass/>`_ (Ruby)
Importing
=========
The recommended way to use these tests in an implementation
is to import them with git-subtree_.
.. _git-subtree: https://github.com/git/git/tree/master/contrib/subtree
To import the first time to a ``./css-parsing-tests`` sub-directory,
run this from the top-level of a git repository::
git subtree add -P css-parsing-tests https://github.com/SimonSapin/css-parsing-tests.git master
Later, to merge changes made in the upstream repository, run::
git subtree pull -P css-parsing-tests https://github.com/SimonSapin/css-parsing-tests.git master
Test files
==========
CSS Syntax specification describes a number of "functions".
Each ``.json`` file in this repository corresponds to such a function.
The files are encoded as UTF-8
and each contain a JSON array with an even number of items,
where each pair of items is one function input
associated with the expected result.
``component_value_list.json``
Tests `Parse a list of component values
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-list-of-component-values>`_.
The Unicode input is represented by a JSON string,
the output as an array of `component values`_ as described below.
``component_value_list.json``
Tests `Parse a component value
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-component-value>`_.
The Unicode input is represented by a JSON string,
the output as a `component value`_.
``declaration_list.json``
Tests `Parse a list of declarations
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-list-of-declarations>`_.
The Unicode input is represented by a JSON string,
the output as an array of declarations_ and at-rules_.
``one_declaration.json``
Tests `Parse a declaration
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-declaration>`_.
The Unicode input is represented by a JSON string,
the output as a declaration_.
``one_rule.json``
Tests `Parse a rule
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-rule>`_.
The Unicode input is represented by a JSON string,
the output as a `qualified rule`_ or at-rule_.
``rule_list.json``
Tests `Parse a list of rules
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-list-of-rules>`_.
The Unicode input is represented by a JSON string,
the output as a list of `qualified rules`_ or at-rules_.
``stylesheet.json``
Tests `Parse a stylesheet
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-stylesheet>`_.
The Unicode input is represented by a JSON string,
the output as a list of `qualified rules`_ or at-rules_.
``stylesheet_bytes.json``
Tests `Parse a stylesheet
<http://dev.w3.org/csswg/css-syntax-3/#parse-a-stylesheet>`_
together with `The input byte stream
<http://dev.w3.org/csswg/css-syntax/#input-byte-stream>`_.
The input is represented as a JSON object containing:
* A required ``css_bytes``, the input byte string,
represented as a JSON string where code points U+0000 to U+00FF
represent bytes of the same value.
* An optional ``protocol_encoding``,
a protocol encoding label as a JSON string, or null.
* An optional ``environment_encoding``,
an environment encoding label as a JSON string, or null.
* An optional ``comment`` that is ignored.
The output is represented a list of `qualified rules`_ or at-rules_.
``color3.json``
Tests the ``<color>`` syntax `defined in CSS Color Level 3
<http://www.w3.org/TR/css3-color/#colorunits>`_.
The Unicode input is represented by a JSON string,
the output as one of:
* null if the input is not a valid color in CSS syntax
* The string "currentColor" for the currentColor keyword
* An array of length 4 for every other values:
four (floating point) numbers for the Red, Green, Blue and Alpha channel.
Each value is between 0 and 1.
``color3_hsl.json``
Same as ``color3.json``.
This file is generated by the ``make_color3_hsl.py`` Python script.
``color3_keywords.json``
Same as ``color3.json``,
except that the values for the Red, Green and Blue channel
are between 0 and 255.
This file is generated by the ``make_color3_keywords.py`` Python script.
``An+B.json``
Tests the `An+B <http://dev.w3.org/csswg/css-syntax/#the-anb-type>`_
syntax defined in CSS Syntax Level 3.
This `differs <http://dev.w3.org/csswg/css-syntax/#changes>`_ from the
`nth grammar rule <http://www.w3.org/TR/css3-selectors/#nth-child-pseudo>`_
in Selectors Level 3 only in that
``-`` charecters and digits can be escaped in some cases.
The Unicode input is represented by a JSON string,
the output as null for invalid syntax,
or an array of two integers ``[A, B]``.
Result representation
=====================
AST nodes (the results of parsing) are represented in JSON as follow.
This representation was chosen to be compact
(and thus less annoying to write by hand)
while staying unambiguous.
For example, the difference between ``@import`` and ``\@import`` is not lost:
they are represented as ``["at-keyword", "import"]`` and ``["ident", "@import"]``,
respectively.
Rules and declarations
----------------------
.. _at-rule:
.. _at-rules:
.. _qualified rule:
.. _qualified rules:
.. _declaration:
.. _declarations:
At-rule
An array of length 4: the string ``"at-rule"``,
the name (value of the at-keyword) as a string,
the prelude as a nested array of `component values`_,
and the optional block as a nested array of component value, or null.
Qualified rule
An array of length 3: the string ``"qualified rule"``,
the prelude as a nested array of `component values`_,
and the block as a nested array of component value.
Declaration
An array of length 4: the string ``"declaration"``, the name as a string,
the value as a nested array of `component values`_,
and a the important flag as a boolean.
.. _component value:
.. _component values:
Component values
----------------
<ident>
Array of length 2: the string ``"ident"``, and the value as a string.
<at-keyword>
Array of length 2: the string ``"at-keyword"``, and the value as a string.
<hash>
Array of length 3: the string ``"hash"``, the value as a string,
and the type as the string ``"id"`` or ``"unrestricted"``.
<string>
Array of length 2: the string ``"string"``, and the value as a string.
<bad-string>
Array of length 1: the string ``"bad-string"``.
<url>
Array of length 2: the string ``"url"``, and the value as a string.
<bad-url>
Array of length 1: the string ``"bad-url"``.
<delim>
The value as a one-character string.
<number>
Array of length 4: the string ``"number"``, the representation as a string,
the value as a number, and the type as the string ``"integer"`` or ``"number"``.
<percentage>
Array of length 4: the string ``"percentage"``, the representation as a string,
the value as a number, and the type as the string ``"integer"`` or ``"number"``.
<dimension>
Array of length 4: the string ``"dimension"``, the representation as a string,
the value as a number, the type as the string ``"integer"`` or ``"number"``,
and the unit as a string.
<unicode-range>
Array of length 3: the string ``"unicode-range"``,
followed by the *start* and *end* integers as two numbers.
<include-match>
The string ``"~="``.
<dash-match>
The string ``"|="``.
<prefix-match>
The string ``"^="``.
<suffix-match>
The string ``"$="``.
<substring-match>
The string ``"*="``.
<column>
The string ``"||"``.
<whitespace>
The string ``" "`` (a single space.)
<CDO>
The string ``"<!--"``.
<CDC>
The string ``"-->"``.
<colon>
The string ``":"``.
<semicolon>
The string ``";"``.
<comma>
The string ``","``.
{} block
An array of length N+1: the string ``"{}"``
followed by the N `component values`_ of the blocks content.
[] block
An array of length N+1: the string ``"[]"``
followed by the N `component values`_ of the blocks content.
() block
An array of length N+1: the string ``"()"``
followed by the N `component values`_ of the blocks content.
Function
An array of length N+2: the string ``"function"``
and the name of the function as a string
followed by the N `component values`_ of the functions arguments.
<bad-string>
The array of two strings ``["error", "bad-string"]``.
<bad-url>
The array of two strings ``["error", "bad-url"]``.
Unmatched <}>
The array of two strings ``["error", "}"]``.
Unmatched <]>
The array of two strings ``["error", "]"]``.
Unmatched <)>
The array of two strings ``["error", ")"]``.

View File

@ -0,0 +1,142 @@
[
"", null,
" /* hey */\n", null,
"4", null,
"top", null,
"/**/transparent", [0, 0, 0, 0],
"transparent", [0, 0, 0, 0],
" transparent\n", [0, 0, 0, 0],
"TransParent", [0, 0, 0, 0],
"currentColor", "currentColor",
"CURRENTcolor", "currentColor",
"current-Color", null,
"black", [0, 0, 0, 1],
"white", [1, 1, 1, 1],
"fuchsia", [1, 0, 1, 1],
"cyan", [0, 1, 1, 1],
"CyAn", [0, 1, 1, 1],
"#", null,
"#f", null,
"#ff", null,
"#fff", [1, 1, 1, 1],
"#ffg", null,
"#ffff", null,
"#fffff", null,
"#ffffff", [1, 1, 1, 1],
"#fffffg", null,
"#fffffff", null,
"#ffffffff", null,
"#fffffffff", null,
"#FFCc99", [1, 0.8, 0.6, 1],
"#369", [0.2, 0.4, 0.6, 1],
"rgb(00, 51, 102)", [0, 0.2, 0.4, 1],
"r\\gb(00, 51, 102)", [0, 0.2, 0.4, 1],
"r\\67 b(00, 51, 102)", [0, 0.2, 0.4, 1],
"RGB(153, 204, 255)", [0.6, 0.8, 1, 1],
"rgB(0, 0, 0)", [0, 0, 0, 1],
"rgB(0, 51, 255)", [0, 0.2, 1, 1],
"rgb(0,51,255)", [0, 0.2, 1, 1],
"rgb(0\t, 51 ,255)", [0, 0.2, 1, 1],
"rgb(/* R */0, /* G */51, /* B */255)", [0, 0.2, 1, 1],
"rgb(-51, 306, 0)", [-0.2, 1.2, 0, 1],
"rgb(42%, 3%, 50%)", [0.42, 0.03, 0.5, 1],
"RGB(100%, 100%, 100%)", [1, 1, 1, 1],
"rgB(0%, 0%, 0%)", [0, 0, 0, 1],
"rgB(10%, 20%, 30%)", [0.1, 0.2, 0.3, 1],
"rgb(10%,20%,30%)", [0.1, 0.2, 0.3, 1],
"rgb(10%\t, 20% ,30%)", [0.1, 0.2, 0.3, 1],
"rgb(/* R */ 10%, /* G */ 20%, /* B */ 30%)", [0.1, 0.2, 0.3, 1],
"rgb(-12%, 110%, 1400%)", [-0.12, 1.1, 14, 1],
"rgb(10%, 50%, 0)", null,
"rgb(255, 50%, 0%)", null,
"rgb(0, 0 0)", null,
"rgb(0, 0, 0deg)", null,
"rgb(0, 0, light)", null,
"rgb()", null,
"rgb(0)", null,
"rgb(0, 0)", null,
"rgb(0, 0, 0, 0)", null,
"rgb(0%)", null,
"rgb(0%, 0%)", null,
"rgb(0%, 0%, 0%, 0%)", null,
"rgb(0%, 0%, 0%, 0)", null,
"rgba(0, 0, 0, 0)", [0, 0, 0, 0],
"rgba(204, 0, 102, 0.3)", [0.8, 0, 0.4, 0.3],
"RGBA(255, 255, 255, 0)", [1, 1, 1, 0],
"rgBA(0, 51, 255, 1)", [0, 0.2, 1, 1],
"rgba(0, 51, 255, 1.1)", [0, 0.2, 1, 1],
"rgba(0, 51, 255, 37)", [0, 0.2, 1, 1],
"rgba(0, 51, 255, 0.42)", [0, 0.2, 1, 0.42],
"rgba(0, 51, 255, 0)", [0, 0.2, 1, 0],
"rgba(0, 51, 255, -0.1)", [0, 0.2, 1, 0],
"rgba(0, 51, 255, -139)", [0, 0.2, 1, 0],
"rgba(42%, 3%, 50%, 0.3)", [0.42, 0.03, 0.5, 0.3],
"RGBA(100%, 100%, 100%, 0)", [1, 1, 1, 0],
"rgBA(0%, 20%, 100%, 1)", [0, 0.2, 1, 1],
"rgba(0%, 20%, 100%, 1.1)", [0, 0.2, 1, 1],
"rgba(0%, 20%, 100%, 37)", [0, 0.2, 1, 1],
"rgba(0%, 20%, 100%, 0.42)", [0, 0.2, 1, 0.42],
"rgba(0%, 20%, 100%, 0)", [0, 0.2, 1, 0],
"rgba(0%, 20%, 100%, -0.1)", [0, 0.2, 1, 0],
"rgba(0%, 20%, 100%, -139)", [0, 0.2, 1, 0],
"rgba(255, 255, 255, 0%)", null,
"rgba(10%, 50%, 0, 1)", null,
"rgba(255, 50%, 0%, 1)", null,
"rgba(0, 0, 0 0)", null,
"rgba(0, 0, 0, 0deg)", null,
"rgba(0, 0, 0, light)", null,
"rgba()", null,
"rgba(0)", null,
"rgba(0, 0, 0)", null,
"rgba(0, 0, 0, 0, 0)", null,
"rgba(0%)", null,
"rgba(0%, 0%)", null,
"rgba(0%, 0%, 0%)", null,
"rgba(0%, 0%, 0%, 0%)", null,
"rgba(0%, 0%, 0%, 0%, 0%)", null,
"HSL(0, 0%, 0%)", [0, 0, 0, 1],
"hsL(0, 100%, 50%)", [1, 0, 0, 1],
"hsl(60, 100%, 37.5%)", [0.75, 0.75, 0, 1],
"hsl(780, 100%, 37.5%)", [0.75, 0.75, 0, 1],
"hsl(-300, 100%, 37.5%)", [0.75, 0.75, 0, 1],
"hsl(300, 50%, 50%)", [0.75, 0.25, 0.75, 1],
"hsl(10, 50%, 0)", null,
"hsl(50%, 50%, 0%)", null,
"hsl(0, 0% 0%)", null,
"hsl(30deg, 100%, 100%)", null,
"hsl(0, 0%, light)", null,
"hsl()", null,
"hsl(0)", null,
"hsl(0, 0%)", null,
"hsl(0, 0%, 0%, 0%)", null,
"HSLA(-300, 100%, 37.5%, 1)", [0.75, 0.75, 0, 1],
"hsLA(-300, 100%, 37.5%, 12)", [0.75, 0.75, 0, 1],
"hsla(-300, 100%, 37.5%, 0.2)", [0.75, 0.75, 0, 0.2],
"hsla(-300, 100%, 37.5%, 0)", [0.75, 0.75, 0, 0],
"hsla(-300, 100%, 37.5%, -3)", [0.75, 0.75, 0, 0],
"hsla(10, 50%, 0, 1)", null,
"hsla(50%, 50%, 0%, 1)", null,
"hsla(0, 0% 0%, 1)", null,
"hsla(30deg, 100%, 100%, 1)", null,
"hsla(0, 0%, light, 1)", null,
"hsla()", null,
"hsla(0)", null,
"hsla(0, 0%)", null,
"hsla(0, 0%, 0%, 50%)", null,
"hsla(0, 0%, 0%, 1, 0%)", null,
"cmyk(0, 0, 0, 0)", null
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,803 @@
[
"transparent", [0, 0, 0, 0],
"Transparent", [0, 0, 0, 0],
"\\transparent", [0, 0, 0, 0],
"\\74 ransparent", [0, 0, 0, 0],
"ransparent", null,
"black", [0, 0, 0, 1],
"bLack", [0, 0, 0, 1],
"b\\lack", [0, 0, 0, 1],
"b\\6C ack", [0, 0, 0, 1],
"back", null,
"blac", null,
"silver", [192, 192, 192, 1],
"siLver", [192, 192, 192, 1],
"si\\lver", [192, 192, 192, 1],
"si\\6C ver", [192, 192, 192, 1],
"siver", null,
"gray", [128, 128, 128, 1],
"graY", [128, 128, 128, 1],
"gra\\y", [128, 128, 128, 1],
"gra\\79 ", [128, 128, 128, 1],
"gra", null,
"white", [255, 255, 255, 1],
"whitE", [255, 255, 255, 1],
"whit\\65 ", [255, 255, 255, 1],
"whit", null,
"maroon", [128, 0, 0, 1],
"marooN", [128, 0, 0, 1],
"maroo\\n", [128, 0, 0, 1],
"maroo\\6E ", [128, 0, 0, 1],
"maroo", null,
"red", [255, 0, 0, 1],
"Red", [255, 0, 0, 1],
"\\red", [255, 0, 0, 1],
"\\72 ed", [255, 0, 0, 1],
"ed", null,
"purple", [128, 0, 128, 1],
"pUrple", [128, 0, 128, 1],
"p\\urple", [128, 0, 128, 1],
"p\\75 rple", [128, 0, 128, 1],
"prple", null,
"fuchsia", [255, 0, 255, 1],
"fUchsia", [255, 0, 255, 1],
"f\\uchsia", [255, 0, 255, 1],
"f\\75 chsia", [255, 0, 255, 1],
"fchsia", null,
"green", [0, 128, 0, 1],
"greeN", [0, 128, 0, 1],
"gree\\n", [0, 128, 0, 1],
"gree\\6E ", [0, 128, 0, 1],
"gree", null,
"lime", [0, 255, 0, 1],
"liMe", [0, 255, 0, 1],
"li\\me", [0, 255, 0, 1],
"li\\6D e", [0, 255, 0, 1],
"lie", null,
"olive", [128, 128, 0, 1],
"oLive", [128, 128, 0, 1],
"o\\live", [128, 128, 0, 1],
"o\\6C ive", [128, 128, 0, 1],
"oive", null,
"yellow", [255, 255, 0, 1],
"Yellow", [255, 255, 0, 1],
"\\yellow", [255, 255, 0, 1],
"\\79 ellow", [255, 255, 0, 1],
"ellow", null,
"navy", [0, 0, 128, 1],
"nAvy", [0, 0, 128, 1],
"n\\61 vy", [0, 0, 128, 1],
"nvy", null,
"blue", [0, 0, 255, 1],
"blUe", [0, 0, 255, 1],
"bl\\ue", [0, 0, 255, 1],
"bl\\75 e", [0, 0, 255, 1],
"ble", null,
"teal", [0, 128, 128, 1],
"teaL", [0, 128, 128, 1],
"tea\\l", [0, 128, 128, 1],
"tea\\6C ", [0, 128, 128, 1],
"tea", null,
"aqua", [0, 255, 255, 1],
"Aqua", [0, 255, 255, 1],
"\\61 qua", [0, 255, 255, 1],
"qua", null,
"aliceblue", [240, 248, 255, 1],
"alicebluE", [240, 248, 255, 1],
"aliceblu\\65 ", [240, 248, 255, 1],
"aliceblu", null,
"antiquewhite", [250, 235, 215, 1],
"antiquEwhite", [250, 235, 215, 1],
"antiqu\\65 white", [250, 235, 215, 1],
"antiquwhite", null,
"aqua", [0, 255, 255, 1],
"aquA", [0, 255, 255, 1],
"aqu\\61 ", [0, 255, 255, 1],
"aqu", null,
"aquamarine", [127, 255, 212, 1],
"Aquamarine", [127, 255, 212, 1],
"\\61 quamarine", [127, 255, 212, 1],
"quamarine", null,
"azure", [240, 255, 255, 1],
"aZure", [240, 255, 255, 1],
"a\\zure", [240, 255, 255, 1],
"a\\7A ure", [240, 255, 255, 1],
"aure", null,
"beige", [245, 245, 220, 1],
"beIge", [245, 245, 220, 1],
"be\\ige", [245, 245, 220, 1],
"be\\69 ge", [245, 245, 220, 1],
"bege", null,
"bisque", [255, 228, 196, 1],
"bisquE", [255, 228, 196, 1],
"bisqu\\65 ", [255, 228, 196, 1],
"bisqu", null,
"black", [0, 0, 0, 1],
"blacK", [0, 0, 0, 1],
"blac\\k", [0, 0, 0, 1],
"blac\\6B ", [0, 0, 0, 1],
"blac", null,
"blac", null,
"blanchedalmond", [255, 235, 205, 1],
"blanchedalmOnd", [255, 235, 205, 1],
"blanchedalm\\ond", [255, 235, 205, 1],
"blanchedalm\\6F nd", [255, 235, 205, 1],
"blanchedalmnd", null,
"blue", [0, 0, 255, 1],
"blUe", [0, 0, 255, 1],
"bl\\ue", [0, 0, 255, 1],
"bl\\75 e", [0, 0, 255, 1],
"ble", null,
"blueviolet", [138, 43, 226, 1],
"bluevioLet", [138, 43, 226, 1],
"bluevio\\let", [138, 43, 226, 1],
"bluevio\\6C et", [138, 43, 226, 1],
"bluevioet", null,
"brown", [165, 42, 42, 1],
"broWn", [165, 42, 42, 1],
"bro\\wn", [165, 42, 42, 1],
"bro\\77 n", [165, 42, 42, 1],
"bron", null,
"burlywood", [222, 184, 135, 1],
"buRlywood", [222, 184, 135, 1],
"bu\\rlywood", [222, 184, 135, 1],
"bu\\72 lywood", [222, 184, 135, 1],
"bulywood", null,
"cadetblue", [95, 158, 160, 1],
"cadEtblue", [95, 158, 160, 1],
"cad\\65 tblue", [95, 158, 160, 1],
"cadtblue", null,
"chartreuse", [127, 255, 0, 1],
"cHartreuse", [127, 255, 0, 1],
"c\\hartreuse", [127, 255, 0, 1],
"c\\68 artreuse", [127, 255, 0, 1],
"cartreuse", null,
"chocolate", [210, 105, 30, 1],
"chocoLate", [210, 105, 30, 1],
"choco\\late", [210, 105, 30, 1],
"choco\\6C ate", [210, 105, 30, 1],
"chocoate", null,
"coral", [255, 127, 80, 1],
"corAl", [255, 127, 80, 1],
"cor\\61 l", [255, 127, 80, 1],
"corl", null,
"cornflowerblue", [100, 149, 237, 1],
"cornflOwerblue", [100, 149, 237, 1],
"cornfl\\owerblue", [100, 149, 237, 1],
"cornfl\\6F werblue", [100, 149, 237, 1],
"cornflwerblue", null,
"cornsilk", [255, 248, 220, 1],
"corNsilk", [255, 248, 220, 1],
"cor\\nsilk", [255, 248, 220, 1],
"cor\\6E silk", [255, 248, 220, 1],
"corsilk", null,
"cornsil", null,
"crimson", [220, 20, 60, 1],
"cRimson", [220, 20, 60, 1],
"c\\rimson", [220, 20, 60, 1],
"c\\72 imson", [220, 20, 60, 1],
"cimson", null,
"cyan", [0, 255, 255, 1],
"cYan", [0, 255, 255, 1],
"c\\yan", [0, 255, 255, 1],
"c\\79 an", [0, 255, 255, 1],
"can", null,
"darkblue", [0, 0, 139, 1],
"darkblUe", [0, 0, 139, 1],
"darkbl\\ue", [0, 0, 139, 1],
"darkbl\\75 e", [0, 0, 139, 1],
"darkble", null,
"darblue", null,
"darkcyan", [0, 139, 139, 1],
"darkcyaN", [0, 139, 139, 1],
"darkcya\\n", [0, 139, 139, 1],
"darkcya\\6E ", [0, 139, 139, 1],
"darkcya", null,
"darcyan", null,
"darkgoldenrod", [184, 134, 11, 1],
"dArkgoldenrod", [184, 134, 11, 1],
"d\\61 rkgoldenrod", [184, 134, 11, 1],
"drkgoldenrod", null,
"dargoldenrod", null,
"darkgray", [169, 169, 169, 1],
"dArkgray", [169, 169, 169, 1],
"d\\61 rkgray", [169, 169, 169, 1],
"drkgray", null,
"dargray", null,
"darkgreen", [0, 100, 0, 1],
"darkgrEen", [0, 100, 0, 1],
"darkgr\\65 en", [0, 100, 0, 1],
"darkgren", null,
"dargreen", null,
"darkgrey", [169, 169, 169, 1],
"darKgrey", [169, 169, 169, 1],
"dar\\kgrey", [169, 169, 169, 1],
"dar\\6B grey", [169, 169, 169, 1],
"dargrey", null,
"dargrey", null,
"darkkhaki", [189, 183, 107, 1],
"darkkhakI", [189, 183, 107, 1],
"darkkhak\\i", [189, 183, 107, 1],
"darkkhak\\69 ", [189, 183, 107, 1],
"darkkhak", null,
"darhai", null,
"darkmagenta", [139, 0, 139, 1],
"dArkmagenta", [139, 0, 139, 1],
"d\\61 rkmagenta", [139, 0, 139, 1],
"drkmagenta", null,
"darmagenta", null,
"darkolivegreen", [85, 107, 47, 1],
"darkOlivegreen", [85, 107, 47, 1],
"dark\\olivegreen", [85, 107, 47, 1],
"dark\\6F livegreen", [85, 107, 47, 1],
"darklivegreen", null,
"darolivegreen", null,
"darkorange", [255, 140, 0, 1],
"darkoraNge", [255, 140, 0, 1],
"darkora\\nge", [255, 140, 0, 1],
"darkora\\6E ge", [255, 140, 0, 1],
"darkorage", null,
"darorange", null,
"darkorchid", [153, 50, 204, 1],
"darkorchId", [153, 50, 204, 1],
"darkorch\\id", [153, 50, 204, 1],
"darkorch\\69 d", [153, 50, 204, 1],
"darkorchd", null,
"darorchid", null,
"darkred", [139, 0, 0, 1],
"Darkred", [139, 0, 0, 1],
"\\64 arkred", [139, 0, 0, 1],
"arkred", null,
"darred", null,
"darksalmon", [233, 150, 122, 1],
"Darksalmon", [233, 150, 122, 1],
"\\64 arksalmon", [233, 150, 122, 1],
"arksalmon", null,
"darsalmon", null,
"darkseagreen", [143, 188, 143, 1],
"darKseagreen", [143, 188, 143, 1],
"dar\\kseagreen", [143, 188, 143, 1],
"dar\\6B seagreen", [143, 188, 143, 1],
"darseagreen", null,
"darseagreen", null,
"darkslateblue", [72, 61, 139, 1],
"Darkslateblue", [72, 61, 139, 1],
"\\64 arkslateblue", [72, 61, 139, 1],
"arkslateblue", null,
"darslateblue", null,
"darkslategray", [47, 79, 79, 1],
"dArkslategray", [47, 79, 79, 1],
"d\\61 rkslategray", [47, 79, 79, 1],
"drkslategray", null,
"darslategray", null,
"darkslategrey", [47, 79, 79, 1],
"daRkslategrey", [47, 79, 79, 1],
"da\\rkslategrey", [47, 79, 79, 1],
"da\\72 kslategrey", [47, 79, 79, 1],
"dakslategrey", null,
"darslategrey", null,
"darkturquoise", [0, 206, 209, 1],
"darKturquoise", [0, 206, 209, 1],
"dar\\kturquoise", [0, 206, 209, 1],
"dar\\6B turquoise", [0, 206, 209, 1],
"darturquoise", null,
"darturquoise", null,
"darkviolet", [148, 0, 211, 1],
"darkviOlet", [148, 0, 211, 1],
"darkvi\\olet", [148, 0, 211, 1],
"darkvi\\6F let", [148, 0, 211, 1],
"darkvilet", null,
"darviolet", null,
"deeppink", [255, 20, 147, 1],
"dEeppink", [255, 20, 147, 1],
"d\\65 eppink", [255, 20, 147, 1],
"deppink", null,
"deeppin", null,
"deepskyblue", [0, 191, 255, 1],
"deePskyblue", [0, 191, 255, 1],
"dee\\pskyblue", [0, 191, 255, 1],
"dee\\70 skyblue", [0, 191, 255, 1],
"deeskyblue", null,
"deepsyblue", null,
"dimgray", [105, 105, 105, 1],
"dimGray", [105, 105, 105, 1],
"dim\\gray", [105, 105, 105, 1],
"dim\\67 ray", [105, 105, 105, 1],
"dimray", null,
"dimgrey", [105, 105, 105, 1],
"dimgRey", [105, 105, 105, 1],
"dimg\\rey", [105, 105, 105, 1],
"dimg\\72 ey", [105, 105, 105, 1],
"dimgey", null,
"dodgerblue", [30, 144, 255, 1],
"dOdgerblue", [30, 144, 255, 1],
"d\\odgerblue", [30, 144, 255, 1],
"d\\6F dgerblue", [30, 144, 255, 1],
"ddgerblue", null,
"firebrick", [178, 34, 34, 1],
"firebricK", [178, 34, 34, 1],
"firebric\\k", [178, 34, 34, 1],
"firebric\\6B ", [178, 34, 34, 1],
"firebric", null,
"firebric", null,
"floralwhite", [255, 250, 240, 1],
"floralwhIte", [255, 250, 240, 1],
"floralwh\\ite", [255, 250, 240, 1],
"floralwh\\69 te", [255, 250, 240, 1],
"floralwhte", null,
"forestgreen", [34, 139, 34, 1],
"forestgreEn", [34, 139, 34, 1],
"forestgre\\65 n", [34, 139, 34, 1],
"forestgren", null,
"fuchsia", [255, 0, 255, 1],
"fuChsia", [255, 0, 255, 1],
"fu\\63 hsia", [255, 0, 255, 1],
"fuhsia", null,
"gainsboro", [220, 220, 220, 1],
"gaiNsboro", [220, 220, 220, 1],
"gai\\nsboro", [220, 220, 220, 1],
"gai\\6E sboro", [220, 220, 220, 1],
"gaisboro", null,
"ghostwhite", [248, 248, 255, 1],
"ghostwhIte", [248, 248, 255, 1],
"ghostwh\\ite", [248, 248, 255, 1],
"ghostwh\\69 te", [248, 248, 255, 1],
"ghostwhte", null,
"gold", [255, 215, 0, 1],
"Gold", [255, 215, 0, 1],
"\\gold", [255, 215, 0, 1],
"\\67 old", [255, 215, 0, 1],
"old", null,
"goldenrod", [218, 165, 32, 1],
"goldenRod", [218, 165, 32, 1],
"golden\\rod", [218, 165, 32, 1],
"golden\\72 od", [218, 165, 32, 1],
"goldenod", null,
"gray", [128, 128, 128, 1],
"grAy", [128, 128, 128, 1],
"gr\\61 y", [128, 128, 128, 1],
"gry", null,
"green", [0, 128, 0, 1],
"gReen", [0, 128, 0, 1],
"g\\reen", [0, 128, 0, 1],
"g\\72 een", [0, 128, 0, 1],
"geen", null,
"greenyellow", [173, 255, 47, 1],
"greenyEllow", [173, 255, 47, 1],
"greeny\\65 llow", [173, 255, 47, 1],
"greenyllow", null,
"grey", [128, 128, 128, 1],
"gRey", [128, 128, 128, 1],
"g\\rey", [128, 128, 128, 1],
"g\\72 ey", [128, 128, 128, 1],
"gey", null,
"honeydew", [240, 255, 240, 1],
"hoNeydew", [240, 255, 240, 1],
"ho\\neydew", [240, 255, 240, 1],
"ho\\6E eydew", [240, 255, 240, 1],
"hoeydew", null,
"hotpink", [255, 105, 180, 1],
"hotpiNk", [255, 105, 180, 1],
"hotpi\\nk", [255, 105, 180, 1],
"hotpi\\6E k", [255, 105, 180, 1],
"hotpik", null,
"hotpin", null,
"indianred", [205, 92, 92, 1],
"indiAnred", [205, 92, 92, 1],
"indi\\61 nred", [205, 92, 92, 1],
"indinred", null,
"indigo", [75, 0, 130, 1],
"indigO", [75, 0, 130, 1],
"indig\\o", [75, 0, 130, 1],
"indig\\6F ", [75, 0, 130, 1],
"indig", null,
"ivory", [255, 255, 240, 1],
"ivoRy", [255, 255, 240, 1],
"ivo\\ry", [255, 255, 240, 1],
"ivo\\72 y", [255, 255, 240, 1],
"ivoy", null,
"khaki", [240, 230, 140, 1],
"khakI", [240, 230, 140, 1],
"khak\\i", [240, 230, 140, 1],
"khak\\69 ", [240, 230, 140, 1],
"khak", null,
"hai", null,
"lavender", [230, 230, 250, 1],
"Lavender", [230, 230, 250, 1],
"\\lavender", [230, 230, 250, 1],
"\\6C avender", [230, 230, 250, 1],
"avender", null,
"lavenderblush", [255, 240, 245, 1],
"lavEnderblush", [255, 240, 245, 1],
"lav\\65 nderblush", [255, 240, 245, 1],
"lavnderblush", null,
"lawngreen", [124, 252, 0, 1],
"lAwngreen", [124, 252, 0, 1],
"l\\61 wngreen", [124, 252, 0, 1],
"lwngreen", null,
"lemonchiffon", [255, 250, 205, 1],
"lemonchiffoN", [255, 250, 205, 1],
"lemonchiffo\\n", [255, 250, 205, 1],
"lemonchiffo\\6E ", [255, 250, 205, 1],
"lemonchiffo", null,
"lightblue", [173, 216, 230, 1],
"ligHtblue", [173, 216, 230, 1],
"lig\\htblue", [173, 216, 230, 1],
"lig\\68 tblue", [173, 216, 230, 1],
"ligtblue", null,
"lightcoral", [240, 128, 128, 1],
"lightCoral", [240, 128, 128, 1],
"light\\63 oral", [240, 128, 128, 1],
"lightoral", null,
"lightcyan", [224, 255, 255, 1],
"lightCyan", [224, 255, 255, 1],
"light\\63 yan", [224, 255, 255, 1],
"lightyan", null,
"lightgoldenrodyellow", [250, 250, 210, 1],
"lightgoLdenrodyellow", [250, 250, 210, 1],
"lightgo\\ldenrodyellow", [250, 250, 210, 1],
"lightgo\\6C denrodyellow", [250, 250, 210, 1],
"lightgodenrodyellow", null,
"lightgray", [211, 211, 211, 1],
"lightgrAy", [211, 211, 211, 1],
"lightgr\\61 y", [211, 211, 211, 1],
"lightgry", null,
"lightgreen", [144, 238, 144, 1],
"lightgreeN", [144, 238, 144, 1],
"lightgree\\n", [144, 238, 144, 1],
"lightgree\\6E ", [144, 238, 144, 1],
"lightgree", null,
"lightgrey", [211, 211, 211, 1],
"Lightgrey", [211, 211, 211, 1],
"\\lightgrey", [211, 211, 211, 1],
"\\6C ightgrey", [211, 211, 211, 1],
"ightgrey", null,
"lightpink", [255, 182, 193, 1],
"lIghtpink", [255, 182, 193, 1],
"l\\ightpink", [255, 182, 193, 1],
"l\\69 ghtpink", [255, 182, 193, 1],
"lghtpink", null,
"lightpin", null,
"lightsalmon", [255, 160, 122, 1],
"lighTsalmon", [255, 160, 122, 1],
"ligh\\tsalmon", [255, 160, 122, 1],
"ligh\\74 salmon", [255, 160, 122, 1],
"lighsalmon", null,
"lightseagreen", [32, 178, 170, 1],
"liGhtseagreen", [32, 178, 170, 1],
"li\\ghtseagreen", [32, 178, 170, 1],
"li\\67 htseagreen", [32, 178, 170, 1],
"lihtseagreen", null,
"lightskyblue", [135, 206, 250, 1],
"lightskyblUe", [135, 206, 250, 1],
"lightskybl\\ue", [135, 206, 250, 1],
"lightskybl\\75 e", [135, 206, 250, 1],
"lightskyble", null,
"lightsyblue", null,
"lightslategray", [119, 136, 153, 1],
"lightslategRay", [119, 136, 153, 1],
"lightslateg\\ray", [119, 136, 153, 1],
"lightslateg\\72 ay", [119, 136, 153, 1],
"lightslategay", null,
"lightslategrey", [119, 136, 153, 1],
"lightslategrEy", [119, 136, 153, 1],
"lightslategr\\65 y", [119, 136, 153, 1],
"lightslategry", null,
"lightsteelblue", [176, 196, 222, 1],
"lightsteelbluE", [176, 196, 222, 1],
"lightsteelblu\\65 ", [176, 196, 222, 1],
"lightsteelblu", null,
"lightyellow", [255, 255, 224, 1],
"lightyelloW", [255, 255, 224, 1],
"lightyello\\w", [255, 255, 224, 1],
"lightyello\\77 ", [255, 255, 224, 1],
"lightyello", null,
"lime", [0, 255, 0, 1],
"limE", [0, 255, 0, 1],
"lim\\65 ", [0, 255, 0, 1],
"lim", null,
"limegreen", [50, 205, 50, 1],
"lImegreen", [50, 205, 50, 1],
"l\\imegreen", [50, 205, 50, 1],
"l\\69 megreen", [50, 205, 50, 1],
"lmegreen", null,
"linen", [250, 240, 230, 1],
"lInen", [250, 240, 230, 1],
"l\\inen", [250, 240, 230, 1],
"l\\69 nen", [250, 240, 230, 1],
"lnen", null,
"magenta", [255, 0, 255, 1],
"mageNta", [255, 0, 255, 1],
"mage\\nta", [255, 0, 255, 1],
"mage\\6E ta", [255, 0, 255, 1],
"mageta", null,
"maroon", [128, 0, 0, 1],
"mAroon", [128, 0, 0, 1],
"m\\61 roon", [128, 0, 0, 1],
"mroon", null,
"mediumaquamarine", [102, 205, 170, 1],
"mediumaqUamarine", [102, 205, 170, 1],
"mediumaq\\uamarine", [102, 205, 170, 1],
"mediumaq\\75 amarine", [102, 205, 170, 1],
"mediumaqamarine", null,
"mediumblue", [0, 0, 205, 1],
"mediuMblue", [0, 0, 205, 1],
"mediu\\mblue", [0, 0, 205, 1],
"mediu\\6D blue", [0, 0, 205, 1],
"mediublue", null,
"mediumorchid", [186, 85, 211, 1],
"mediumorchId", [186, 85, 211, 1],
"mediumorch\\id", [186, 85, 211, 1],
"mediumorch\\69 d", [186, 85, 211, 1],
"mediumorchd", null,
"mediumpurple", [147, 112, 219, 1],
"mediumpurplE", [147, 112, 219, 1],
"mediumpurpl\\65 ", [147, 112, 219, 1],
"mediumpurpl", null,
"mediumseagreen", [60, 179, 113, 1],
"mediumseagReen", [60, 179, 113, 1],
"mediumseag\\reen", [60, 179, 113, 1],
"mediumseag\\72 een", [60, 179, 113, 1],
"mediumseageen", null,
"mediumslateblue", [123, 104, 238, 1],
"mediUmslateblue", [123, 104, 238, 1],
"medi\\umslateblue", [123, 104, 238, 1],
"medi\\75 mslateblue", [123, 104, 238, 1],
"medimslateblue", null,
"mediumspringgreen", [0, 250, 154, 1],
"mediumspRinggreen", [0, 250, 154, 1],
"mediumsp\\ringgreen", [0, 250, 154, 1],
"mediumsp\\72 inggreen", [0, 250, 154, 1],
"mediumspinggreen", null,
"mediumturquoise", [72, 209, 204, 1],
"mediumTurquoise", [72, 209, 204, 1],
"medium\\turquoise", [72, 209, 204, 1],
"medium\\74 urquoise", [72, 209, 204, 1],
"mediumurquoise", null,
"mediumvioletred", [199, 21, 133, 1],
"mediumvIoletred", [199, 21, 133, 1],
"mediumv\\ioletred", [199, 21, 133, 1],
"mediumv\\69 oletred", [199, 21, 133, 1],
"mediumvoletred", null,
"midnightblue", [25, 25, 112, 1],
"midniGhtblue", [25, 25, 112, 1],
"midni\\ghtblue", [25, 25, 112, 1],
"midni\\67 htblue", [25, 25, 112, 1],
"midnihtblue", null,
"mintcream", [245, 255, 250, 1],
"mintcrEam", [245, 255, 250, 1],
"mintcr\\65 am", [245, 255, 250, 1],
"mintcram", null,
"mistyrose", [255, 228, 225, 1],
"mistyroSe", [255, 228, 225, 1],
"mistyro\\se", [255, 228, 225, 1],
"mistyro\\73 e", [255, 228, 225, 1],
"mistyroe", null,
"moccasin", [255, 228, 181, 1],
"moccAsin", [255, 228, 181, 1],
"mocc\\61 sin", [255, 228, 181, 1],
"moccsin", null,
"navajowhite", [255, 222, 173, 1],
"navajowHite", [255, 222, 173, 1],
"navajow\\hite", [255, 222, 173, 1],
"navajow\\68 ite", [255, 222, 173, 1],
"navajowite", null,
"navy", [0, 0, 128, 1],
"naVy", [0, 0, 128, 1],
"na\\vy", [0, 0, 128, 1],
"na\\76 y", [0, 0, 128, 1],
"nay", null,
"oldlace", [253, 245, 230, 1],
"Oldlace", [253, 245, 230, 1],
"\\oldlace", [253, 245, 230, 1],
"\\6F ldlace", [253, 245, 230, 1],
"ldlace", null,
"olive", [128, 128, 0, 1],
"Olive", [128, 128, 0, 1],
"\\olive", [128, 128, 0, 1],
"\\6F live", [128, 128, 0, 1],
"live", null,
"olivedrab", [107, 142, 35, 1],
"olivEdrab", [107, 142, 35, 1],
"oliv\\65 drab", [107, 142, 35, 1],
"olivdrab", null,
"orange", [255, 165, 0, 1],
"orAnge", [255, 165, 0, 1],
"or\\61 nge", [255, 165, 0, 1],
"ornge", null,
"orangered", [255, 69, 0, 1],
"orangeRed", [255, 69, 0, 1],
"orange\\red", [255, 69, 0, 1],
"orange\\72 ed", [255, 69, 0, 1],
"orangeed", null,
"orchid", [218, 112, 214, 1],
"orchId", [218, 112, 214, 1],
"orch\\id", [218, 112, 214, 1],
"orch\\69 d", [218, 112, 214, 1],
"orchd", null,
"palegoldenrod", [238, 232, 170, 1],
"palegoldEnrod", [238, 232, 170, 1],
"palegold\\65 nrod", [238, 232, 170, 1],
"palegoldnrod", null,
"palegreen", [152, 251, 152, 1],
"Palegreen", [152, 251, 152, 1],
"\\palegreen", [152, 251, 152, 1],
"\\70 alegreen", [152, 251, 152, 1],
"alegreen", null,
"paleturquoise", [175, 238, 238, 1],
"paleturquoIse", [175, 238, 238, 1],
"paleturquo\\ise", [175, 238, 238, 1],
"paleturquo\\69 se", [175, 238, 238, 1],
"paleturquose", null,
"palevioletred", [219, 112, 147, 1],
"palevioletrEd", [219, 112, 147, 1],
"palevioletr\\65 d", [219, 112, 147, 1],
"palevioletrd", null,
"papayawhip", [255, 239, 213, 1],
"papayawhiP", [255, 239, 213, 1],
"papayawhi\\p", [255, 239, 213, 1],
"papayawhi\\70 ", [255, 239, 213, 1],
"papayawhi", null,
"peachpuff", [255, 218, 185, 1],
"peacHpuff", [255, 218, 185, 1],
"peac\\hpuff", [255, 218, 185, 1],
"peac\\68 puff", [255, 218, 185, 1],
"peacpuff", null,
"peru", [205, 133, 63, 1],
"perU", [205, 133, 63, 1],
"per\\u", [205, 133, 63, 1],
"per\\75 ", [205, 133, 63, 1],
"per", null,
"pink", [255, 192, 203, 1],
"Pink", [255, 192, 203, 1],
"\\pink", [255, 192, 203, 1],
"\\70 ink", [255, 192, 203, 1],
"ink", null,
"pin", null,
"plum", [221, 160, 221, 1],
"pLum", [221, 160, 221, 1],
"p\\lum", [221, 160, 221, 1],
"p\\6C um", [221, 160, 221, 1],
"pum", null,
"powderblue", [176, 224, 230, 1],
"powdErblue", [176, 224, 230, 1],
"powd\\65 rblue", [176, 224, 230, 1],
"powdrblue", null,
"purple", [128, 0, 128, 1],
"purPle", [128, 0, 128, 1],
"pur\\ple", [128, 0, 128, 1],
"pur\\70 le", [128, 0, 128, 1],
"purle", null,
"red", [255, 0, 0, 1],
"rEd", [255, 0, 0, 1],
"r\\65 d", [255, 0, 0, 1],
"rd", null,
"rosybrown", [188, 143, 143, 1],
"roSybrown", [188, 143, 143, 1],
"ro\\sybrown", [188, 143, 143, 1],
"ro\\73 ybrown", [188, 143, 143, 1],
"roybrown", null,
"royalblue", [65, 105, 225, 1],
"royAlblue", [65, 105, 225, 1],
"roy\\61 lblue", [65, 105, 225, 1],
"roylblue", null,
"saddlebrown", [139, 69, 19, 1],
"saddlebRown", [139, 69, 19, 1],
"saddleb\\rown", [139, 69, 19, 1],
"saddleb\\72 own", [139, 69, 19, 1],
"saddlebown", null,
"salmon", [250, 128, 114, 1],
"saLmon", [250, 128, 114, 1],
"sa\\lmon", [250, 128, 114, 1],
"sa\\6C mon", [250, 128, 114, 1],
"samon", null,
"sandybrown", [244, 164, 96, 1],
"sAndybrown", [244, 164, 96, 1],
"s\\61 ndybrown", [244, 164, 96, 1],
"sndybrown", null,
"seagreen", [46, 139, 87, 1],
"seagreEn", [46, 139, 87, 1],
"seagre\\65 n", [46, 139, 87, 1],
"seagren", null,
"seashell", [255, 245, 238, 1],
"seashelL", [255, 245, 238, 1],
"seashel\\l", [255, 245, 238, 1],
"seashel\\6C ", [255, 245, 238, 1],
"seashel", null,
"sienna", [160, 82, 45, 1],
"Sienna", [160, 82, 45, 1],
"\\sienna", [160, 82, 45, 1],
"\\73 ienna", [160, 82, 45, 1],
"ienna", null,
"silver", [192, 192, 192, 1],
"sIlver", [192, 192, 192, 1],
"s\\ilver", [192, 192, 192, 1],
"s\\69 lver", [192, 192, 192, 1],
"slver", null,
"skyblue", [135, 206, 235, 1],
"skybluE", [135, 206, 235, 1],
"skyblu\\65 ", [135, 206, 235, 1],
"skyblu", null,
"syblue", null,
"slateblue", [106, 90, 205, 1],
"slaTeblue", [106, 90, 205, 1],
"sla\\teblue", [106, 90, 205, 1],
"sla\\74 eblue", [106, 90, 205, 1],
"slaeblue", null,
"slategray", [112, 128, 144, 1],
"slatEgray", [112, 128, 144, 1],
"slat\\65 gray", [112, 128, 144, 1],
"slatgray", null,
"slategrey", [112, 128, 144, 1],
"slateGrey", [112, 128, 144, 1],
"slate\\grey", [112, 128, 144, 1],
"slate\\67 rey", [112, 128, 144, 1],
"slaterey", null,
"snow", [255, 250, 250, 1],
"snOw", [255, 250, 250, 1],
"sn\\ow", [255, 250, 250, 1],
"sn\\6F w", [255, 250, 250, 1],
"snw", null,
"springgreen", [0, 255, 127, 1],
"springgrEen", [0, 255, 127, 1],
"springgr\\65 en", [0, 255, 127, 1],
"springgren", null,
"steelblue", [70, 130, 180, 1],
"steelbluE", [70, 130, 180, 1],
"steelblu\\65 ", [70, 130, 180, 1],
"steelblu", null,
"tan", [210, 180, 140, 1],
"Tan", [210, 180, 140, 1],
"\\tan", [210, 180, 140, 1],
"\\74 an", [210, 180, 140, 1],
"an", null,
"teal", [0, 128, 128, 1],
"teAl", [0, 128, 128, 1],
"te\\61 l", [0, 128, 128, 1],
"tel", null,
"thistle", [216, 191, 216, 1],
"tHistle", [216, 191, 216, 1],
"t\\histle", [216, 191, 216, 1],
"t\\68 istle", [216, 191, 216, 1],
"tistle", null,
"tomato", [255, 99, 71, 1],
"Tomato", [255, 99, 71, 1],
"\\tomato", [255, 99, 71, 1],
"\\74 omato", [255, 99, 71, 1],
"omato", null,
"turquoise", [64, 224, 208, 1],
"turqUoise", [64, 224, 208, 1],
"turq\\uoise", [64, 224, 208, 1],
"turq\\75 oise", [64, 224, 208, 1],
"turqoise", null,
"violet", [238, 130, 238, 1],
"viOlet", [238, 130, 238, 1],
"vi\\olet", [238, 130, 238, 1],
"vi\\6F let", [238, 130, 238, 1],
"vilet", null,
"wheat", [245, 222, 179, 1],
"wheaT", [245, 222, 179, 1],
"whea\\t", [245, 222, 179, 1],
"whea\\74 ", [245, 222, 179, 1],
"whea", null,
"white", [255, 255, 255, 1],
"White", [255, 255, 255, 1],
"\\white", [255, 255, 255, 1],
"\\77 hite", [255, 255, 255, 1],
"hite", null,
"whitesmoke", [245, 245, 245, 1],
"wHitesmoke", [245, 245, 245, 1],
"w\\hitesmoke", [245, 245, 245, 1],
"w\\68 itesmoke", [245, 245, 245, 1],
"witesmoke", null,
"whitesmoe", null,
"yellow", [255, 255, 0, 1],
"Yellow", [255, 255, 0, 1],
"\\yellow", [255, 255, 0, 1],
"\\79 ellow", [255, 255, 0, 1],
"ellow", null,
"yellowgreen", [154, 205, 50, 1],
"yellowgreEn", [154, 205, 50, 1],
"yellowgre\\65 n", [154, 205, 50, 1],
"yellowgren", null
]

View File

@ -0,0 +1,426 @@
[
"", [],
"/*/*///** /* **/*//* ", [
"/", "*", "/"
],
"red", [
["ident", "red"]
],
" \t\t\r\n\nRed ", [
" ", ["ident", "Red"], " "
],
"red/* CDC */-->", [
["ident", "red"], "-->"
],
"red-->/* Not CDC */", [
["ident", "red--"], ">"
],
"\\- red0 -red --red -\\-red\\ blue 0red -0red \u0000red _Red .red rêd r\\êd \u007F\u0080\u0081", [
["ident", "-"], " ",
["ident", "red0"], " ",
["ident", "-red"], " ",
"-", ["ident", "-red"], " ",
["ident", "--red blue"], " ",
["dimension", "0", 0, "integer", "red"], " ",
["dimension", "-0", 0, "integer", "red"], " ",
["ident", "\uFFFDred"], " ",
["ident", "_Red"], " ",
".", ["ident", "red"], " ",
["ident", "rêd"], " ",
["ident", "rêd"], " ",
"\u007F", ["ident", "\u0080\u0081"]
],
"\\30red \\00030 red \\30\r\nred \\0000000red \\1100000red \\red \\r ed \\.red \\ red \\\nred \\376\\37 6\\000376\\0000376\\", [
["ident", "0red"], " ",
["ident", "0red"], " ",
["ident", "0red"], " ",
["ident", "\uFFFD0red"], " ",
["ident", "\uFFFD0red"], " ",
["ident", "red"], " ",
["ident", "r"], " ", ["ident", "ed"], " ",
["ident", ".red"], " ",
["ident", " red"], " ",
"\\", " ", ["ident", "red"], " ",
["ident", "Ͷ76Ͷ76\uFFFD"]
],
"rgba0() -rgba() --rgba() -\\-rgba() 0rgba() -0rgba() _rgba() .rgba() rgbâ() \\30rgba() rgba () @rgba() #rgba()", [
["function", "rgba0"], " ",
["function", "-rgba"], " ",
"-", ["function", "-rgba"], " ",
["function", "--rgba"], " ",
["dimension", "0", 0, "integer", "rgba"], ["()"], " ",
["dimension", "-0", 0, "integer", "rgba"], ["()"], " ",
["function", "_rgba"], " ",
".", ["function", "rgba"], " ",
["function", "rgbâ"], " ",
["function", "0rgba"], " ",
["ident", "rgba"], " ", ["()"], " ",
["at-keyword", "rgba"], ["()"], " ",
["hash", "rgba", "id"], ["()"]
],
"@media0 @-Media @--media @-\\-media @0media @-0media @_media @.media @medİa @\\30 media\\", [
["at-keyword", "media0"], " ",
["at-keyword", "-Media"], " ",
"@", "-", ["ident", "-media"], " ",
["at-keyword", "--media"], " ",
"@", ["dimension", "0", 0, "integer", "media"], " ",
"@", ["dimension", "-0", 0, "integer", "media"], " ",
["at-keyword", "_media"], " ",
"@", ".", ["ident", "media"], " ",
["at-keyword", "medİa"], " ",
["at-keyword", "0media\uFFFD"]
],
"#red0 #-Red #--red #-\\-red #0red #-0red #_Red #.red #rêd #êrd #\\.red\\", [
["hash", "red0", "id"], " ",
["hash", "-Red", "id"], " ",
["hash", "--red", "unrestricted"], " ",
["hash", "--red", "id"], " ",
["hash", "0red", "unrestricted"], " ",
["hash", "-0red", "unrestricted"], " ",
["hash", "_Red", "id"], " ",
"#", ".", ["ident", "red"], " ",
["hash", "rêd", "id"], " ",
["hash", "êrd", "id"], " ",
["hash", ".red\uFFFD", "id"]
],
"p[example=\"\\\nfoo(int x) {\\\n this.x = x;\\\n}\\\n\"]", [
["ident", "p"], ["[]",
["ident", "example"], "=", ["string", "foo(int x) { this.x = x;}"]
]
],
"'' 'Lorem \"îpsum\"' 'a\\\nb' 'a\nb 'eof", [
["string", ""], " ",
["string", "Lorem \"îpsum\""], " ",
["string", "ab"], " ",
["error", "bad-string"], " ", ["ident", "b"], " ",
["string", "eof"]
],
"\"\" \"Lorem 'îpsum'\" \"a\\\nb\" \"a\nb \"eof", [
["string", ""], " ",
["string", "Lorem 'îpsum'"], " ",
["string", "ab"], " ",
["error", "bad-string"], " ", ["ident", "b"], " ",
["string", "eof"]
],
"\"Lo\\rem \\130 ps\\u m\" '\\376\\37 6\\000376\\0000376\\", [
["string", "Lorem İpsu m"], " ",
["string", "Ͷ76Ͷ76"]
],
"url( '') url('Lorem \"îpsum\"'\n) url('a\\\nb' ) url('a\nb' \\){ ) url('eof", [
["url", ""], " ",
["url", "Lorem \"îpsum\""], " ",
["url", "ab"], " ",
["error", "bad-url"], " ",
["url", "eof"]
],
"url(", [
["url", ""]
],
"url( \t", [
["url", ""]
],
"url(\"\") url(\"Lorem 'îpsum'\"\n) url(\"a\\\nb\" ) url(\"a\nb\" \\){ ) url(\"eof", [
["url", ""], " ",
["url", "Lorem 'îpsum'"], " ",
["url", "ab"], " ",
["error", "bad-url"], " ",
["url", "eof"]
],
"url(\"Lo\\rem \\130 ps\\u m\") url('\\376\\37 6\\000376\\0000376\\", [
["url", "Lorem İpsu m"], " ",
["url", "Ͷ76Ͷ76"]
],
"URL(foo) Url(foo) ûrl(foo) url (foo) url\\ (foo) url(\t 'foo' ", [
["url", "foo"], " ",
["url", "foo"], " ",
["function", "ûrl", ["ident", "foo"]], " ",
["ident", "url"], " ", ["()", ["ident", "foo"]], " ",
["function", "url ", ["ident", "foo"]], " ",
["url", "foo"]
],
"url('a' b) url('c' d)", [["error", "bad-url"], " ", ["error", "bad-url"]],
"url('a\nb') url('c\n", [["error", "bad-url"], " ", ["error", "bad-url"]],
"url() url( \t) url(\n Foô\\030\n!\n) url(\na\nb\n) url(a\\ b) url(a(b) url(a\\(b) url(a'b) url(a\\'b) url(a\"b) url(a\\\"b) url(a\nb) url(a\\\nb) url(a\\a b) url(a\\", [
["url", ""], " ",
["url", ""], " ",
["url", "Foô0!"], " ",
["error", "bad-url"], " ",
["url", "a b"], " ",
["error", "bad-url"], " ",
["url", "a(b"], " ",
["error", "bad-url"], " ",
["url", "a'b"], " ",
["error", "bad-url"], " ",
["url", "a\"b"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["url", "a\nb"], " ",
["url", "a\uFFFD"]
],
"url(\u0000!#$%&*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u009e\u009f\u00a0\u00a1\u00a2", [
["url", "\uFFFD!#$%&*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u009e\u009f\u00a0¡¢"]
],
"url(\u0001) url(\u0002) url(\u0003) url(\u0004) url(\u0005) url(\u0006) url(\u0007) url(\u0008) url(\u000b) url(\u000e) url(\u000f) url(\u0010) url(\u0011) url(\u0012) url(\u0013) url(\u0014) url(\u0015) url(\u0016) url(\u0017) url(\u0018) url(\u0019) url(\u001a) url(\u001b) url(\u001c) url(\u001d) url(\u001e) url(\u001f) url(\u007f)", [
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"], " ",
["error", "bad-url"]
],
"12 +34 -45 .67 +.89 -.01 2.3 +45.0 -0.67", [
["number", "12", 12, "integer"], " ",
["number", "+34", 34, "integer"], " ",
["number", "-45", -45, "integer"], " ",
["number", ".67", 0.67, "number"], " ",
["number", "+.89", 0.89, "number"], " ",
["number", "-.01", -0.01, "number"], " ",
["number", "2.3", 2.3, "number"], " ",
["number", "+45.0", 45, "number"], " ",
["number", "-0.67", -0.67, "number"]
],
"12e2 +34e+1 -45E-0 .68e+3 +.79e-1 -.01E2 2.3E+1 +45.0e6 -0.67e0", [
["number", "12e2", 1200, "number"], " ",
["number", "+34e+1", 340, "number"], " ",
["number", "-45E-0", -45, "number"], " ",
["number", ".68e+3", 680, "number"], " ",
["number", "+.79e-1", 0.079, "number"], " ",
["number", "-.01E2", -1, "number"], " ",
["number", "2.3E+1", 23, "number"], " ",
["number", "+45.0e6", 45000000, "number"], " ",
["number", "-0.67e0", -0.67, "number"]
],
"3. /* Decimal point must have following digits */", [
["number", "3", 3, "integer"], ".", " "
],
"3\\65-2 /* Scientific notation E can not be escaped */", [
["dimension", "3", 3, "integer", "e-2"], " "
],
"3e-2.1 /* Integer exponents only */", [
["number", "3e-2", 0.03, "number"],
["number", ".1", 0.1, "number"], " "
],
"12% +34% -45% .67% +.89% -.01% 2.3% +45.0% -0.67%", [
["percentage", "12", 12, "integer"], " ",
["percentage", "+34", 34, "integer"], " ",
["percentage", "-45", -45, "integer"], " ",
["percentage", ".67", 0.67, "number"], " ",
["percentage", "+.89", 0.89, "number"], " ",
["percentage", "-.01", -0.01, "number"], " ",
["percentage", "2.3", 2.3, "number"], " ",
["percentage", "+45.0", 45, "number"], " ",
["percentage", "-0.67", -0.67, "number"]
],
"12e2% +34e+1% -45E-0% .68e+3% +.79e-1% -.01E2% 2.3E+1% +45.0e6% -0.67e0%", [
["percentage", "12e2", 1200, "number"], " ",
["percentage", "+34e+1", 340, "number"], " ",
["percentage", "-45E-0", -45, "number"], " ",
["percentage", ".68e+3", 680, "number"], " ",
["percentage", "+.79e-1", 0.079, "number"], " ",
["percentage", "-.01E2", -1, "number"], " ",
["percentage", "2.3E+1", 23, "number"], " ",
["percentage", "+45.0e6", 45000000, "number"], " ",
["percentage", "-0.67e0", -0.67, "number"]
],
"12\\% /* Percent sign can not be escaped */", [
["dimension", "12", 12, "integer", "%"], " "
],
"12px +34px -45px .67px +.89px -.01px 2.3px +45.0px -0.67px", [
["dimension", "12", 12, "integer", "px"], " ",
["dimension", "+34", 34, "integer", "px"], " ",
["dimension", "-45", -45, "integer", "px"], " ",
["dimension", ".67", 0.67, "number", "px"], " ",
["dimension", "+.89", 0.89, "number", "px"], " ",
["dimension", "-.01", -0.01, "number", "px"], " ",
["dimension", "2.3", 2.3, "number", "px"], " ",
["dimension", "+45.0", 45, "number", "px"], " ",
["dimension", "-0.67", -0.67, "number", "px"]
],
"12e2px +34e+1px -45E-0px .68e+3px +.79e-1px -.01E2px 2.3E+1px +45.0e6px -0.67e0px", [
["dimension", "12e2", 1200, "number", "px"], " ",
["dimension", "+34e+1", 340, "number", "px"], " ",
["dimension", "-45E-0", -45, "number", "px"], " ",
["dimension", ".68e+3", 680, "number", "px"], " ",
["dimension", "+.79e-1", 0.079, "number", "px"], " ",
["dimension", "-.01E2", -1, "number", "px"], " ",
["dimension", "2.3E+1", 23, "number", "px"], " ",
["dimension", "+45.0e6", 45000000, "number", "px"], " ",
["dimension", "-0.67e0", -0.67, "number", "px"]
],
"12red0 12.0-red 12--red 12-\\-red 120red 12-0red 12\u0000red 12_Red 12.red 12rêd", [
["dimension", "12", 12.0, "integer", "red0"], " ",
["dimension", "12.0", 12.0, "number", "-red"], " ",
["number", "12", 12.0, "integer"], "-", ["ident", "-red"], " ",
["dimension", "12", 12.0, "integer", "--red"], " ",
["dimension", "120", 120.0, "integer", "red"], " ",
["number", "12", 12.0, "integer"], ["dimension", "-0", 0, "integer", "red"], " ",
["dimension", "12", 12.0, "integer", "\uFFFDred"], " ",
["dimension", "12", 12.0, "integer", "_Red"], " ",
["number", "12", 12.0, "integer"], ".", ["ident", "red"], " ",
["dimension", "12", 12.0, "integer", "rêd"]
],
"u+1 U+10 U+100 U+1000 U+10000 U+100000 U+1000000", [
["unicode-range", 1, 1], " ",
["unicode-range", 16, 16], " ",
["unicode-range", 256, 256], " ",
["unicode-range", 4096, 4096], " ",
["unicode-range", 65536, 65536], " ",
["unicode-range", 1048576, 1048576], " ",
["unicode-range", 1048576, 1048576], ["number", "0", 0, "integer"]
],
"u+? u+1? U+10? U+100? U+1000? U+10000? U+100000?", [
["unicode-range", 0, 15], " ",
["unicode-range", 16, 31], " ",
["unicode-range", 256, 271], " ",
["unicode-range", 4096, 4111], " ",
["unicode-range", 65536, 65551], " ",
["unicode-range", 1048576, 1048591], " ",
["unicode-range", 1048576, 1048576], "?"
],
"u+?? U+1?? U+10?? U+100?? U+1000?? U+10000??", [
["unicode-range", 0, 255], " ",
["unicode-range", 256, 511], " ",
["unicode-range", 4096, 4351], " ",
["unicode-range", 65536, 65791], " ",
["unicode-range", 1048576, 1048831], " ",
["unicode-range", 1048576, 1048591], "?"
],
"u+??? U+1??? U+10??? U+100??? U+1000???", [
["unicode-range", 0, 4095], " ",
["unicode-range", 4096, 8191], " ",
["unicode-range", 65536, 69631], " ",
["unicode-range", 1048576, 1052671], " ",
["unicode-range", 1048576, 1048831], "?"
],
"u+???? U+1???? U+10???? U+100????", [
["unicode-range", 0, 65535], " ",
["unicode-range", 65536, 131071], " ",
["unicode-range", 1048576, 1114111], " ",
["unicode-range", 1048576, 1052671], "?"
],
"u+????? U+1????? U+10?????", [
["unicode-range", 0, 1048575], " ",
["unicode-range", 1048576, 2097151], " ",
["unicode-range", 1048576, 1114111], "?"
],
"u+?????? U+1??????", [
["unicode-range", 0, 16777215], " ",
["unicode-range", 1048576, 2097151], "?"
],
"u+1-2 U+100000-2 U+1000000-2 U+10-200000", [
["unicode-range", 1, 2], " ",
["unicode-range", 1048576, 2], " ",
["unicode-range", 1048576, 1048576], ["number", "0", 0, "integer"],
["number", "-2", -2, "integer"], " ",
["unicode-range", 16, 2097152]
],
"ù+12 Ü+12 u +12 U+ 12 U+12 - 20 U+1?2 U+1?-50", [
["ident", "ù"], ["number", "+12", 12, "integer"], " ",
["ident", "Ü"], ["number", "+12", 12, "integer"], " ",
["ident", "u"], " ", ["number", "+12", 12, "integer"], " ",
["ident", "U"], "+", " ", ["number", "12", 12, "integer"], " ",
["unicode-range", 18, 18], " ", "-", " ", ["number", "20", 20, "integer"], " ",
["unicode-range", 16, 31], ["number", "2", 2, "integer"], " ",
["unicode-range", 16, 31], ["number", "-50", -50, "integer"]
],
"~=|=^=$=*=||<!------> |/**/| ~/**/=", [
"~=", "|=", "^=", "$=", "*=", "||", "<!--", "-", "-", "-->",
" ", "|", "|", " ", "~", "="
],
"a:not([href^=http\\:], [href ^=\t'https\\:'\n]) { color: rgba(0%, 100%, 50%); }", [
["ident", "a"], ":", ["function", "not",
["[]",
["ident", "href"], "^=", ["ident", "http:"]
], ",", " ", ["[]",
["ident", "href"], " ", "^=", " ", ["string", "https:"], " "
]
], " ", ["{}",
" ", ["ident", "color"], ":", " ", ["function", "rgba",
["percentage", "0", 0, "integer"], ",", " ",
["percentage", "100", 100, "integer"], ",", " ",
["percentage", "50", 50, "integer"]
], ";", " "
]
],
"@media print { (foo]{bar) }baz", [
["at-keyword", "media"], " ", ["ident", "print"], " ", ["{}",
" ", ["()",
["ident", "foo"], ["error", "]"], ["{}",
["ident", "bar"], ["error", ")"], " "
], ["ident", "baz"]
]
]
]
]

View File

@ -0,0 +1,44 @@
[
"", [],
";; /**/ ; ;", [],
"a:b; c:d 42!important;\n", [
["declaration", "a", [["ident", "b"]], false],
["declaration", "c", [["ident", "d"], " ", ["number", "42", 42, "integer"]], true]
],
"z;a:b", [
["error", "invalid"],
["declaration", "a", [["ident", "b"]], false]
],
"z:x!;a:b", [
["declaration", "z", [["ident", "x"], "!"], false],
["declaration", "a", [["ident", "b"]], false]
],
"a:b; c+:d", [
["declaration", "a", [["ident", "b"]], false],
["error", "invalid"]
],
"@import 'foo.css'; a:b; @import 'bar.css'", [
["at-rule", "import", [" ", ["string", "foo.css"]], null],
["declaration", "a", [["ident", "b"]], false],
["at-rule", "import", [" ", ["string", "bar.css"]], null]
],
"@media screen { div{;}} a:b;; @media print{div{", [
["at-rule", "media", [" ", ["ident", "screen"], " "], [" ", ["ident", "div"], ["{}", ";"]]],
["declaration", "a", [["ident", "b"]], false],
["at-rule", "media", [" ", ["ident", "print"]], [["ident", "div"], ["{}"]]]
],
"@ media screen { div{;}} a:b;; @media print{div{", [
["error", "invalid"],
["at-rule", "media", [" ", ["ident", "print"]], [["ident", "div"], ["{}"]]]
],
"", []
]

View File

@ -0,0 +1,23 @@
import colorsys # It turns out Python already does HSL -> RGB!
def trim(s):
return s if not s.endswith('.0') else s[:-2]
print('[')
print(',\n'.join(
'"hsl%s(%s, %s%%, %s%%%s)", [%s, %s, %s, %s]' % (
('a' if a is not None else '', h,
trim(str(s / 10.)), trim(str(l / 10.)),
', %s' % a if a is not None else '') +
tuple(trim(str(round(v, 10)))
for v in colorsys.hls_to_rgb(h / 360., l / 1000., s / 1000.)) +
(a if a is not None else 1,)
)
for a in [None, 1, .2, 0]
for l in range(0, 1001, 125)
for s in range(0, 1001, 125)
for h in range(0, 360, 30)
))
print(']')

View File

@ -0,0 +1,192 @@
all_keywords = [
('transparent', (0, 0, 0, 0)),
('black', (0, 0, 0, 1)),
('silver', (192, 192, 192, 1)),
('gray', (128, 128, 128, 1)),
('white', (255, 255, 255, 1)),
('maroon', (128, 0, 0, 1)),
('red', (255, 0, 0, 1)),
('purple', (128, 0, 128, 1)),
('fuchsia', (255, 0, 255, 1)),
('green', (0, 128, 0, 1)),
('lime', (0, 255, 0, 1)),
('olive', (128, 128, 0, 1)),
('yellow', (255, 255, 0, 1)),
('navy', (0, 0, 128, 1)),
('blue', (0, 0, 255, 1)),
('teal', (0, 128, 128, 1)),
('aqua', (0, 255, 255, 1)),
('aliceblue', (240, 248, 255, 1)),
('antiquewhite', (250, 235, 215, 1)),
('aqua', (0, 255, 255, 1)),
('aquamarine', (127, 255, 212, 1)),
('azure', (240, 255, 255, 1)),
('beige', (245, 245, 220, 1)),
('bisque', (255, 228, 196, 1)),
('black', (0, 0, 0, 1)),
('blanchedalmond', (255, 235, 205, 1)),
('blue', (0, 0, 255, 1)),
('blueviolet', (138, 43, 226, 1)),
('brown', (165, 42, 42, 1)),
('burlywood', (222, 184, 135, 1)),
('cadetblue', (95, 158, 160, 1)),
('chartreuse', (127, 255, 0, 1)),
('chocolate', (210, 105, 30, 1)),
('coral', (255, 127, 80, 1)),
('cornflowerblue', (100, 149, 237, 1)),
('cornsilk', (255, 248, 220, 1)),
('crimson', (220, 20, 60, 1)),
('cyan', (0, 255, 255, 1)),
('darkblue', (0, 0, 139, 1)),
('darkcyan', (0, 139, 139, 1)),
('darkgoldenrod', (184, 134, 11, 1)),
('darkgray', (169, 169, 169, 1)),
('darkgreen', (0, 100, 0, 1)),
('darkgrey', (169, 169, 169, 1)),
('darkkhaki', (189, 183, 107, 1)),
('darkmagenta', (139, 0, 139, 1)),
('darkolivegreen', (85, 107, 47, 1)),
('darkorange', (255, 140, 0, 1)),
('darkorchid', (153, 50, 204, 1)),
('darkred', (139, 0, 0, 1)),
('darksalmon', (233, 150, 122, 1)),
('darkseagreen', (143, 188, 143, 1)),
('darkslateblue', (72, 61, 139, 1)),
('darkslategray', (47, 79, 79, 1)),
('darkslategrey', (47, 79, 79, 1)),
('darkturquoise', (0, 206, 209, 1)),
('darkviolet', (148, 0, 211, 1)),
('deeppink', (255, 20, 147, 1)),
('deepskyblue', (0, 191, 255, 1)),
('dimgray', (105, 105, 105, 1)),
('dimgrey', (105, 105, 105, 1)),
('dodgerblue', (30, 144, 255, 1)),
('firebrick', (178, 34, 34, 1)),
('floralwhite', (255, 250, 240, 1)),
('forestgreen', (34, 139, 34, 1)),
('fuchsia', (255, 0, 255, 1)),
('gainsboro', (220, 220, 220, 1)),
('ghostwhite', (248, 248, 255, 1)),
('gold', (255, 215, 0, 1)),
('goldenrod', (218, 165, 32, 1)),
('gray', (128, 128, 128, 1)),
('green', (0, 128, 0, 1)),
('greenyellow', (173, 255, 47, 1)),
('grey', (128, 128, 128, 1)),
('honeydew', (240, 255, 240, 1)),
('hotpink', (255, 105, 180, 1)),
('indianred', (205, 92, 92, 1)),
('indigo', (75, 0, 130, 1)),
('ivory', (255, 255, 240, 1)),
('khaki', (240, 230, 140, 1)),
('lavender', (230, 230, 250, 1)),
('lavenderblush', (255, 240, 245, 1)),
('lawngreen', (124, 252, 0, 1)),
('lemonchiffon', (255, 250, 205, 1)),
('lightblue', (173, 216, 230, 1)),
('lightcoral', (240, 128, 128, 1)),
('lightcyan', (224, 255, 255, 1)),
('lightgoldenrodyellow', (250, 250, 210, 1)),
('lightgray', (211, 211, 211, 1)),
('lightgreen', (144, 238, 144, 1)),
('lightgrey', (211, 211, 211, 1)),
('lightpink', (255, 182, 193, 1)),
('lightsalmon', (255, 160, 122, 1)),
('lightseagreen', (32, 178, 170, 1)),
('lightskyblue', (135, 206, 250, 1)),
('lightslategray', (119, 136, 153, 1)),
('lightslategrey', (119, 136, 153, 1)),
('lightsteelblue', (176, 196, 222, 1)),
('lightyellow', (255, 255, 224, 1)),
('lime', (0, 255, 0, 1)),
('limegreen', (50, 205, 50, 1)),
('linen', (250, 240, 230, 1)),
('magenta', (255, 0, 255, 1)),
('maroon', (128, 0, 0, 1)),
('mediumaquamarine', (102, 205, 170, 1)),
('mediumblue', (0, 0, 205, 1)),
('mediumorchid', (186, 85, 211, 1)),
('mediumpurple', (147, 112, 219, 1)),
('mediumseagreen', (60, 179, 113, 1)),
('mediumslateblue', (123, 104, 238, 1)),
('mediumspringgreen', (0, 250, 154, 1)),
('mediumturquoise', (72, 209, 204, 1)),
('mediumvioletred', (199, 21, 133, 1)),
('midnightblue', (25, 25, 112, 1)),
('mintcream', (245, 255, 250, 1)),
('mistyrose', (255, 228, 225, 1)),
('moccasin', (255, 228, 181, 1)),
('navajowhite', (255, 222, 173, 1)),
('navy', (0, 0, 128, 1)),
('oldlace', (253, 245, 230, 1)),
('olive', (128, 128, 0, 1)),
('olivedrab', (107, 142, 35, 1)),
('orange', (255, 165, 0, 1)),
('orangered', (255, 69, 0, 1)),
('orchid', (218, 112, 214, 1)),
('palegoldenrod', (238, 232, 170, 1)),
('palegreen', (152, 251, 152, 1)),
('paleturquoise', (175, 238, 238, 1)),
('palevioletred', (219, 112, 147, 1)),
('papayawhip', (255, 239, 213, 1)),
('peachpuff', (255, 218, 185, 1)),
('peru', (205, 133, 63, 1)),
('pink', (255, 192, 203, 1)),
('plum', (221, 160, 221, 1)),
('powderblue', (176, 224, 230, 1)),
('purple', (128, 0, 128, 1)),
('red', (255, 0, 0, 1)),
('rosybrown', (188, 143, 143, 1)),
('royalblue', (65, 105, 225, 1)),
('saddlebrown', (139, 69, 19, 1)),
('salmon', (250, 128, 114, 1)),
('sandybrown', (244, 164, 96, 1)),
('seagreen', (46, 139, 87, 1)),
('seashell', (255, 245, 238, 1)),
('sienna', (160, 82, 45, 1)),
('silver', (192, 192, 192, 1)),
('skyblue', (135, 206, 235, 1)),
('slateblue', (106, 90, 205, 1)),
('slategray', (112, 128, 144, 1)),
('slategrey', (112, 128, 144, 1)),
('snow', (255, 250, 250, 1)),
('springgreen', (0, 255, 127, 1)),
('steelblue', (70, 130, 180, 1)),
('tan', (210, 180, 140, 1)),
('teal', (0, 128, 128, 1)),
('thistle', (216, 191, 216, 1)),
('tomato', (255, 99, 71, 1)),
('turquoise', (64, 224, 208, 1)),
('violet', (238, 130, 238, 1)),
('wheat', (245, 222, 179, 1)),
('white', (255, 255, 255, 1)),
('whitesmoke', (245, 245, 245, 1)),
('yellow', (255, 255, 0, 1)),
('yellowgreen', (154, 205, 50, 1)),
]
def replace(s, i, r):
i %= len(s)
return s[:i] + r(s[i]) + s[i + 1:]
print('[')
print(',\n'.join(
'"%s", %s' % (css, list(rgba) if valid else 'null')
for i, (keyword, rgba) in enumerate(all_keywords)
for css, valid, run in [
(keyword, True, True),
(replace(keyword, i, str.upper), True, True),
(replace(keyword, i, lambda c: r'\\' + c), True,
keyword[i % len(keyword)] not in 'abcdef'),
(replace(keyword, i, lambda c: r'\\%X ' % ord(c)), True, True),
(replace(keyword, i, lambda c: ''), False, True),
# Kelving sign: u''.lower() == u'k', but should not match in CSS
(keyword.replace('k', u''), False, 'k' in keyword)
]
if run
))
print(']')

View File

@ -0,0 +1,27 @@
[
"", ["error", "empty"],
" ", ["error", "empty"],
"/**/", ["error", "empty"],
" /**/\t/* a */\n\n", ["error", "empty"],
".", ".",
"a", ["ident", "a"],
"/**/ 4px", ["dimension", "4", 4, "integer", "px"],
"rgba(100%, 0%, 50%, .5)", ["function", "rgba",
["percentage", "100", 100, "integer"], ",", " ",
["percentage", "0", 0, "integer"], ",", " ",
["percentage", "50", 50, "integer"], ",", " ",
["number", ".5", 0.5, "number"]
],
" /**/ { foo: bar; @baz [)", ["{}",
" ", ["ident", "foo"], ":", " ", ["ident", "bar"], ";", " ",
["at-keyword", "baz"], " ", ["[]",
["error", ")"]
]
],
".foo", ["error", "extra-input"]
]

View File

@ -0,0 +1,46 @@
[
"", ["error", "empty"],
" /**/\n", ["error", "empty"],
" ;", ["error", "invalid"],
"foo", ["error", "invalid"],
"@foo:", ["error", "invalid"],
"#foo:", ["error", "invalid"],
".foo:", ["error", "invalid"],
"foo*:", ["error", "invalid"],
"foo.. 9000", ["error", "invalid"],
"foo:", ["declaration", "foo", [], false],
"foo :", ["declaration", "foo", [], false],
"\n/**/ foo: ", ["declaration", "foo", [" "], false],
"foo:;", ["declaration", "foo", [";"], false],
" /**/ foo /**/ :", ["declaration", "foo", [], false],
"foo:;bar:;", ["declaration", "foo", [";", ["ident", "bar"], ":", ";"], false],
"foo: 9000 !Important", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " "
], true],
"foo: 9000 ! /**/\t IMPORTant /**/\f", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " "
], true],
"foo: 9000 /* Dotted capital I */!İmportant", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " ", "!", ["ident", "İmportant"]
], false],
"foo: 9000 !important!", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " ", "!", ["ident", "important"], "!"
], false],
"foo: 9000 important", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " ", ["ident", "important"]
], false],
"foo:important", ["declaration", "foo", [
["ident", "important"]
], false],
"foo: 9000 @bar{ !important", ["declaration", "foo", [
" ", ["number", "9000", 9000, "integer"], " ", ["at-keyword", "bar"], ["{}",
" ", "!", ["ident", "important"]
]
], false]
]

View File

@ -0,0 +1,36 @@
[
"", ["error", "empty"],
"foo", ["error", "invalid"],
"foo 4", ["error", "invalid"],
"@foo", ["at-rule", "foo", [], null],
"@foo bar; \t/* comment */", ["at-rule", "foo", [" ", ["ident", "bar"]], null],
" /**/ @foo bar{[(4", ["at-rule", "foo",
[" ", ["ident", "bar"]],
[["[]", ["()", ["number", "4", 4, "integer"]]]]
],
"@foo { bar", ["at-rule", "foo", [" "], [" ", ["ident", "bar"]]],
"@foo [ bar", ["at-rule", "foo", [" ", ["[]", " ", ["ident", "bar"]]], null],
" /**/ div > p { color: #aaa; } /**/ ", ["qualified rule",
[["ident", "div"], " ", ">", " ", ["ident", "p"], " "],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], ";", " "]
],
" /**/ { color: #aaa ", ["qualified rule",
[],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], " "]
],
" /* CDO/CDC are not special */ <!-- --> {", ["qualified rule",
["<!--", " ", "-->", " "], []
],
"div { color: #aaa; } p{}", ["error", "extra-input"],
"div {} -->", ["error", "extra-input"],
"{}a", ["error", "extra-input"]
]

View File

@ -0,0 +1,48 @@
[
"", [],
"foo", [["error", "invalid"]],
"foo 4", [["error", "invalid"]],
"@foo", [["at-rule", "foo", [], null]],
"@foo bar; \t/* comment */", [["at-rule", "foo", [" ", ["ident", "bar"]], null]],
" /**/ @foo bar{[(4", [["at-rule", "foo",
[" ", ["ident", "bar"]],
[["[]", ["()", ["number", "4", 4, "integer"]]]]
]],
"@foo { bar", [["at-rule", "foo", [" "], [" ", ["ident", "bar"]]]],
"@foo [ bar", [["at-rule", "foo", [" ", ["[]", " ", ["ident", "bar"]]], null]],
" /**/ div > p { color: #aaa; } /**/ ", [["qualified rule",
[["ident", "div"], " ", ">", " ", ["ident", "p"], " "],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], ";", " "]
]],
" /**/ { color: #aaa ", [["qualified rule",
[],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], " "]
]],
" /* CDO/CDC are not special */ <!-- --> {", [["qualified rule",
["<!--", " ", "-->", " "], []
]],
"div { color: #aaa; } p{}", [
["qualified rule", [["ident", "div"], " "],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], ";", " "]
],
["qualified rule", [["ident", "p"]], []]
],
"div {} -->", [
["qualified rule", [["ident", "div"], " "], []],
["error", "invalid"]
],
"{}a", [["qualified rule", [], []], ["error", "invalid"]],
"{}@a", [["qualified rule", [], []], ["at-rule", "a", [], null]]
]

View File

@ -0,0 +1,44 @@
[
"", [],
"foo", [["error", "invalid"]],
"foo 4", [["error", "invalid"]],
"@foo", [["at-rule", "foo", [], null]],
"@foo bar; \t/* comment */", [["at-rule", "foo", [" ", ["ident", "bar"]], null]],
" /**/ @foo bar{[(4", [["at-rule", "foo",
[" ", ["ident", "bar"]],
[["[]", ["()", ["number", "4", 4, "integer"]]]]
]],
"@foo { bar", [["at-rule", "foo", [" "], [" ", ["ident", "bar"]]]],
"@foo [ bar", [["at-rule", "foo", [" ", ["[]", " ", ["ident", "bar"]]], null]],
" /**/ div > p { color: #aaa; } /**/ ", [["qualified rule",
[["ident", "div"], " ", ">", " ", ["ident", "p"], " "],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], ";", " "]
]],
" /**/ { color: #aaa ", [["qualified rule",
[],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], " "]
]],
" /* CDO/CDC are ignored between rules */ <!-- --> {", [["qualified rule", [], []]],
" <!-- --> a<!---->{", [["qualified rule", [["ident", "a"], "<!--", "-->"], []]],
"div { color: #aaa; } p{}", [
["qualified rule", [["ident", "div"], " "],
[" ", ["ident", "color"], ":", " ", ["hash", "aaa", "id"], ";", " "]
],
["qualified rule", [["ident", "p"]], []]
],
"div {} -->", [["qualified rule", [["ident", "div"], " "], []]],
"{}a", [["qualified rule", [], []], ["error", "invalid"]],
"{}@a", [["qualified rule", [], []], ["at-rule", "a", [], null]]
]

View File

@ -0,0 +1,146 @@
[
{"css_bytes": ""},
[[], "utf-8"],
{"css_bytes": "@\u00C3\u00A9",
"protocol_encoding": null, "environment_encoding": null},
[[["at-rule", "é", [], null]], "utf-8"],
{"css_bytes": "@\u00C3\u00A9"},
[[["at-rule", "é", [], null]], "utf-8"],
{"css_bytes": "@\u0000\u00E9\u0000",
"comment": "Untagged UTF-16, parsed as UTF-8"},
[[["at-rule", "<22><><EFBFBD>", [], null]], "utf-8"],
{"css_bytes": "\u00FF\u00FE@\u0000\u00E9\u0000",
"comment": "UTF-16 with a BOM"},
[[["at-rule", "é", [], null]], "utf-16le"],
{"css_bytes": "\u00FE\u00FF\u0000@\u0000\u00E9"},
[[["at-rule", "é", [], null]], "utf-16be"],
{"css_bytes": "@\u00E9"},
[[["at-rule", "<22>", [], null]], "utf-8"],
{"css_bytes": "@\u00E9", "protocol_encoding": "ISO-8859-2"},
[[["at-rule", "é", [], null]], "iso-8859-2"],
{"css_bytes": "@\u00E9", "protocol_encoding": "ISO-8859-5"},
[[["at-rule", "щ", [], null]], "iso-8859-5"],
{"css_bytes": "@\u00C3\u00A9", "protocol_encoding": "ISO-8859-2"},
[[["at-rule", "ĂŠ", [], null]], "iso-8859-2"],
{"css_bytes": "\u00EF\u00BB\u00BF @\u00C3\u00A9",
"protocol_encoding": "ISO-8859-2",
"comment": "BOM takes precedence over protocol"},
[[["at-rule", "é", [], null]], "utf-8"],
{"css_bytes": "@charset \"ISO-8859-5\"; @\u00E9"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "щ", [], null]],
"iso-8859-5"],
{"css_bytes": "@Charset \"ISO-8859-5\"; @\u00E9",
"comment": "@charset has to match an exact byte pattern"},
[[["at-rule", "Charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "<22>", [], null]],
"utf-8"],
{"css_bytes": "@charset \"ISO-8859-5\"; @\u00E9",
"comment": "@charset has to match an exact byte pattern"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "<22>", [], null]],
"utf-8"],
{"css_bytes": "@charset 'ISO-8859-5'; @\u00E9",
"comment": "@charset has to match an exact byte pattern"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "<22>", [], null]],
"utf-8"],
{"css_bytes": "@charset \"ISO-8859-5\" ; @\u00E9",
"comment": "@charset has to match an exact byte pattern"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"], " "], null],
["at-rule", "<22>", [], null]],
"utf-8"],
{"css_bytes": "@\u0000c\u0000h\u0000a\u0000r\u0000s\u0000e\u0000t\u0000 \u0000\"\u0000U\u0000T\u0000F\u0000-\u00001\u00006\u0000L\u0000E\u0000\"\u0000;\u0000@\u0000\u00e9\u0000",
"comment": "@charset has to be ASCII-compatible itself"},
[[["at-rule", "<22>c<EFBFBD>h<EFBFBD>a<EFBFBD>r<EFBFBD>s<EFBFBD>e<EFBFBD>t<EFBFBD>",
[" ", ["ident", "<22>"], ["string", "<22>U<EFBFBD>T<EFBFBD>F<EFBFBD>-<2D>1<EFBFBD>6<EFBFBD>L<EFBFBD>E<EFBFBD>"], ["ident", "<22>"]], null],
["error", "invalid"]],
"utf-8"],
{"css_bytes": "@charset \"UTF-16LE\"; @\u00C3\u00A9",
"comment": "@charset can only specify ASCII-compatible encodings"},
[[["at-rule", "charset", [" ", ["string", "UTF-16LE"]], null],
["at-rule", "é", [], null]],
"utf-8"],
{"css_bytes": "\u00EF\u00BB\u00BF @charset \"ISO-8859-5\"; @\u00E9",
"comment": "BOM takes precedence over @charset"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "<22>", [], null]],
"utf-8"],
{"css_bytes": "\u00EF\u00BB\u00BF @charset \"ISO-8859-5\"; @\u00C3\u00A9",
"comment": "BOM takes precedence over @charset"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "é", [], null]],
"utf-8"],
{"css_bytes": "@charset \"ISO-8859-5\"; @\u00E9",
"protocol_encoding": " Iso-8859-2",
"comment": "Protocol takes precedence over @charset"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "é", [], null]],
"iso-8859-2"],
{"css_bytes": "@charset \"ISO-8859-5\"; @\u00E9",
"protocol_encoding": "kamoulox",
"comment": "Unknow protocol encoding falls back to @charset"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "щ", [], null]],
"iso-8859-5"],
{"css_bytes": "@\u00E9", "environment_encoding": "ISO-8859-2"},
[[["at-rule", "é", [], null]], "iso-8859-2"],
{"css_bytes": "@\u00E9", "environment_encoding": "ISO-8859-5"},
[[["at-rule", "щ", [], null]], "iso-8859-5"],
{"css_bytes": "@charset \"ISO-8859-5\"; @\u00E9",
"environment_encoding": "ISO-8859-2",
"comment": "@character takes precedence over environment"},
[[["at-rule", "charset", [" ", ["string", "ISO-8859-5"]], null],
["at-rule", "щ", [], null]],
"iso-8859-5"],
{"css_bytes": "@charset \"kamoulox\"; @\u00E9",
"environment_encoding": "ISO-8859-2",
"comment": "@character with unknown encoding falls back to environment encoding"},
[[["at-rule", "charset", [" ", ["string", "kamoulox"]], null],
["at-rule", "é", [], null]],
"iso-8859-2"],
{"css_bytes": "@\u00E9",
"protocol_encoding": "ISO-8859-2",
"environment_encoding": "ISO-8859-5",
"comment": "protocol takes precedence over environment"},
[[["at-rule", "é", [], null]], "iso-8859-2"],
{"css_bytes": "\u00EF\u00BB\u00BF @\u00C3\u00A9",
"environment_encoding": "ISO-8859-5",
"comment": "BOM takes precedence over environment"},
[[["at-rule", "é", [], null]], "utf-8"]
]

102
tinycss2/nth.py Normal file
View File

@ -0,0 +1,102 @@
import re
from .parser import _next_significant, _to_token_iterator
def parse_nth(input):
"""Parse `<An+B> <http://dev.w3.org/csswg/css-syntax-3/#anb>`_,
as found in `:nth-child()
<http://dev.w3.org/csswg/selectors/#nth-child-pseudo>`_
and related Selector pseudo-classes.
Although tinycss2 does not include a full Selector parser,
this bit of syntax is included as it is particularly tricky to define
on top of a CSS tokenizer.
:param input:
A :term:`string`, or an iterable yielding :term:`component values`
(eg. the :attr:`~tinycss2.ast.FunctionBlock.arguments`
of a functional pseudo-class.)
:returns:
A ``(a, b)`` tuple of integers, or :obj:`None` if the input is invalid.
"""
tokens = _to_token_iterator(input, skip_comments=True)
token = _next_significant(tokens)
if token is None:
return
token_type = token.type
if token_type == 'number' and token.is_integer:
return parse_end(tokens, 0, token.int_value)
elif token_type == 'dimension' and token.is_integer:
unit = token.lower_unit
if unit == 'n':
return parse_b(tokens, token.int_value)
elif unit == 'n-':
return parse_signless_b(tokens, token.int_value, -1)
else:
match = N_DASH_DIGITS_RE.match(unit)
if match:
return parse_end(tokens, token.int_value, int(match.group(1)))
elif token_type == 'ident':
ident = token.lower_value
if ident == 'even':
return parse_end(tokens, 2, 0)
elif ident == 'odd':
return parse_end(tokens, 2, 1)
elif ident == 'n':
return parse_b(tokens, 1)
elif ident == '-n':
return parse_b(tokens, -1)
elif ident == 'n-':
return parse_signless_b(tokens, 1, -1)
elif ident == '-n-':
return parse_signless_b(tokens, -1, -1)
elif ident[0] == '-':
match = N_DASH_DIGITS_RE.match(ident[1:])
if match:
return parse_end(tokens, -1, int(match.group(1)))
else:
match = N_DASH_DIGITS_RE.match(ident)
if match:
return parse_end(tokens, 1, int(match.group(1)))
elif token == '+':
token = next(tokens) # Whitespace after an initial '+' is invalid.
if token.type == 'ident':
ident = token.lower_value
if ident == 'n':
return parse_b(tokens, 1)
elif ident == 'n-':
return parse_signless_b(tokens, 1, -1)
else:
match = N_DASH_DIGITS_RE.match(ident)
if match:
return parse_end(tokens, 1, int(match.group(1)))
def parse_b(tokens, a):
token = _next_significant(tokens)
if token is None:
return (a, 0)
elif token == '+':
return parse_signless_b(tokens, a, 1)
elif token == '-':
return parse_signless_b(tokens, a, -1)
elif (token.type == 'number' and token.is_integer and
token.representation[0] in '-+'):
return parse_end(tokens, a, token.int_value)
def parse_signless_b(tokens, a, b_sign):
token = _next_significant(tokens)
if (token.type == 'number' and token.is_integer and
not token.representation[0] in '-+'):
return parse_end(tokens, a, b_sign * token.int_value)
def parse_end(tokens, a, b):
if _next_significant(tokens) is None:
return (a, b)
N_DASH_DIGITS_RE = re.compile('^n(-[0-9]+)$')

361
tinycss2/parser.py Normal file
View File

@ -0,0 +1,361 @@
# coding: utf-8
from ._compat import basestring
from .ast import AtRule, Declaration, ParseError, QualifiedRule
from .tokenizer import parse_component_value_list
def _to_token_iterator(input, skip_comments=False):
"""
:param input: A string or an iterable of :term:`component values`.
:param skip_comments:
If the input is a string, ignore all CSS comments.
:returns: A iterator yielding :term:`component values`.
"""
# Accept ASCII-only byte strings on Python 2, with implicit conversion.
if isinstance(input, basestring):
input = parse_component_value_list(input, skip_comments)
return iter(input)
def _next_significant(tokens):
"""Return the next significant (neither whitespace or comment) token.
:param tokens: An *iterator* yielding :term:`component values`.
:returns: A :term:`component value`, or :obj:`None`.
"""
for token in tokens:
if token.type not in ('whitespace', 'comment'):
return token
def parse_one_component_value(input, skip_comments=False):
"""Parse a single :diagram:`component value`.
This is used e.g. for an attribute value
referred to by ``attr(foo length)``.
:param input:
A :term:`string`, or an iterable of :term:`component values`.
:param skip_comments:
If the input is a string, ignore all CSS comments.
:returns:
A :term:`component value` (that is neither whitespace or comment),
or a :class:`~tinycss2.ast.ParseError`.
"""
tokens = _to_token_iterator(input, skip_comments)
first = _next_significant(tokens)
second = _next_significant(tokens)
if first is None:
return ParseError(1, 1, 'empty', 'Input is empty')
if second is not None:
return ParseError(
second.source_line, second.source_column, 'extra-input',
'Got more than one token')
else:
return first
def parse_one_declaration(input, skip_comments=False):
"""Parse a single :diagram:`declaration`.
This is used e.g. for a declaration in an `@supports
<http://dev.w3.org/csswg/css-conditional/#at-supports>`_ test.
:param input:
A :term:`string`, or an iterable of :term:`component values`.
:param skip_comments:
If the input is a string, ignore all CSS comments.
:returns:
A :class:`~tinycss2.ast.Declaration`
or :class:`~tinycss2.ast.ParseError`.
Any whitespace or comment before the ``:`` colon is dropped.
"""
tokens = _to_token_iterator(input, skip_comments)
first_token = _next_significant(tokens)
if first_token is None:
return ParseError(1, 1, 'empty', 'Input is empty')
return _parse_declaration(first_token, tokens)
def _parse_declaration(first_token, tokens):
"""Parse a declaration.
Consume :obj:`tokens` until the end of the declaration or the first error.
:param first_token: The first :term:`component value` of the rule.
:param tokens: An *iterator* yielding :term:`component values`.
:returns:
A :class:`~tinycss2.ast.Declaration`
or :class:`~tinycss2.ast.ParseError`.
"""
name = first_token
if name.type != 'ident':
return ParseError(name.source_line, name.source_column, 'invalid',
'Expected <ident> for declaration name, got %s.'
% name.type)
colon = _next_significant(tokens)
if colon is None:
return ParseError(name.source_line, name.source_column, 'invalid',
"Expected ':' after declaration name, got EOF")
elif colon != ':':
return ParseError(colon.source_line, colon.source_column, 'invalid',
"Expected ':' after declaration name, got %s."
% colon.type)
value = []
state = 'value'
for i, token in enumerate(tokens):
if state == 'value' and token == '!':
state = 'bang'
bang_position = i
elif state == 'bang' and token.type == 'ident' \
and token.lower_value == 'important':
state = 'important'
elif token.type not in ('whitespace', 'comment'):
state = 'value'
value.append(token)
if state == 'important':
del value[bang_position:]
return Declaration(name.source_line, name.source_column, name.value,
name.lower_value, value, state == 'important')
def _consume_declaration_in_list(first_token, tokens):
"""Like :func:`_parse_declaration`, but stop at the first ``;``."""
other_declaration_tokens = []
for token in tokens:
if token == ';':
break
other_declaration_tokens.append(token)
return _parse_declaration(first_token, iter(other_declaration_tokens))
def parse_declaration_list(input, skip_comments=False, skip_whitespace=False):
"""Parse a :diagram:`declaration list` (which may also contain at-rules).
This is used e.g. for the :attr:`~tinycss2.ast.QualifiedRule.content`
of a style rule or ``@page`` rule,
or for the ``style`` attribute of an HTML element.
In contexts that dont expect any at-rule,
all :class:`~tinycss2.ast.AtRule` objects
should simply be rejected as invalid.
:param input: A string or an iterable of :term:`component values`.
:param skip_comments:
Ignore CSS comments at the top-level of the list.
If the input is a string, ignore all comments.
:param skip_whitespace:
Ignore whitespace at the top-level of the list.
Whitespace is still preserved
in the :attr:`~tinycss2.ast.Declaration.value` of declarations
and the :attr:`~tinycss2.ast.AtRule.prelude`
and :attr:`~tinycss2.ast.AtRule.content` of at-rules.
:returns:
A list of
:class:`~tinycss2.ast.Declaration`,
:class:`~tinycss2.ast.AtRule`,
:class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
:class:`~tinycss2.ast.WhitespaceToken`
(if ``skip_whitespace`` is false),
and :class:`~tinycss2.ast.ParseError` objects
"""
tokens = _to_token_iterator(input, skip_comments)
result = []
for token in tokens:
if token.type == 'whitespace':
if not skip_whitespace:
result.append(token)
elif token.type == 'comment':
if not skip_comments:
result.append(token)
elif token.type == 'at-keyword':
result.append(_consume_at_rule(token, tokens))
elif token != ';':
result.append(_consume_declaration_in_list(token, tokens))
return result
def parse_one_rule(input, skip_comments=False):
"""Parse a single :diagram:`qualified rule` or :diagram:`at-rule`.
This would be used e.g. by `insertRule()
<http://dev.w3.org/csswg/cssom/#dom-cssstylesheet-insertrule>`_
in an implementation of CSSOM.
:param input: A string or an iterable of :term:`component values`.
:param skip_comments:
If the input is a string, ignore all CSS comments.
:returns:
A :class:`~tinycss2.ast.QualifiedRule`,
:class:`~tinycss2.ast.AtRule`,
or :class:`~tinycss2.ast.ParseError` objects.
Any whitespace or comment before or after the rule is dropped.
"""
tokens = _to_token_iterator(input, skip_comments)
first = _next_significant(tokens)
if first is None:
return ParseError(1, 1, 'empty', 'Input is empty')
rule = _consume_rule(first, tokens)
next = _next_significant(tokens)
if next is not None:
return ParseError(
next.source_line, next.source_column, 'extra-input',
'Expected a single rule, got %s after the first rule.' % next.type)
return rule
def parse_rule_list(input, skip_comments=False, skip_whitespace=False):
"""Parse a non-top-level :diagram:`rule list`.
This is used for parsing the :attr:`~tinycss2.ast.AtRule.content`
of nested rules like ``@media``.
This differs from :func:`parse_stylesheet` in that
top-level ``<!--`` and ``-->`` tokens are not ignored.
:param input: A string or an iterable of :term:`component values`.
:param skip_comments:
Ignore CSS comments at the top-level of the list.
If the input is a string, ignore all comments.
:param skip_whitespace:
Ignore whitespace at the top-level of the list.
Whitespace is still preserved
in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
:returns:
A list of
:class:`~tinycss2.ast.QualifiedRule`,
:class:`~tinycss2.ast.AtRule`,
:class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
:class:`~tinycss2.ast.WhitespaceToken`
(if ``skip_whitespace`` is false),
and :class:`~tinycss2.ast.ParseError` objects.
"""
tokens = _to_token_iterator(input, skip_comments)
result = []
for token in tokens:
if token.type == 'whitespace':
if not skip_whitespace:
result.append(token)
elif token.type == 'comment':
if not skip_comments:
result.append(token)
else:
result.append(_consume_rule(token, tokens))
return result
def parse_stylesheet(input, skip_comments=False, skip_whitespace=False):
"""Parse :diagram:`stylesheet` from text.
This is used e.g. for a ``<style>`` HTML element.
This differs from :func:`parse_rule_list` in that
top-level ``<!--`` and ``-->`` tokens are ignored.
This is a legacy quirk for the ``<style>`` HTML element.
:param input: A string or an iterable of :term:`component values`.
:param skip_comments:
Ignore CSS comments at the top-level of the stylesheet.
If the input is a string, ignore all comments.
:param skip_whitespace:
Ignore whitespace at the top-level of the stylesheet.
Whitespace is still preserved
in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
:returns:
A list of
:class:`~tinycss2.ast.QualifiedRule`,
:class:`~tinycss2.ast.AtRule`,
:class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
:class:`~tinycss2.ast.WhitespaceToken`
(if ``skip_whitespace`` is false),
and :class:`~tinycss2.ast.ParseError` objects.
"""
tokens = _to_token_iterator(input, skip_comments)
result = []
for token in tokens:
if token.type == 'whitespace':
if not skip_whitespace:
result.append(token)
elif token.type == 'comment':
if not skip_comments:
result.append(token)
elif token not in ('<!--', '-->'):
result.append(_consume_rule(token, tokens))
return result
def _consume_rule(first_token, tokens):
"""Parse a qualified rule or at-rule.
Consume just enough of :obj:`tokens` for this rule.
:param first_token: The first :term:`component value` of the rule.
:param tokens: An *iterator* yielding :term:`component values`.
:returns:
A :class:`~tinycss2.ast.QualifiedRule`,
:class:`~tinycss2.ast.AtRule`,
or :class:`~tinycss2.ast.ParseError`.
"""
if first_token.type == 'at-keyword':
return _consume_at_rule(first_token, tokens)
if first_token.type == '{} block':
prelude = []
block = first_token
else:
prelude = [first_token]
for token in tokens:
if token.type == '{} block':
block = token
break
prelude.append(token)
else:
return ParseError(
prelude[-1].source_line, prelude[-1].source_column, 'invalid',
'EOF reached before {} block for a qualified rule.')
return QualifiedRule(first_token.source_line, first_token.source_column,
prelude, block.content)
def _consume_at_rule(at_keyword, tokens):
"""Parse an at-rule.
Consume just enough of :obj:`tokens` for this rule.
:param at_keyword: The :class:`AtKeywordToken` object starting this rule.
:param tokens: An *iterator* yielding :term:`component values`.
:returns:
A :class:`~tinycss2.ast.QualifiedRule`,
or :class:`~tinycss2.ast.ParseError`.
"""
prelude = []
content = None
for token in tokens:
if token.type == '{} block':
content = token.content
break
elif token == ';':
break
prelude.append(token)
return AtRule(at_keyword.source_line, at_keyword.source_column,
at_keyword.value, at_keyword.lower_value, prelude, content)

122
tinycss2/serializer.py Normal file
View File

@ -0,0 +1,122 @@
from __future__ import unicode_literals
def serialize(nodes):
"""Serialize nodes to CSS syntax.
This should be used for :term:`component values`
instead of just :meth:`~tinycss2.ast.Node.serialize` on each node
as it takes care of corner cases such as ``;`` between declarations,
and consecutive identifiers
that would otherwise parse back as the same token.
:param nodes: an iterable of :class:`~tinycss2.ast.Node` objects
:returns: an Unicode string
"""
chunks = []
_serialize_to(nodes, chunks.append)
return ''.join(chunks)
def serialize_identifier(value):
"""Serialize any string as a CSS identifier
:param value: a :term:`string`
:returns:
an Unicode string
that would parse as an :class:`~tinycss2.ast.IdentToken`
whose :attr:`~tinycss2.ast.IdentToken.value` attribute
equals the passed :obj:`value` argument.
"""
if value == '-':
return r'\-'
if value[0] == '-':
result = '-'
value = value[1:]
else:
result = ''
c = value[0]
result += (
c if c in ('abcdefghijklmnopqrstuvwxyz_'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
r'\A ' if c == '\n' else
r'\D ' if c == '\r' else
r'\C ' if c == '\f' else
'\\%X ' % ord(c) if c in '0123456789' else
'\\' + c
)
result += serialize_name(value[1:])
return result
def serialize_name(value):
return ''.join(
c if c in ('abcdefghijklmnopqrstuvwxyz-_0123456789'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
r'\A ' if c == '\n' else
r'\D ' if c == '\r' else
r'\C ' if c == '\f' else
'\\' + c
for c in value
)
def serialize_string_value(value):
return ''.join(
r'\"' if c == '"' else
r'\\' if c == '\\' else
r'\A ' if c == '\n' else
r'\D ' if c == '\r' else
r'\C ' if c == '\f' else
c
for c in value
)
# http://dev.w3.org/csswg/css-syntax/#serialization-tables
def _serialize_to(nodes, write):
"""Serialize an iterable of nodes to CSS syntax,
writing chunks as Unicode string
by calling the provided :obj:`write` callback.
"""
bad_pairs = BAD_PAIRS
previous_type = None
for node in nodes:
serialization_type = (node.type if node.type != 'literal'
else node.value)
if (previous_type, serialization_type) in bad_pairs:
write('/**/')
elif previous_type == '\\' and not (
serialization_type == 'whitespace' and
node.value.startswith('\n')):
write('\n')
node._serialize_to(write)
if serialization_type == 'declaration':
write(';')
previous_type = serialization_type
BAD_PAIRS = set(
[(a, b)
for a in ('ident', 'at-keyword', 'hash', 'dimension', '#', '-',
'number')
for b in ('ident', 'function', 'url', 'number', 'percentage',
'dimension', 'unicode-range')] +
[(a, b)
for a in ('ident', 'at-keyword', 'hash', 'dimension')
for b in ('-', '-->')] +
[(a, b)
for a in ('#', '-', 'number', '@')
for b in ('ident', 'function', 'url')] +
[(a, b)
for a in ('unicode-range', '.', '+')
for b in ('number', 'percentage', 'dimension')] +
[('@', b) for b in ('ident', 'function', 'url', 'unicode-range', '-')] +
[('unicode-range', b) for b in ('ident', 'function', '?')] +
[(a, '=') for a in '$*^~|'] +
[('ident', '() block'), ('|', '|'), ('/', '*')]
)

230
tinycss2/test.py Normal file
View File

@ -0,0 +1,230 @@
# coding: utf8
import functools
import json
import os.path
import pprint
import pytest
from webencodings import Encoding, lookup
from . import (parse_component_value_list, parse_declaration_list,
parse_one_component_value, parse_one_declaration,
parse_one_rule, parse_rule_list, parse_stylesheet,
parse_stylesheet_bytes, serialize)
from .ast import (AtKeywordToken, AtRule, Comment, CurlyBracketsBlock,
Declaration, DimensionToken, FunctionBlock, HashToken,
IdentToken, LiteralToken, NumberToken, ParenthesesBlock,
ParseError, PercentageToken, QualifiedRule,
SquareBracketsBlock, StringToken, UnicodeRangeToken,
URLToken, WhitespaceToken)
from .color3 import RGBA, parse_color
from .nth import parse_nth
def generic(func):
implementations = func()
@functools.wraps(func)
def run(value):
repr(value) # Test that this does not raise.
return implementations[type(value)](value)
return run
@generic
def to_json():
def numeric(t):
return [
t.representation, t.value,
'integer' if t.int_value is not None else 'number']
return {
type(None): lambda _: None,
str: lambda s: s,
int: lambda s: s,
list: lambda l: [to_json(el) for el in l],
tuple: lambda l: [to_json(el) for el in l],
Encoding: lambda e: e.name,
ParseError: lambda e: ['error', e.kind],
Comment: lambda t: '/* … */',
WhitespaceToken: lambda t: ' ',
LiteralToken: lambda t: t.value,
IdentToken: lambda t: ['ident', t.value],
AtKeywordToken: lambda t: ['at-keyword', t.value],
HashToken: lambda t: ['hash', t.value,
'id' if t.is_identifier else 'unrestricted'],
StringToken: lambda t: ['string', t.value],
URLToken: lambda t: ['url', t.value],
NumberToken: lambda t: ['number'] + numeric(t),
PercentageToken: lambda t: ['percentage'] + numeric(t),
DimensionToken: lambda t: ['dimension'] + numeric(t) + [t.unit],
UnicodeRangeToken: lambda t: ['unicode-range', t.start, t.end],
CurlyBracketsBlock: lambda t: ['{}'] + to_json(t.content),
SquareBracketsBlock: lambda t: ['[]'] + to_json(t.content),
ParenthesesBlock: lambda t: ['()'] + to_json(t.content),
FunctionBlock: lambda t: ['function', t.name] + to_json(t.arguments),
Declaration: lambda d: ['declaration', d.name,
to_json(d.value), d.important],
AtRule: lambda r: ['at-rule', r.at_keyword, to_json(r.prelude),
to_json(r.content)],
QualifiedRule: lambda r: ['qualified rule', to_json(r.prelude),
to_json(r.content)],
RGBA: lambda v: [round(c, 10) for c in v],
}
def load_json(filename):
json_data = json.load(open(os.path.join(
os.path.dirname(__file__), 'css-parsing-tests', filename)))
return list(zip(json_data[::2], json_data[1::2]))
def json_test(filename=None):
def decorator(function):
filename_ = filename or function.__name__.split('_', 1)[-1] + '.json'
@pytest.mark.parametrize(('css', 'expected'), load_json(filename_))
def test(css, expected):
value = to_json(function(css))
if value != expected: # pragma: no cover
pprint.pprint(value)
assert value == expected
return test
return decorator
SKIP = dict(skip_comments=True, skip_whitespace=True)
@json_test()
def test_component_value_list(input):
return parse_component_value_list(input, skip_comments=True)
@json_test()
def test_one_component_value(input):
return parse_one_component_value(input, skip_comments=True)
@json_test()
def test_declaration_list(input):
return parse_declaration_list(input, **SKIP)
@json_test()
def test_one_declaration(input):
return parse_one_declaration(input, skip_comments=True)
@json_test()
def test_stylesheet(input):
return parse_stylesheet(input, **SKIP)
@json_test()
def test_rule_list(input):
return parse_rule_list(input, **SKIP)
@json_test()
def test_one_rule(input):
return parse_one_rule(input, skip_comments=True)
@json_test()
def test_color3(input):
return parse_color(input)
@json_test(filename='An+B.json')
def test_nth(input):
return parse_nth(input)
# Do not use @pytest.mark.parametrize because it is slow with that many values.
def test_color3_hsl():
for css, expected in load_json('color3_hsl.json'):
assert to_json(parse_color(css)) == expected
def test_color3_keywords():
for css, expected in load_json('color3_keywords.json'):
result = parse_color(css)
if result is not None:
r, g, b, a = result
result = [r * 255, g * 255, b * 255, a]
assert result == expected
@json_test()
def test_stylesheet_bytes(kwargs):
kwargs['css_bytes'] = kwargs['css_bytes'].encode('latin1')
kwargs.pop('comment', None)
if kwargs.get('environment_encoding'):
kwargs['environment_encoding'] = lookup(kwargs['environment_encoding'])
kwargs.update(SKIP)
return parse_stylesheet_bytes(**kwargs)
@json_test(filename='component_value_list.json')
def test_serialization(css):
parsed = parse_component_value_list(css, skip_comments=True)
return parse_component_value_list(serialize(parsed), skip_comments=True)
def test_skip():
source = '''
/* foo */
@media print {
#foo {
width: /* bar*/4px;
color: green;
}
}
'''
no_ws = parse_stylesheet(source, skip_whitespace=True)
no_comment = parse_stylesheet(source, skip_comments=True)
default = parse_component_value_list(source)
assert serialize(no_ws) != source
assert serialize(no_comment) != source
assert serialize(default) == source
def test_comment_eof():
source = '/* foo '
parsed = parse_component_value_list(source)
assert serialize(parsed) == '/* foo */'
def test_parse_declaration_value_color():
source = 'color:#369'
declaration = parse_one_declaration(source)
(value_token,) = declaration.value
assert parse_color(value_token) == (.2, .4, .6, 1)
assert declaration.serialize() == source
def test_serialize_rules():
source = '@import "a.css"; foo#bar.baz { color: red } /**/ @media print{}'
rules = parse_rule_list(source)
assert serialize(rules) == source
def test_serialize_declarations():
source = 'color: #123; /**/ @top-left {} width:7px !important;'
rules = parse_declaration_list(source)
assert serialize(rules) == source
def test_backslash_delim():
source = '\\\nfoo'
tokens = parse_component_value_list(source)
assert [t.type for t in tokens] == ['literal', 'whitespace', 'ident']
assert tokens[0].value == '\\'
del tokens[1]
assert [t.type for t in tokens] == ['literal', 'ident']
assert serialize(tokens) == source

402
tinycss2/tokenizer.py Normal file
View File

@ -0,0 +1,402 @@
from __future__ import unicode_literals
import re
import sys
from webencodings import ascii_lower
from ._compat import unichr
from .ast import (AtKeywordToken, Comment, CurlyBracketsBlock, DimensionToken,
FunctionBlock, HashToken, IdentToken, LiteralToken,
NumberToken, ParenthesesBlock, ParseError, PercentageToken,
SquareBracketsBlock, StringToken, UnicodeRangeToken,
URLToken, WhitespaceToken)
_NUMBER_RE = re.compile(r'[-+]?([0-9]*\.)?[0-9]+([eE][+-]?[0-9]+)?')
_HEX_ESCAPE_RE = re.compile(r'([0-9A-Fa-f]{1,6})[ \n\t]?')
def parse_component_value_list(css, skip_comments=False):
"""Parse a list of component values.
:param css: A :term:`string`.
:param skip_comments:
Ignore CSS comments.
The return values (and recursively its blocks and functions)
will not contain any :class:`~tinycss2.ast.Comment` object.
:returns: A list of :term:`component values`.
"""
css = (css.replace('\0', '\uFFFD')
# This turns out to be faster than a regexp:
.replace('\r\n', '\n').replace('\r', '\n').replace('\f', '\n'))
length = len(css)
token_start_pos = pos = 0 # Character index in the css source.
line = 1 # First line is line 1.
last_newline = -1
root = tokens = []
end_char = None # Pop the stack when encountering this character.
stack = [] # Stack of nested blocks: (tokens, end_char) tuples.
while pos < length:
newline = css.rfind('\n', token_start_pos, pos)
if newline != -1:
line += 1 + css.count('\n', token_start_pos, newline)
last_newline = newline
# First character in a line is in column 1.
column = pos - last_newline
token_start_pos = pos
c = css[pos]
if c in ' \n\t':
pos += 1
while css.startswith((' ', '\n', '\t'), pos):
pos += 1
value = css[token_start_pos:pos]
tokens.append(WhitespaceToken(line, column, value))
continue
elif (c in 'Uu' and pos + 2 < length and css[pos + 1] == '+' and
css[pos + 2] in '0123456789abcdefABCDEF?'):
start, end, pos = _consume_unicode_range(css, pos + 2)
tokens.append(UnicodeRangeToken(line, column, start, end))
continue
elif css.startswith('-->', pos): # Check before identifiers
tokens.append(LiteralToken(line, column, '-->'))
pos += 3
continue
elif _is_ident_start(css, pos):
value, pos = _consume_ident(css, pos)
if not css.startswith('(', pos): # Not a function
tokens.append(IdentToken(line, column, value))
continue
pos += 1 # Skip the '('
if ascii_lower(value) == 'url':
value, pos = _consume_url(css, pos)
tokens.append(
URLToken(line, column, value) if value is not None
else ParseError(line, column, 'bad-url', 'bad URL token'))
continue
arguments = []
tokens.append(FunctionBlock(line, column, value, arguments))
stack.append((tokens, end_char))
end_char = ')'
tokens = arguments
continue
match = _NUMBER_RE.match(css, pos)
if match:
pos = match.end()
repr_ = css[token_start_pos:pos]
value = float(repr_)
int_value = int(repr_) if not any(match.groups()) else None
if pos < length and _is_ident_start(css, pos):
unit, pos = _consume_ident(css, pos)
tokens.append(DimensionToken(
line, column, value, int_value, repr_, unit))
elif css.startswith('%', pos):
pos += 1
tokens.append(PercentageToken(
line, column, value, int_value, repr_))
else:
tokens.append(NumberToken(
line, column, value, int_value, repr_))
elif c == '@':
pos += 1
if pos < length and _is_ident_start(css, pos):
value, pos = _consume_ident(css, pos)
tokens.append(AtKeywordToken(line, column, value))
else:
tokens.append(LiteralToken(line, column, '@'))
elif c == '#':
pos += 1
if pos < length and (
css[pos] in '0123456789abcdefghijklmnopqrstuvwxyz'
'-_ABCDEFGHIJKLMNOPQRSTUVWXYZ' or
ord(css[pos]) > 0x7F or # Non-ASCII
# Valid escape:
(css[pos] == '\\' and not css.startswith('\\\n', pos))):
is_identifier = _is_ident_start(css, pos)
value, pos = _consume_ident(css, pos)
tokens.append(HashToken(line, column, value, is_identifier))
else:
tokens.append(LiteralToken(line, column, '#'))
elif c == '{':
content = []
tokens.append(CurlyBracketsBlock(line, column, content))
stack.append((tokens, end_char))
end_char = '}'
tokens = content
pos += 1
elif c == '[':
content = []
tokens.append(SquareBracketsBlock(line, column, content))
stack.append((tokens, end_char))
end_char = ']'
tokens = content
pos += 1
elif c == '(':
content = []
tokens.append(ParenthesesBlock(line, column, content))
stack.append((tokens, end_char))
end_char = ')'
tokens = content
pos += 1
elif c == end_char: # Matching }, ] or )
# The top-level end_char is None (never equal to a character),
# so we never get here if the stack is empty.
tokens, end_char = stack.pop()
pos += 1
elif c in '}])':
tokens.append(ParseError(line, column, c, 'Unmatched ' + c))
pos += 1
elif c in ('"', "'"):
value, pos = _consume_quoted_string(css, pos)
tokens.append(
StringToken(line, column, value) if value is not None
else ParseError(line, column, 'bad-string',
'bad string token'))
elif css.startswith('/*', pos): # Comment
pos = css.find('*/', pos + 2)
if pos == -1:
if not skip_comments:
tokens.append(
Comment(line, column, css[token_start_pos + 2:]))
break
if not skip_comments:
tokens.append(
Comment(line, column, css[token_start_pos + 2:pos]))
pos += 2
elif css.startswith('<!--', pos):
tokens.append(LiteralToken(line, column, '<!--'))
pos += 4
elif css.startswith('||', pos):
tokens.append(LiteralToken(line, column, '||'))
pos += 2
elif c in '~|^$*':
pos += 1
if css.startswith('=', pos):
pos += 1
tokens.append(LiteralToken(line, column, c + '='))
else:
tokens.append(LiteralToken(line, column, c))
else:
tokens.append(LiteralToken(line, column, c))
pos += 1
return root
def _is_name_start(css, pos):
"""Return true if the given character is a name-start code point."""
# https://www.w3.org/TR/css-syntax-3/#name-start-code-point
c = css[pos]
return (
c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' or
ord(c) > 0x7F)
def _is_ident_start(css, pos):
"""Return True if the given position is the start of a CSS identifier."""
# https://www.w3.org/TR/css-syntax-3/#would-start-an-identifier
if _is_name_start(css, pos):
return True
elif css[pos] == '-':
pos += 1
return (
# Name-start code point:
(pos < len(css) and _is_name_start(css, pos)) or
# Valid escape:
(css.startswith('\\', pos) and not css.startswith('\\\n', pos)))
elif css[pos] == '\\':
return not css.startswith('\\\n', pos)
return False
def _consume_ident(css, pos):
"""Return (unescaped_value, new_pos).
Assumes pos starts at a valid identifier. See :func:`_is_ident_start`.
"""
# http://dev.w3.org/csswg/css-syntax/#consume-a-name
chunks = []
length = len(css)
start_pos = pos
while pos < length:
c = css[pos]
if c in ('abcdefghijklmnopqrstuvwxyz-_0123456789'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F:
pos += 1
elif c == '\\' and not css.startswith('\\\n', pos):
# Valid escape
chunks.append(css[start_pos:pos])
c, pos = _consume_escape(css, pos + 1)
chunks.append(c)
start_pos = pos
else:
break
chunks.append(css[start_pos:pos])
return ''.join(chunks), pos
def _consume_quoted_string(css, pos):
"""Return (unescaped_value, new_pos)."""
# http://dev.w3.org/csswg/css-syntax/#consume-a-string-token
quote = css[pos]
assert quote in ('"', "'")
pos += 1
chunks = []
length = len(css)
start_pos = pos
while pos < length:
c = css[pos]
if c == quote:
chunks.append(css[start_pos:pos])
pos += 1
break
elif c == '\\':
chunks.append(css[start_pos:pos])
pos += 1
if pos < length:
if css[pos] == '\n': # Ignore escaped newlines
pos += 1
else:
c, pos = _consume_escape(css, pos)
chunks.append(c)
# else: Escaped EOF, do nothing
start_pos = pos
elif c == '\n': # Unescaped newline
return None, pos # bad-string
else:
pos += 1
else:
chunks.append(css[start_pos:pos])
return ''.join(chunks), pos
def _consume_escape(css, pos):
r"""Return (unescaped_char, new_pos).
Assumes a valid escape: pos is just after '\' and not followed by '\n'.
"""
# http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-character
hex_match = _HEX_ESCAPE_RE.match(css, pos)
if hex_match:
codepoint = int(hex_match.group(1), 16)
return (
unichr(codepoint) if 0 < codepoint <= sys.maxunicode else '\uFFFD',
hex_match.end())
elif pos < len(css):
return css[pos], pos + 1
else:
return '\uFFFD', pos
def _consume_url(css, pos):
"""Return (unescaped_url, new_pos)
The given pos is assumed to be just after the '(' of 'url('.
"""
length = len(css)
# http://dev.w3.org/csswg/css-syntax/#consume-a-url-token
# Skip whitespace
while css.startswith((' ', '\n', '\t'), pos):
pos += 1
if pos >= length: # EOF
return '', pos
c = css[pos]
if c in ('"', "'"):
value, pos = _consume_quoted_string(css, pos)
elif c == ')':
return '', pos + 1
else:
chunks = []
start_pos = pos
while 1:
if pos >= length: # EOF
chunks.append(css[start_pos:pos])
return ''.join(chunks), pos
c = css[pos]
if c == ')':
chunks.append(css[start_pos:pos])
pos += 1
return ''.join(chunks), pos
elif c in ' \n\t':
chunks.append(css[start_pos:pos])
value = ''.join(chunks)
pos += 1
break
elif c == '\\' and not css.startswith('\\\n', pos):
# Valid escape
chunks.append(css[start_pos:pos])
c, pos = _consume_escape(css, pos + 1)
chunks.append(c)
start_pos = pos
elif (c in
'"\'('
# http://dev.w3.org/csswg/css-syntax/#non-printable-character
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0e'
'\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19'
'\x1a\x1b\x1c\x1d\x1e\x1f\x7f'):
value = None # Parse error
pos += 1
break
else:
pos += 1
if value is not None:
while css.startswith((' ', '\n', '\t'), pos):
pos += 1
if pos < length:
if css[pos] == ')':
return value, pos + 1
else:
return value, pos
# http://dev.w3.org/csswg/css-syntax/#consume-the-remnants-of-a-bad-url0
while pos < length:
if css.startswith('\\)', pos):
pos += 2
elif css[pos] == ')':
pos += 1
break
else:
pos += 1
return None, pos # bad-url
def _consume_unicode_range(css, pos):
"""Return (range, new_pos)
The given pos is assume to be just after the '+' of 'U+' or 'u+'.
"""
# http://dev.w3.org/csswg/css-syntax/#consume-a-unicode-range-token
length = len(css)
start_pos = pos
max_pos = min(pos + 6, length)
while pos < max_pos and css[pos] in '0123456789abcdefABCDEF':
pos += 1
start = css[start_pos:pos]
start_pos = pos
# Same max_pos as before: total of hex digits and question marks <= 6
while pos < max_pos and css[pos] == '?':
pos += 1
question_marks = pos - start_pos
if question_marks:
end = start + 'F' * question_marks
start = start + '0' * question_marks
elif (pos + 1 < length and css[pos] == '-' and
css[pos + 1] in '0123456789abcdefABCDEF'):
pos += 1
start_pos = pos
max_pos = min(pos + 6, length)
while pos < max_pos and css[pos] in '0123456789abcdefABCDEF':
pos += 1
end = css[start_pos:pos]
else:
end = start
return int(start, 16), int(end, 16), pos