├── .coveragerc ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── NEWS.rst ├── README.rst ├── SECURITY.md ├── conftest.py ├── cssutils ├── __init__.py ├── _fetch.py ├── _fetchgae.py ├── codec.py ├── css │ ├── __init__.py │ ├── colors.py │ ├── csscharsetrule.py │ ├── csscomment.py │ ├── cssfontfacerule.py │ ├── cssimportrule.py │ ├── cssmediarule.py │ ├── cssnamespacerule.py │ ├── csspagerule.py │ ├── cssproperties.py │ ├── cssrule.py │ ├── cssrulelist.py │ ├── cssstyledeclaration.py │ ├── cssstylerule.py │ ├── cssstylesheet.py │ ├── cssunknownrule.py │ ├── cssvalue.py │ ├── cssvariablesdeclaration.py │ ├── cssvariablesrule.py │ ├── marginrule.py │ ├── property.py │ ├── selector.py │ ├── selectorlist.py │ └── value.py ├── css2productions.py ├── cssproductions.py ├── errorhandler.py ├── helper.py ├── parse.py ├── prodparser.py ├── profiles.py ├── sac.py ├── script.py ├── scripts │ ├── __init__.py │ ├── csscapture.py │ ├── csscombine.py │ └── cssparse.py ├── serialize.py ├── settings.py ├── stylesheets │ ├── __init__.py │ ├── medialist.py │ ├── mediaquery.py │ ├── stylesheet.py │ └── stylesheetlist.py ├── tests │ ├── __init__.py │ ├── basetest.py │ ├── sheets │ │ ├── 096.css │ │ ├── 097.css │ │ ├── 1.css │ │ ├── 1ascii.css │ │ ├── 1import.css │ │ ├── 1inherit-ascii.css │ │ ├── 1inherit-iso.css │ │ ├── 1inherit-utf8.css │ │ ├── 1utf.css │ │ ├── 2inherit-iso.css │ │ ├── 2resolve.css │ │ ├── acid2.css │ │ ├── all.css │ │ ├── atrule.css │ │ ├── bad.css │ │ ├── basic.css │ │ ├── bundle.css │ │ ├── cases.css │ │ ├── csscombine-1.css │ │ ├── csscombine-2.css │ │ ├── csscombine-proxy.css │ │ ├── cthedot_default.css │ │ ├── default_html4.css │ │ ├── hacks.css │ │ ├── html.css │ │ ├── html20.css │ │ ├── html40.css │ │ ├── images │ │ │ └── example.gif │ │ ├── import.css │ │ ├── import │ │ │ ├── images2 │ │ │ │ └── example2.gif │ │ │ ├── import-impossible.css │ │ │ └── import2.css │ │ ├── import3.css │ │ ├── ll.css │ │ ├── ll2.css │ │ ├── multiple-values.css │ │ ├── page_test.css │ │ ├── sample_5.css │ │ ├── sample_7.css │ │ ├── simple.css │ │ ├── single-color.css │ │ ├── slashcode.css │ │ ├── t-HACKS.css │ │ ├── test-unicode.css │ │ ├── test.css │ │ ├── tigris.css │ │ ├── tigris2.css │ │ ├── u_simple.css │ │ ├── v_simple.css │ │ ├── var │ │ │ ├── start.css │ │ │ ├── use.css │ │ │ ├── vars.css │ │ │ └── vars2.css │ │ ├── vars.css │ │ ├── varsimport.css │ │ ├── xhtml2.css │ │ ├── xhtml22.css │ │ └── yuck.css │ ├── test_codec.py │ ├── test_csscharsetrule.py │ ├── test_csscomment.py │ ├── test_cssfontfacerule.py │ ├── test_cssimportrule.py │ ├── test_cssmediarule.py │ ├── test_cssnamespacerule.py │ ├── test_csspagerule.py │ ├── test_cssproperties.py │ ├── test_cssrule.py │ ├── test_cssrulelist.py │ ├── test_cssstyledeclaration.py │ ├── test_cssstylerule.py │ ├── test_cssstylesheet.py │ ├── test_cssunknownrule.py │ ├── test_cssutils.py │ ├── test_cssutilsimport.py │ ├── test_cssvalue.py │ ├── test_cssvariablesdeclaration.py │ ├── test_cssvariablesrule.py │ ├── test_domimplementation.py │ ├── test_encutils.py │ ├── test_errorhandler.py │ ├── test_helper.py │ ├── test_marginrule.py │ ├── test_medialist.py │ ├── test_mediaquery.py │ ├── test_parse.py │ ├── test_prodparser.py │ ├── test_profiles.py │ ├── test_properties.py │ ├── test_property.py │ ├── test_scripts_csscombine.py │ ├── test_selector.py │ ├── test_selectorlist.py │ ├── test_serialize.py │ ├── test_settings.py │ ├── test_stylesheet.py │ ├── test_tokenize2.py │ ├── test_util.py │ ├── test_value.py │ └── test_x.py ├── tokenize2.py └── util.py ├── docs ├── backlog.rst ├── codec.rst ├── conf.py ├── css.rst ├── encutils.rst ├── history.rst ├── html │ ├── .buildinfo │ ├── CHANGELOG.html │ ├── README.html │ ├── _2to3 py3.html │ ├── _sources │ │ ├── CHANGELOG.txt │ │ ├── README.txt │ │ ├── docs │ │ │ ├── backlog.txt │ │ │ ├── codec.txt │ │ │ ├── css.txt │ │ │ ├── cssstyledeclaration.txt │ │ │ ├── cssutils.txt │ │ │ ├── encutils.txt │ │ │ ├── implementation.txt │ │ │ ├── logging.txt │ │ │ ├── migrate.txt │ │ │ ├── parse.txt │ │ │ ├── profiles.txt │ │ │ ├── scripts.txt │ │ │ ├── serialize.txt │ │ │ ├── settings.txt │ │ │ ├── stylesheets.txt │ │ │ ├── utilities.txt │ │ │ └── variables.txt │ │ └── index.txt │ ├── _static │ │ ├── basic.css │ │ ├── default.css │ │ ├── doctools.js │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ └── underscore.js │ ├── docs │ │ ├── backlog.html │ │ ├── codec.html │ │ ├── css.html │ │ ├── encutils.html │ │ ├── logging.html │ │ ├── migrate.html │ │ ├── parse.html │ │ ├── profiles.html │ │ ├── scripts.html │ │ ├── serialize.html │ │ ├── settings.html │ │ ├── stylesheets.html │ │ ├── utilities.html │ │ └── variables.html │ ├── genindex.html │ ├── index.html │ ├── py-modindex.html │ ├── search.html │ └── searchindex.js ├── index.rst ├── known issues.rst ├── logging.rst ├── migrate.rst ├── parse.rst ├── profiles.rst ├── scripts.rst ├── serialize.rst ├── settings.rst ├── stylesheets.rst ├── utilities.rst └── variables.rst ├── encutils └── __init__.py ├── examples ├── __init__.py ├── build.py ├── codec.py ├── cssencodings.py ├── customlog.py ├── imports.py ├── minify.py ├── parse.py ├── properties_with_same_name.py ├── selectors_tolower.py ├── serialize.py ├── style.py ├── styledeclaration.py ├── testutil.py └── website.py ├── mypy.ini ├── pyproject.toml ├── pytest.ini ├── ruff.toml ├── sheets ├── 096.css ├── 097.css ├── 1.css ├── 1ascii.css ├── 1import.css ├── 1inherit-ascii.css ├── 1inherit-iso.css ├── 1inherit-utf8.css ├── 1utf.css ├── 2inherit-iso.css ├── 2resolve.css ├── acid2.css ├── all.css ├── atrule.css ├── bad.css ├── basic.css ├── bundle.css ├── cases.css ├── csscombine-1.css ├── csscombine-2.css ├── csscombine-proxy.css ├── cthedot_default.css ├── default_html4.css ├── hacks.css ├── html.css ├── html20.css ├── html40.css ├── images │ └── example.gif ├── import.css ├── import │ ├── images2 │ │ └── example2.gif │ ├── import-impossible.css │ └── import2.css ├── import3.css ├── ll.css ├── ll2.css ├── multiple-values.css ├── page_test.css ├── sample_5.css ├── sample_7.css ├── simple.css ├── single-color.css ├── slashcode.css ├── t-HACKS.css ├── test-unicode.css ├── test.css ├── tigris.css ├── tigris2.css ├── u_simple.css ├── v_simple.css ├── var │ ├── start.css │ ├── use.css │ ├── vars.css │ └── vars2.css ├── vars.css ├── varsimport.css ├── xhtml2.css ├── xhtml22.css └── yuck.css ├── tools ├── speed.py └── try.py ├── towncrier.toml └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | # leading `*/` for pytest-dev/pytest-cov#456 4 | */.tox/* 5 | disable_warnings = 6 | couldnt-parse 7 | 8 | [report] 9 | show_missing = True 10 | exclude_also = 11 | # Exclude common false positives per 12 | # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion 13 | # Ref jaraco/skeleton#97 and jaraco/skeleton#135 14 | class .*\bProtocol\): 15 | if TYPE_CHECKING: 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 4 7 | insert_final_newline = true 8 | end_of_line = lf 9 | 10 | [*.py] 11 | indent_style = space 12 | max_line_length = 88 13 | 14 | [*.{yml,yaml}] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.rst] 19 | indent_style = space 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | tidelift: pypi/cssutils 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | merge_group: 5 | push: 6 | branches-ignore: 7 | # temporary GH branches relating to merge queues (jaraco/skeleton#93) 8 | - gh-readonly-queue/** 9 | tags: 10 | # required if branches-ignore is supplied (jaraco/skeleton#103) 11 | - '**' 12 | pull_request: 13 | workflow_dispatch: 14 | 15 | permissions: 16 | contents: read 17 | 18 | env: 19 | # Environment variable to support color support (jaraco/skeleton#66) 20 | FORCE_COLOR: 1 21 | 22 | # Suppress noisy pip warnings 23 | PIP_DISABLE_PIP_VERSION_CHECK: 'true' 24 | PIP_NO_WARN_SCRIPT_LOCATION: 'true' 25 | 26 | # Ensure tests can sense settings about the environment 27 | TOX_OVERRIDE: >- 28 | testenv.pass_env+=GITHUB_*,FORCE_COLOR 29 | 30 | 31 | jobs: 32 | test: 33 | strategy: 34 | # https://blog.jaraco.com/efficient-use-of-ci-resources/ 35 | matrix: 36 | python: 37 | - "3.9" 38 | - "3.13" 39 | platform: 40 | - ubuntu-latest 41 | - macos-latest 42 | - windows-latest 43 | include: 44 | - python: "3.10" 45 | platform: ubuntu-latest 46 | - python: "3.11" 47 | platform: ubuntu-latest 48 | - python: "3.12" 49 | platform: ubuntu-latest 50 | - python: "3.14" 51 | platform: ubuntu-latest 52 | - python: pypy3.10 53 | platform: ubuntu-latest 54 | runs-on: ${{ matrix.platform }} 55 | continue-on-error: ${{ matrix.python == '3.14' }} 56 | steps: 57 | - uses: actions/checkout@v4 58 | - name: Install build dependencies 59 | # Install dependencies for building packages on pre-release Pythons 60 | # jaraco/skeleton#161 61 | if: matrix.python == '3.14' && matrix.platform == 'ubuntu-latest' 62 | run: | 63 | sudo apt update 64 | sudo apt install -y libxml2-dev libxslt-dev 65 | - name: Setup Python 66 | uses: actions/setup-python@v5 67 | with: 68 | python-version: ${{ matrix.python }} 69 | allow-prereleases: true 70 | - name: Install tox 71 | run: python -m pip install tox 72 | - name: Run 73 | run: tox 74 | 75 | collateral: 76 | strategy: 77 | fail-fast: false 78 | matrix: 79 | job: 80 | - diffcov 81 | - docs 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | with: 86 | fetch-depth: 0 87 | - name: Setup Python 88 | uses: actions/setup-python@v5 89 | with: 90 | python-version: 3.x 91 | - name: Install tox 92 | run: python -m pip install tox 93 | - name: Eval ${{ matrix.job }} 94 | run: tox -e ${{ matrix.job }} 95 | 96 | check: # This job does nothing and is only used for the branch protection 97 | if: always() 98 | 99 | needs: 100 | - test 101 | - collateral 102 | 103 | runs-on: ubuntu-latest 104 | 105 | steps: 106 | - name: Decide whether the needed jobs succeeded or failed 107 | uses: re-actors/alls-green@release/v1 108 | with: 109 | jobs: ${{ toJSON(needs) }} 110 | 111 | release: 112 | permissions: 113 | contents: write 114 | needs: 115 | - check 116 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 117 | runs-on: ubuntu-latest 118 | 119 | steps: 120 | - uses: actions/checkout@v4 121 | - name: Setup Python 122 | uses: actions/setup-python@v5 123 | with: 124 | python-version: 3.x 125 | - name: Install tox 126 | run: python -m pip install tox 127 | - name: Run 128 | run: tox -e release 129 | env: 130 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 131 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 132 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.9.9 4 | hooks: 5 | - id: ruff 6 | args: [--fix, --unsafe-fixes] 7 | - id: ruff-format 8 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | python: 3 | install: 4 | - path: . 5 | extra_requirements: 6 | - doc 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | # required boilerplate readthedocs/readthedocs.org#10401 12 | build: 13 | os: ubuntu-lts-latest 14 | tools: 15 | python: latest 16 | # post-checkout job to ensure the clone isn't shallow jaraco/skeleton#114 17 | jobs: 18 | post_checkout: 19 | - git fetch --unshallow || true 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Contact 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | import pytest 4 | 5 | import cssutils 6 | 7 | collect_ignore = [ 8 | 'cssutils/_fetchgae.py', 9 | 'tools', 10 | ] 11 | 12 | 13 | try: 14 | importlib.import_module('lxml.etree') 15 | except ImportError: 16 | collect_ignore += ['examples/style.py'] 17 | 18 | 19 | @pytest.fixture(autouse=True) 20 | def hermetic_profiles(): 21 | """ 22 | Ensure that tests are hermetic w.r.t. profiles. 23 | """ 24 | before = list(cssutils.profile.profiles) 25 | yield 26 | assert before == cssutils.profile.profiles 27 | 28 | 29 | @pytest.fixture 30 | def saved_profiles(monkeypatch): 31 | profiles = cssutils.profiles.Profiles(log=cssutils.log) 32 | monkeypatch.setattr(cssutils, 'profile', profiles) 33 | 34 | 35 | @pytest.fixture(autouse=True) 36 | def raise_exceptions(): 37 | # configure log to raise exceptions 38 | cssutils.log.raiseExceptions = True 39 | 40 | 41 | @pytest.fixture(autouse=True) 42 | def restore_serializer_preference_defaults(): 43 | cssutils.ser.prefs.useDefaults() 44 | -------------------------------------------------------------------------------- /cssutils/_fetch.py: -------------------------------------------------------------------------------- 1 | """Default URL reading functions""" 2 | 3 | __all__ = ['_defaultFetcher'] 4 | 5 | import functools 6 | import urllib.error 7 | import urllib.request 8 | 9 | try: 10 | from importlib import metadata 11 | except ImportError: 12 | import importlib_metadata as metadata 13 | 14 | import encutils 15 | 16 | from . import errorhandler 17 | 18 | log = errorhandler.ErrorHandler() 19 | 20 | 21 | @functools.lru_cache 22 | def _get_version(): 23 | try: 24 | return metadata.version('cssutils') 25 | except metadata.PackageNotFoundError: 26 | return 'unknown' 27 | 28 | 29 | def _defaultFetcher(url): 30 | """Retrieve data from ``url``. cssutils default implementation of fetch 31 | URL function. 32 | 33 | Returns ``(encoding, string)`` or ``None`` 34 | """ 35 | try: 36 | request = urllib.request.Request(url) 37 | agent = f'cssutils/{_get_version()} (https://pypi.org/project/cssutils)' 38 | request.add_header('User-agent', agent) 39 | res = urllib.request.urlopen(request) 40 | except urllib.error.HTTPError as e: 41 | # http error, e.g. 404, e can be raised 42 | log.warn(f'HTTPError opening url={url}: {e.code} {e.msg}', error=e) 43 | except urllib.error.URLError as e: 44 | # URLError like mailto: or other IO errors, e can be raised 45 | log.warn('URLError, %s' % e.reason, error=e) 46 | except OSError as e: 47 | # e.g if file URL and not found 48 | log.warn(e, error=OSError) 49 | except ValueError as e: 50 | # invalid url, e.g. "1" 51 | log.warn('ValueError, %s' % e.args[0], error=ValueError) 52 | else: 53 | if res: 54 | mimeType, encoding = encutils.getHTTPInfo(res) 55 | if mimeType != 'text/css': 56 | log.error( 57 | 'Expected "text/css" mime type for url=%r but found: %r' 58 | % (url, mimeType), 59 | error=ValueError, 60 | ) 61 | content = res.read() 62 | if hasattr(res, 'close'): 63 | res.close() 64 | return encoding, content 65 | -------------------------------------------------------------------------------- /cssutils/_fetchgae.py: -------------------------------------------------------------------------------- 1 | """GAE specific URL reading functions""" 2 | 3 | __all__ = ['_defaultFetcher'] 4 | 5 | import email.message 6 | 7 | from google.appengine.api import urlfetch 8 | 9 | from . import errorhandler 10 | 11 | log = errorhandler.ErrorHandler() 12 | 13 | 14 | def _parse_header(content_type): 15 | msg = email.message.EmailMessage() 16 | msg['content-type'] = content_type 17 | return msg.get_content_type(), msg['content-type'].params 18 | 19 | 20 | def _defaultFetcher(url): 21 | """ 22 | uses GoogleAppEngine (GAE) 23 | fetch(url, payload=None, method=GET, headers={}, allow_truncated=False) 24 | 25 | Response 26 | content 27 | The body content of the response. 28 | content_was_truncated 29 | True if the allow_truncated parameter to fetch() was True and 30 | the response exceeded the maximum response size. In this case, 31 | the content attribute contains the truncated response. 32 | status_code 33 | The HTTP status code. 34 | headers 35 | The HTTP response headers, as a mapping of names to values. 36 | 37 | Exceptions 38 | exception InvalidURLError() 39 | The URL of the request was not a valid URL, or it used an 40 | unsupported method. Only http and https URLs are supported. 41 | exception DownloadError() 42 | There was an error retrieving the data. 43 | 44 | This exception is not raised if the server returns an HTTP 45 | error code: In that case, the response data comes back intact, 46 | including the error code. 47 | 48 | exception ResponseTooLargeError() 49 | The response data exceeded the maximum allowed size, and the 50 | allow_truncated parameter passed to fetch() was False. 51 | """ 52 | try: 53 | r = urlfetch.fetch(url, method=urlfetch.GET) 54 | except urlfetch.Error as e: 55 | log.warn(f'Error opening url={url!r}: {e}', error=IOError) 56 | return 57 | 58 | if r.status_code != 200: 59 | # TODO: 301 etc 60 | log.warn( 61 | f'Error opening url={url!r}: HTTP status {r.status_code}', 62 | error=IOError, 63 | ) 64 | return 65 | 66 | # find mimetype and encoding 67 | try: 68 | mimetype, params = _parse_header(r.headers['content-type']) 69 | encoding = params['charset'] 70 | except KeyError: 71 | mimetype = 'application/octet-stream' 72 | encoding = None 73 | if mimetype != 'text/css': 74 | log.error( 75 | f'Expected "text/css" mime type for url {url!r} but found: {mimetype!r}', 76 | error=ValueError, 77 | ) 78 | return encoding, r.content 79 | -------------------------------------------------------------------------------- /cssutils/css/__init__.py: -------------------------------------------------------------------------------- 1 | """Implements Document Object Model Level 2 CSS 2 | http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html 3 | 4 | currently implemented 5 | - CSSStyleSheet 6 | - CSSRuleList 7 | - CSSRule 8 | - CSSComment (cssutils addon) 9 | - CSSCharsetRule 10 | - CSSFontFaceRule 11 | - CSSImportRule 12 | - CSSMediaRule 13 | - CSSNamespaceRule (WD) 14 | - CSSPageRule 15 | - CSSStyleRule 16 | - CSSUnkownRule 17 | - Selector and SelectorList 18 | - CSSStyleDeclaration 19 | - CSS2Properties 20 | - CSSValue 21 | - CSSPrimitiveValue 22 | - CSSValueList 23 | - CSSVariablesRule 24 | - CSSVariablesDeclaration 25 | 26 | todo 27 | - RGBColor, Rect, Counter 28 | """ 29 | 30 | __all__ = [ 31 | 'CSSStyleSheet', 32 | 'CSSRuleList', 33 | 'CSSRule', 34 | 'CSSComment', 35 | 'CSSCharsetRule', 36 | 'CSSFontFaceRule', 37 | 'CSSImportRule', 38 | 'CSSMediaRule', 39 | 'CSSNamespaceRule', 40 | 'CSSPageRule', 41 | 'MarginRule', 42 | 'CSSStyleRule', 43 | 'CSSUnknownRule', 44 | 'CSSVariablesRule', 45 | 'CSSVariablesDeclaration', 46 | 'Selector', 47 | 'SelectorList', 48 | 'CSSStyleDeclaration', 49 | 'Property', 50 | 'PropertyValue', 51 | 'Value', 52 | 'ColorValue', 53 | 'DimensionValue', 54 | 'URIValue', 55 | 'CSSFunction', 56 | 'CSSVariable', 57 | 'MSValue', 58 | 'CSSCalc', 59 | ] 60 | 61 | from .csscharsetrule import CSSCharsetRule 62 | from .csscomment import CSSComment 63 | from .cssfontfacerule import CSSFontFaceRule 64 | from .cssimportrule import CSSImportRule 65 | from .cssmediarule import CSSMediaRule 66 | from .cssnamespacerule import CSSNamespaceRule 67 | from .csspagerule import CSSPageRule 68 | from .cssrule import CSSRule 69 | from .cssrulelist import CSSRuleList 70 | from .cssstyledeclaration import CSSStyleDeclaration 71 | from .cssstylerule import CSSStyleRule 72 | from .cssstylesheet import CSSStyleSheet 73 | from .cssunknownrule import CSSUnknownRule 74 | from .cssvariablesdeclaration import CSSVariablesDeclaration 75 | from .cssvariablesrule import CSSVariablesRule 76 | from .marginrule import MarginRule 77 | from .property import Property 78 | from .selector import Selector 79 | from .selectorlist import SelectorList 80 | from .value import ( 81 | ColorValue, 82 | CSSCalc, 83 | CSSFunction, 84 | CSSVariable, 85 | DimensionValue, 86 | MSValue, 87 | PropertyValue, 88 | URIValue, 89 | Value, 90 | ) 91 | -------------------------------------------------------------------------------- /cssutils/css/csscomment.py: -------------------------------------------------------------------------------- 1 | """CSSComment is not defined in DOM Level 2 at all but a cssutils defined 2 | class only. 3 | 4 | Implements CSSRule which is also extended for a CSSComment rule type. 5 | """ 6 | 7 | __all__ = ['CSSComment'] 8 | 9 | import xml.dom 10 | 11 | import cssutils 12 | 13 | from . import cssrule 14 | 15 | 16 | class CSSComment(cssrule.CSSRule): 17 | """ 18 | Represents a CSS comment (cssutils only). 19 | 20 | Format:: 21 | 22 | /*...*/ 23 | """ 24 | 25 | def __init__( 26 | self, cssText=None, parentRule=None, parentStyleSheet=None, readonly=False 27 | ): 28 | super().__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) 29 | 30 | self._cssText = None 31 | if cssText: 32 | self._setCssText(cssText) 33 | 34 | self._readonly = readonly 35 | 36 | def __repr__(self): 37 | return f"cssutils.css.{self.__class__.__name__}(cssText={self.cssText!r})" 38 | 39 | def __str__(self): 40 | return f"" 41 | 42 | def _getCssText(self): 43 | """Return serialized property cssText.""" 44 | return cssutils.ser.do_CSSComment(self) 45 | 46 | def _setCssText(self, cssText): 47 | """ 48 | :param cssText: 49 | textual text to set or tokenlist which is not tokenized 50 | anymore. May also be a single token for this rule 51 | 52 | :exceptions: 53 | - :exc:`~xml.dom.SyntaxErr`: 54 | Raised if the specified CSS string value has a syntax error and 55 | is unparsable. 56 | - :exc:`~xml.dom.InvalidModificationErr`: 57 | Raised if the specified CSS string value represents a different 58 | type of rule than the current one. 59 | - :exc:`~xml.dom.NoModificationAllowedErr`: 60 | Raised if the rule is readonly. 61 | """ 62 | super()._setCssText(cssText) 63 | tokenizer = self._tokenize2(cssText) 64 | 65 | commenttoken = self._nexttoken(tokenizer) 66 | unexpected = self._nexttoken(tokenizer) 67 | 68 | if ( 69 | not commenttoken 70 | or self._type(commenttoken) != self._prods.COMMENT 71 | or unexpected 72 | ): 73 | self._log.error( 74 | 'CSSComment: Not a CSSComment: %r' % self._valuestr(cssText), 75 | error=xml.dom.InvalidModificationErr, 76 | ) 77 | else: 78 | self._cssText = self._tokenvalue(commenttoken) 79 | 80 | cssText = property( 81 | _getCssText, 82 | _setCssText, 83 | doc="The parsable textual representation of this rule.", 84 | ) 85 | 86 | type = property( 87 | lambda self: self.COMMENT, 88 | doc="The type of this rule, as defined by a CSSRule type constant.", 89 | ) 90 | 91 | # constant but needed: 92 | wellformed = property(lambda self: True) 93 | -------------------------------------------------------------------------------- /cssutils/css/cssrulelist.py: -------------------------------------------------------------------------------- 1 | """CSSRuleList implements DOM Level 2 CSS CSSRuleList. 2 | Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist.""" 3 | 4 | __all__ = ['CSSRuleList'] 5 | 6 | 7 | class CSSRuleList(list): 8 | """The CSSRuleList object represents an (ordered) list of statements. 9 | 10 | The items in the CSSRuleList are accessible via an integral index, 11 | starting from 0. 12 | 13 | Subclasses a standard Python list so theoretically all standard list 14 | methods are available. Setting methods like ``__init__``, ``append``, 15 | ``extend`` or ``__setslice__`` are added later on instances of this 16 | class if so desired. 17 | E.g. CSSStyleSheet adds ``append`` which is not available in a simple 18 | instance of this class! 19 | """ 20 | 21 | def __init__(self, *ignored): 22 | "Nothing is set as this must also be defined later." 23 | pass 24 | 25 | def __notimplemented(self, *ignored): 26 | "Implemented in class using a CSSRuleList only." 27 | raise NotImplementedError( 28 | 'Must be implemented by class using an instance of this class.' 29 | ) 30 | 31 | append = extend = __setitem__ = __setslice__ = __notimplemented 32 | 33 | def item(self, index): 34 | """(DOM) Retrieve a CSS rule by ordinal `index`. The order in this 35 | collection represents the order of the rules in the CSS style 36 | sheet. If index is greater than or equal to the number of rules in 37 | the list, this returns None. 38 | 39 | Returns CSSRule, the style rule at the index position in the 40 | CSSRuleList, or None if that is not a valid index. 41 | """ 42 | try: 43 | return self[index] 44 | except IndexError: 45 | return None 46 | 47 | @property 48 | def length(self): 49 | """(DOM) The number of CSSRules in the list.""" 50 | return len(self) 51 | 52 | def rulesOfType(self, type): 53 | """Yield the rules which have the given `type` only, one of the 54 | constants defined in :class:`cssutils.css.CSSRule`.""" 55 | for r in self: 56 | if r.type == type: 57 | yield r 58 | -------------------------------------------------------------------------------- /cssutils/errorhandler.py: -------------------------------------------------------------------------------- 1 | """cssutils ErrorHandler 2 | 3 | ErrorHandler 4 | used as log with usual levels (debug, info, warn, error) 5 | 6 | if instanciated with ``raiseExceptions=True`` raises exeptions instead 7 | of logging 8 | 9 | log 10 | defaults to instance of ErrorHandler for any kind of log message from 11 | lexerm, parser etc. 12 | 13 | - raiseExceptions = [False, True] 14 | - setloglevel(loglevel) 15 | """ 16 | 17 | __all__ = ['ErrorHandler'] 18 | 19 | import logging 20 | import urllib.error 21 | import urllib.parse 22 | import urllib.request 23 | import xml.dom 24 | 25 | 26 | class _ErrorHandler: 27 | """ 28 | handles all errors and log messages 29 | """ 30 | 31 | def __init__(self, log, defaultloglevel=logging.INFO, raiseExceptions=True): 32 | """ 33 | inits log if none given 34 | 35 | log 36 | for parse messages, default logs to sys.stderr 37 | defaultloglevel 38 | if none give this is logging.DEBUG 39 | raiseExceptions 40 | - True: Errors will be raised e.g. during building 41 | - False: Errors will be written to the log, this is the 42 | default behaviour when parsing 43 | """ 44 | # may be disabled during setting of known valid items 45 | self.enabled = True 46 | 47 | if log: 48 | self._log = log 49 | else: 50 | import sys 51 | 52 | self._log = logging.getLogger('CSSUTILS') 53 | hdlr = logging.StreamHandler(sys.stderr) 54 | formatter = logging.Formatter('%(levelname)s\t%(message)s') 55 | hdlr.setFormatter(formatter) 56 | self._log.addHandler(hdlr) 57 | self._log.setLevel(defaultloglevel) 58 | 59 | self.raiseExceptions = raiseExceptions 60 | 61 | def __getattr__(self, name): 62 | "use self._log items" 63 | calls = ('debug', 'info', 'warn', 'error', 'critical', 'fatal') 64 | other = ('setLevel', 'getEffectiveLevel', 'addHandler', 'removeHandler') 65 | 66 | if name in calls: 67 | if name == 'warn': 68 | name = 'warning' 69 | self._logcall = getattr(self._log, name) 70 | return self.__handle 71 | elif name in other: 72 | return getattr(self._log, name) 73 | else: 74 | raise AttributeError('(errorhandler) No Attribute %r found' % name) 75 | 76 | def __handle( 77 | self, msg='', token=None, error=xml.dom.SyntaxErr, neverraise=False, args=None 78 | ): 79 | """ 80 | handles all calls 81 | logs or raises exception 82 | """ 83 | if self.enabled: 84 | if error is None: 85 | error = xml.dom.SyntaxErr 86 | 87 | line, col = None, None 88 | if token: 89 | if isinstance(token, tuple): 90 | value, line, col = token[1], token[2], token[3] 91 | else: 92 | value, line, col = token.value, token.line, token.col 93 | msg = f'{msg} [{line}:{col}: {value}]' 94 | 95 | if error and self.raiseExceptions and not neverraise: 96 | if isinstance(error, urllib.error.HTTPError) or isinstance( 97 | error, urllib.error.URLError 98 | ): 99 | raise 100 | elif issubclass(error, xml.dom.DOMException): 101 | error.line = line 102 | error.col = col 103 | raise error(msg) 104 | else: 105 | self._logcall(msg) 106 | 107 | def setLog(self, log): 108 | """set log of errorhandler's log""" 109 | self._log = log 110 | 111 | 112 | class ErrorHandler(_ErrorHandler): 113 | "Singleton, see _ErrorHandler" 114 | 115 | instance = None 116 | 117 | def __init__(self, log=None, defaultloglevel=logging.INFO, raiseExceptions=True): 118 | if ErrorHandler.instance is None: 119 | ErrorHandler.instance = _ErrorHandler( 120 | log=log, 121 | defaultloglevel=defaultloglevel, 122 | raiseExceptions=raiseExceptions, 123 | ) 124 | self.__dict__ = ErrorHandler.instance.__dict__ 125 | -------------------------------------------------------------------------------- /cssutils/helper.py: -------------------------------------------------------------------------------- 1 | """cssutils helper TEST""" 2 | 3 | import itertools 4 | import os 5 | import re 6 | import urllib.error 7 | import urllib.parse 8 | import urllib.request 9 | 10 | 11 | class Deprecated: 12 | """This is a decorator which can be used to mark functions 13 | as deprecated. It will result in a warning being emitted 14 | when the function is used. 15 | 16 | It accepts a single paramter ``msg`` which is shown with the warning. 17 | It should contain information which function or method to use instead. 18 | """ 19 | 20 | def __init__(self, msg): 21 | self.msg = msg 22 | 23 | def __call__(self, func): 24 | def newFunc(*args, **kwargs): 25 | import warnings 26 | 27 | warnings.warn( 28 | f"Call to deprecated method {func.__name__!r}. {self.msg}", 29 | category=DeprecationWarning, 30 | stacklevel=2, 31 | ) 32 | return func(*args, **kwargs) 33 | 34 | newFunc.__name__ = func.__name__ 35 | newFunc.__doc__ = func.__doc__ 36 | newFunc.__dict__.update(func.__dict__) 37 | return newFunc 38 | 39 | 40 | # simple escapes, all non unicodes 41 | _simpleescapes = re.compile(r'(\\[^0-9a-fA-F])').sub 42 | 43 | 44 | def normalize(x): 45 | r""" 46 | normalizes x, namely: 47 | 48 | - remove any \ before non unicode sequences (0-9a-zA-Z) so for 49 | x==r"c\olor\" return "color" (unicode escape sequences should have 50 | been resolved by the tokenizer already) 51 | - lowercase 52 | """ 53 | if x: 54 | 55 | def removeescape(matchobj): 56 | return matchobj.group(0)[1:] 57 | 58 | x = _simpleescapes(removeescape, x) 59 | return x.lower() 60 | else: 61 | return x 62 | 63 | 64 | def path2url(path): 65 | """Return file URL of `path`""" 66 | return 'file:' + urllib.request.pathname2url(os.path.abspath(path)) 67 | 68 | 69 | def pushtoken(token, tokens): 70 | """Return new generator starting with token followed by all tokens in 71 | ``tokens``""" 72 | return itertools.chain([token], tokens) 73 | 74 | 75 | def string(value): 76 | """ 77 | Serialize value with quotes e.g.:: 78 | 79 | ``a \'string`` => ``'a \'string'`` 80 | """ 81 | # \n = 0xa, \r = 0xd, \f = 0xc 82 | value = ( 83 | value.replace('\n', '\\a ') 84 | .replace('\r', '\\d ') 85 | .replace('\f', '\\c ') 86 | .replace('"', '\\"') 87 | ) 88 | 89 | if value.endswith('\\'): 90 | value = value[:-1] + '\\\\' 91 | 92 | return '"%s"' % value 93 | 94 | 95 | def stringvalue(string): 96 | """ 97 | Retrieve actual value of string without quotes. Escaped 98 | quotes inside the value are resolved, e.g.:: 99 | 100 | ``'a \'string'`` => ``a 'string`` 101 | """ 102 | return string.replace('\\' + string[0], string[0])[1:-1] 103 | 104 | 105 | _match_forbidden_in_uri = re.compile(r'''.*?[\(\)\s\;,'"]''', re.U).match 106 | 107 | 108 | def uri(value): 109 | """ 110 | Serialize value by adding ``url()`` and with quotes if needed e.g.:: 111 | 112 | ``"`` => ``url("\"")`` 113 | """ 114 | if _match_forbidden_in_uri(value): 115 | value = string(value) 116 | return 'url(%s)' % value 117 | 118 | 119 | def urivalue(uri): 120 | """ 121 | Return actual content without surrounding "url(" and ")" 122 | and removed surrounding quotes too including contained 123 | escapes of quotes, e.g.:: 124 | 125 | ``url("\"")`` => ``"`` 126 | """ 127 | uri = uri[uri.find('(') + 1 : -1].strip() 128 | if uri and (uri[0] in '\'"') and (uri[0] == uri[-1]): 129 | return stringvalue(uri) 130 | else: 131 | return uri 132 | -------------------------------------------------------------------------------- /cssutils/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | from .csscombine import csscombine 2 | 3 | __all__ = ['csscombine'] 4 | -------------------------------------------------------------------------------- /cssutils/scripts/csscapture.py: -------------------------------------------------------------------------------- 1 | """Retrieve all CSS stylesheets including embedded for a given URL. 2 | Retrieve as StyleSheetList or save to disk - raw, parsed or minified version. 3 | 4 | TODO: 5 | - maybe use DOM 3 load/save? 6 | - logger class which handles all cases when no log is given... 7 | - saveto: why does urllib2 hang? 8 | """ 9 | 10 | __all__ = ['CSSCapture'] 11 | 12 | import logging 13 | import optparse 14 | import sys 15 | 16 | from cssutils.script import CSSCapture 17 | 18 | 19 | def main(args=None): 20 | usage = "usage: %prog [options] URL" 21 | parser = optparse.OptionParser(usage=usage) 22 | parser.add_option( 23 | '-d', 24 | '--debug', 25 | action='store_true', 26 | dest='debug', 27 | help='show debug messages during capturing', 28 | ) 29 | parser.add_option( 30 | '-m', 31 | '--minified', 32 | action='store_true', 33 | dest='minified', 34 | help='saves minified version of captured files', 35 | ) 36 | parser.add_option( 37 | '-n', 38 | '--notsave', 39 | action='store_true', 40 | dest='notsave', 41 | help='if given files are NOT saved, only log is written', 42 | ) 43 | # parser.add_option('-r', '--saveraw', action='store_true', dest='saveraw', 44 | # help='if given saves raw css otherwise cssutils\' parsed files') 45 | parser.add_option( 46 | '-s', 47 | '--saveto', 48 | action='store', 49 | dest='saveto', 50 | help='saving retrieved files to "saveto", defaults to "_CSSCapture_SAVED"', 51 | ) 52 | parser.add_option( 53 | '-u', 54 | '--useragent', 55 | action='store', 56 | dest='ua', 57 | help='useragent to use for request of URL, default is urllib2s default', 58 | ) 59 | options, url = parser.parse_args() 60 | 61 | # TODO: 62 | options.saveraw = False 63 | 64 | if not url: 65 | parser.error('no URL given') 66 | else: 67 | url = url[0] 68 | 69 | if options.debug: 70 | level = logging.DEBUG 71 | else: 72 | level = logging.INFO 73 | 74 | # START 75 | c = CSSCapture(ua=options.ua, defaultloglevel=level) 76 | 77 | stylesheetlist = c.capture(url) 78 | 79 | if options.notsave is None or not options.notsave: 80 | if options.saveto: 81 | saveto = options.saveto 82 | else: 83 | saveto = '_CSSCapture_SAVED' 84 | c.saveto(saveto, saveraw=options.saveraw, minified=options.minified) 85 | else: 86 | for i, s in enumerate(stylesheetlist): 87 | print( 88 | '''%s. 89 | encoding: %r 90 | title: %r 91 | href: %r''' 92 | % (i + 1, s.encoding, s.title, s.href) 93 | ) 94 | 95 | 96 | if __name__ == "__main__": 97 | sys.exit(main()) 98 | -------------------------------------------------------------------------------- /cssutils/scripts/csscombine.py: -------------------------------------------------------------------------------- 1 | r"""Combine all sheets referred to a given CSS *proxy* sheet 2 | into a single new sheet. 3 | 4 | - no ``url()`` values are adjusted so currently when using relative references 5 | for e.g. images it is best to have all sheets in a single folder 6 | - in @import rules only relative paths do work for now but should be used 7 | anyway 8 | - messages are send to stderr 9 | - output to stdout. 10 | 11 | Example:: 12 | 13 | csscombine sheets\csscombine-proxy.css -m -t ascii -s utf-8 14 | 1>combined.css 2>log.txt 15 | 16 | results in log.txt:: 17 | 18 | COMBINING sheets/csscombine-proxy.css 19 | USING SOURCE ENCODING: css 20 | * PROCESSING @import sheets\csscombine-1.css 21 | * PROCESSING @import sheets\csscombine-2.css 22 | INFO Nested @imports are not combined: @import "1.css"; 23 | SETTING TARGET ENCODING: ascii 24 | 25 | and combined.css:: 26 | 27 | @charset "ascii";@import"1.css";@namespaces2"uri";s2|sheet-1\ 28 | {top:1px}s2|sheet-2{top:2px}proxy{top:3px} 29 | 30 | or without option -m:: 31 | 32 | @charset "ascii"; 33 | @import "1.css"; 34 | @namespace s2 "uri"; 35 | @namespace other "other"; 36 | /* proxy sheet were imported sheets should be combined */ 37 | /* non-ascii chars: \F6 \E4 \FC */ 38 | /* @import "csscombine-1.css"; */ 39 | /* combined sheet 1 */ 40 | s2|sheet-1 { 41 | top: 1px 42 | } 43 | /* @import url(csscombine-2.css); */ 44 | /* combined sheet 2 */ 45 | s2|sheet-2 { 46 | top: 2px 47 | } 48 | proxy { 49 | top: 3px 50 | } 51 | 52 | """ 53 | 54 | __all__ = ['csscombine'] 55 | 56 | import optparse 57 | import sys 58 | 59 | from cssutils.script import csscombine 60 | 61 | 62 | def main(args=None): 63 | usage = "usage: %prog [options] [path]" 64 | parser = optparse.OptionParser(usage=usage) 65 | parser.add_option( 66 | '-u', 67 | '--url', 68 | action='store', 69 | dest='url', 70 | help='URL to parse (path is ignored if URL given)', 71 | ) 72 | parser.add_option( 73 | '-s', 74 | '--sourceencoding', 75 | action='store', 76 | dest='sourceencoding', 77 | help='encoding of input, defaulting to "css". ' 78 | 'If given overwrites other encoding information like @charset declarations', 79 | ) 80 | parser.add_option( 81 | '-t', 82 | '--targetencoding', 83 | action='store', 84 | dest='targetencoding', 85 | help='encoding of output, defaulting to "UTF-8"', 86 | default='utf-8', 87 | ) 88 | parser.add_option( 89 | '-m', 90 | '--minify', 91 | action='store_true', 92 | dest='minify', 93 | default=False, 94 | help='saves minified version of combined files, defaults to False', 95 | ) 96 | options, path = parser.parse_args() 97 | 98 | if options.url: 99 | print( 100 | csscombine( 101 | url=options.url, 102 | sourceencoding=options.sourceencoding, 103 | targetencoding=options.targetencoding, 104 | minify=options.minify, 105 | ) 106 | ) 107 | elif path: 108 | print( 109 | csscombine( 110 | path=path[0], 111 | sourceencoding=options.sourceencoding, 112 | targetencoding=options.targetencoding, 113 | minify=options.minify, 114 | ) 115 | ) 116 | else: 117 | parser.error('no path or URL (-u) given') 118 | 119 | 120 | if __name__ == '__main__': 121 | sys.exit(main()) 122 | -------------------------------------------------------------------------------- /cssutils/scripts/cssparse.py: -------------------------------------------------------------------------------- 1 | """utility script to parse given filenames or string""" 2 | 3 | import logging 4 | import optparse 5 | import sys 6 | 7 | import cssutils 8 | 9 | 10 | def main(args=None): 11 | """ 12 | Parses given filename(s) or string or URL (using optional encoding) and 13 | prints the parsed style sheet to stdout. 14 | 15 | Redirect stdout to save CSS. Redirect stderr to save parser log infos. 16 | """ 17 | usage = """usage: %prog [options] filename1.css [filename2.css ...] 18 | [>filename_combined.css] [2>parserinfo.log] """ 19 | p = optparse.OptionParser(usage=usage) 20 | p.add_option( 21 | '-s', '--string', action='store_true', dest='string', help='parse given string' 22 | ) 23 | p.add_option('-u', '--url', action='store', dest='url', help='parse given url') 24 | p.add_option( 25 | '-e', 26 | '--encoding', 27 | action='store', 28 | dest='encoding', 29 | help='encoding of the file or override encoding found', 30 | ) 31 | p.add_option( 32 | '-m', 33 | '--minify', 34 | action='store_true', 35 | dest='minify', 36 | help='minify parsed CSS', 37 | default=False, 38 | ) 39 | p.add_option( 40 | '-d', 41 | '--debug', 42 | action='store_true', 43 | dest='debug', 44 | help='activate debugging output', 45 | ) 46 | 47 | (options, params) = p.parse_args(args) 48 | 49 | if not params and not options.url: 50 | p.error("no filename given") 51 | 52 | if options.debug: 53 | p = cssutils.CSSParser(loglevel=logging.DEBUG) 54 | else: 55 | p = cssutils.CSSParser() 56 | 57 | if options.minify: 58 | cssutils.ser.prefs.useMinified() 59 | 60 | if options.string: 61 | sheet = p.parseString(''.join(params), encoding=options.encoding) 62 | print(sheet.cssText) 63 | elif options.url: 64 | sheet = p.parseUrl(options.url, encoding=options.encoding) 65 | print(sheet.cssText) 66 | else: 67 | for filename in params: 68 | sys.stderr.write('=== CSS FILE: "%s" ===\n' % filename) 69 | sheet = p.parseFile(filename, encoding=options.encoding) 70 | print(sheet.cssText) 71 | print() 72 | sys.stderr.write('\n') 73 | 74 | 75 | if __name__ == "__main__": 76 | sys.exit(main()) 77 | -------------------------------------------------------------------------------- /cssutils/settings.py: -------------------------------------------------------------------------------- 1 | """Experimental settings for special stuff.""" 2 | 3 | 4 | def set(key, value): 5 | """Call to enable special settings: 6 | 7 | ('DXImageTransform.Microsoft', True) 8 | enable support for parsing special MS only filter values 9 | 10 | Clears the tokenizer cache which holds the compiled productions! 11 | """ 12 | if key == 'DXImageTransform.Microsoft' and value is True: 13 | from . import cssproductions, tokenize2 14 | 15 | tokenize2._TOKENIZER_CACHE.clear() 16 | cssproductions.PRODUCTIONS.insert(1, cssproductions._DXImageTransform) 17 | -------------------------------------------------------------------------------- /cssutils/stylesheets/__init__.py: -------------------------------------------------------------------------------- 1 | """Implements Document Object Model Level 2 Style Sheets 2 | http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/stylesheets.html 3 | """ 4 | 5 | __all__ = ['MediaList', 'MediaQuery', 'StyleSheet', 'StyleSheetList'] 6 | 7 | from .medialist import MediaList 8 | from .mediaquery import MediaQuery 9 | from .stylesheet import StyleSheet 10 | from .stylesheetlist import StyleSheetList 11 | -------------------------------------------------------------------------------- /cssutils/stylesheets/stylesheetlist.py: -------------------------------------------------------------------------------- 1 | """StyleSheetList implements DOM Level 2 Style Sheets StyleSheetList.""" 2 | 3 | __all__ = ['StyleSheetList'] 4 | 5 | 6 | class StyleSheetList(list): 7 | """Interface `StyleSheetList` (introduced in DOM Level 2) 8 | 9 | The `StyleSheetList` interface provides the abstraction of an ordered 10 | collection of :class:`~cssutils.stylesheets.StyleSheet` objects. 11 | 12 | The items in the `StyleSheetList` are accessible via an integral index, 13 | starting from 0. 14 | 15 | This Python implementation is based on a standard Python list so e.g. 16 | allows ``examplelist[index]`` usage. 17 | """ 18 | 19 | def item(self, index): 20 | """ 21 | Used to retrieve a style sheet by ordinal `index`. If `index` is 22 | greater than or equal to the number of style sheets in the list, 23 | this returns ``None``. 24 | """ 25 | try: 26 | return self[index] 27 | except IndexError: 28 | return None 29 | 30 | length = property( 31 | lambda self: len(self), 32 | doc="The number of :class:`StyleSheet` objects in the list. The range" 33 | " of valid child stylesheet indices is 0 to length-1 inclusive.", 34 | ) 35 | -------------------------------------------------------------------------------- /cssutils/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """cssutils unittests""" 2 | -------------------------------------------------------------------------------- /cssutils/tests/basetest.py: -------------------------------------------------------------------------------- 1 | """Base class for all tests""" 2 | 3 | import sys 4 | 5 | import pytest 6 | 7 | if sys.version_info >= (3, 9): 8 | from importlib import resources 9 | else: 10 | import importlib_resources as resources 11 | 12 | import cssutils 13 | 14 | 15 | def get_sheet_filename(sheet_name): 16 | """Get the filename for the given sheet.""" 17 | return resources.files('cssutils') / 'tests' / 'sheets' / sheet_name 18 | 19 | 20 | class BaseTestCase: 21 | @staticmethod 22 | def do_equal_p(tests, att='cssText', raising=True): 23 | p = cssutils.CSSParser(raiseExceptions=raising) 24 | # parse and check att of result 25 | for test, expected in tests.items(): 26 | s = p.parseString(test) 27 | if expected is None: 28 | expected = test 29 | assert str(s.__getattribute__(att), 'utf-8') == expected 30 | 31 | @staticmethod 32 | def do_raise_p(tests, raising=True): 33 | # parse and expect raise 34 | p = cssutils.CSSParser(raiseExceptions=raising) 35 | for test, expected in tests.items(): 36 | with pytest.raises(expected): 37 | p.parseString(test) 38 | 39 | def do_equal_r(self, tests, att='cssText'): 40 | # set attribute att of self.r and assert Equal 41 | for test, expected in tests.items(): 42 | self.r.__setattr__(att, test) 43 | if expected is None: 44 | expected = test 45 | assert self.r.__getattribute__(att) == expected 46 | 47 | def do_raise_r(self, tests, att='_setCssText'): 48 | # set self.r and expect raise 49 | for test, expected in tests.items(): 50 | with pytest.raises(expected): 51 | self.r.__getattribute__(att)(test) 52 | 53 | def do_raise_r_list(self, tests, err, att='_setCssText'): 54 | # set self.r and expect raise 55 | for test in tests: 56 | with pytest.raises(err): 57 | self.r.__getattribute__(att)(test) 58 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /*äöüß*/ 3 | /* κουρος */ 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1ascii.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | @import "1inherit-ascii.css"; 3 | @import "1utf.css"; 4 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1import.css: -------------------------------------------------------------------------------- 1 | @charset "iso-8859-1"; 2 | @import "1inherit-iso.css"; 3 | 4 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1inherit-ascii.css: -------------------------------------------------------------------------------- 1 | /* inherited encoding ascii */ 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1inherit-iso.css: -------------------------------------------------------------------------------- 1 | @import "2inherit-iso.css"; 2 | /* 1 inherited encoding iso-8859-1 */ 3 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1inherit-utf8.css: -------------------------------------------------------------------------------- 1 | /* inherit encoding utf-8 */ 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/1utf.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import "1inherit-utf8.css"; 3 | /* € */ 4 | 5 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/2inherit-iso.css: -------------------------------------------------------------------------------- 1 | /* 2 inherited encoding iso-8859-1 */ 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/2resolve.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | @namespace "x"; 3 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/all.css: -------------------------------------------------------------------------------- 1 | /* this sheet should be used in the HTML, deploy.py does resolve 2 | * all imports so that only a single CSS is deployed to cut down 3 | * the number of HTTP requests 4 | */ 5 | @import url(1.css); 6 | @import url(atrule.css); 7 | @import url(simple.css); 8 | @namespace a 'x'; -------------------------------------------------------------------------------- /cssutils/tests/sheets/atrule.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @three-dee { 3 | @background-lighting { 4 | azimuth: 30deg; 5 | elevation: 190deg; 6 | } 7 | H1 { color: red } 8 | } 9 | @page { 10 | foo: bar 11 | } 12 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/bad.css: -------------------------------------------------------------------------------- 1 | P { color: yellow; background-color: blue; 1rogue: catch me if you can } 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/basic.css: -------------------------------------------------------------------------------- 1 | { foo: 1.5; bogus: 3, 2, 1; bar-color: #0FEED0; background: #abc; foreground: rgb( 10, 20, 30 ) } 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/cases.css: -------------------------------------------------------------------------------- 1 | @import url("a"); 2 | b{color:red} 3 | a{float:left;bogus:foo(a, url(a))} 4 | b{color:red} 5 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/csscombine-1.css: -------------------------------------------------------------------------------- 1 | @charset "iso-8859-1"; 2 | /* combined sheet 1 */ 3 | @namespace s1 "uri"; 4 | s1|sheet-1 { 5 | top: 1px 6 | } -------------------------------------------------------------------------------- /cssutils/tests/sheets/csscombine-2.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | /* combined sheet 2 */ 3 | @import "1.css"; 4 | @namespace s2 "uri"; 5 | s2|sheet-2 { 6 | top: 2px; 7 | } -------------------------------------------------------------------------------- /cssutils/tests/sheets/csscombine-proxy.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* proxy sheet were imported sheets should be combined */ 3 | /* non-ascii chars: öäü */ 4 | @import "csscombine-1.css"; 5 | @import url(csscombine-2.css); 6 | @namespace other "other"; 7 | proxy { top: 3px } 8 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/cthedot_default.css: -------------------------------------------------------------------------------- 1 | /* default stylesheet for all variations */ 2 | .jsonly { display: none; } 3 | 4 | .stylenav { 5 | text-align: right; 6 | margin-top: -1em; 7 | } 8 | 9 | html, body { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | body { 14 | font: normal 90%/1.1 Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; 15 | color: #fff; 16 | background-color: #344; 17 | } 18 | 19 | h1, h2, h3, h4, h5, h6 { 20 | font: normal 2.4em Verdana, "Lucida Grande", Arial, Helvetica, sans-serif; 21 | } 22 | h1, h2, h3, caption { 23 | color: #a00; 24 | margin: 0.3em -0.4em 0.5em -0.5em; 25 | } 26 | h2, h3, caption { 27 | margin: 0.3em -1.3em 0.3em 0; 28 | } 29 | h2 { 30 | font-size: 1.5em; 31 | border-right: 1.3em solid #677; 32 | border-bottom: 1px dotted #677; 33 | padding-left: 0.1em; 34 | padding-bottom: 0.1em; 35 | margin-top: 2.1em; 36 | margin-bottom: 0.4em; 37 | } 38 | h3 { 39 | font-size: 0.9em; 40 | font-weight: bold; 41 | text-transform: uppercase; 42 | margin-top: 1.2em; 43 | margin-bottom: 0.1em; 44 | } 45 | caption { 46 | font-size: 1.05em; 47 | text-align: left; 48 | margin-bottom: 0.2em; 49 | } 50 | h4 { 51 | font-size: 0.9em; 52 | font-weight: bold; 53 | margin: 1.2em 0 0; 54 | } 55 | h5, h6 { 56 | font-size: 1em; 57 | margin: 0; 58 | } 59 | h6 { 60 | font-size: 0.9em; 61 | } 62 | 63 | p, ol, ul, dl { 64 | line-height: 1.3; 65 | margin-top: 0; 66 | margin-bottom: 1em; 67 | } 68 | ul { 69 | list-style-type: square; 70 | } 71 | ul.code { 72 | line-height: 1.3; 73 | } 74 | li, dd { 75 | margin-bottom: 0.3em; 76 | } 77 | dt { 78 | font-weight: bold; 79 | } 80 | pre, code { 81 | color: #00a; 82 | line-height: 1.4; 83 | font-size: 1.1em; 84 | } 85 | table code, table pre { 86 | font-size: 1.3em; 87 | } 88 | .deprecated { 89 | color: #888; 90 | } 91 | table { 92 | font-size: 0.9em; 93 | border-collapse: collapse; 94 | } 95 | tr { 96 | vertical-align: top; 97 | line-height: 1.3; 98 | } 99 | td, th { 100 | text-align: left; 101 | padding: 0.4em 0.5em; 102 | border-bottom: 1px dotted #667; 103 | } 104 | td.center { 105 | text-align: center; 106 | } 107 | 108 | tr:hover, li:hover { 109 | background-color: #f8f8f8; 110 | } 111 | 112 | 113 | acronym, .explain { 114 | border-bottom: 1px dotted #344; 115 | } 116 | a { 117 | text-decoration: none; 118 | color: #fff; 119 | border-bottom: 1px solid #aaa; 120 | } 121 | #main a { 122 | color: #a00; 123 | } 124 | a:visited { 125 | color: #eee; 126 | } 127 | #main a:visited { 128 | color: #344; 129 | } 130 | a:hover { 131 | text-decoration: underline; 132 | color: #fff; 133 | } 134 | #main a:hover { 135 | background-color: #f5f8ff; 136 | } 137 | #main a:active { 138 | color: #fff; 139 | background-color: #abb; 140 | } 141 | 142 | label { 143 | display: block; 144 | padding: 0.5em 0 0.1em; 145 | } 146 | input, textarea { 147 | font: bold 1em Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; 148 | background-color: #eee; 149 | width: 100%; 150 | border: 1px inset; 151 | } 152 | #submit, textarea { 153 | margin-bottom: 1.5em; 154 | } 155 | #submit { 156 | font-weight: bold; 157 | color: #00a; 158 | background-color: #fff; 159 | border: 1px outset; 160 | margin-top: 2em; 161 | } 162 | input:focus, input:hover, textarea:focus, textarea:hover { 163 | font-weight: bold; 164 | background-color: #fff; 165 | border-style: solid; 166 | } 167 | #submit:hover, #submit:focus { 168 | background-color: #eee; 169 | border-style: solid; 170 | } 171 | #submit:active { 172 | border-style: inset; 173 | } 174 | 175 | #header { 176 | padding: 1.1em 5% 0; 177 | padding: 40px 5% 0; 178 | color: #334; 179 | background: #fff url(/img/header_r.jpg) no-repeat; 180 | border-bottom: 1px solid #344; 181 | height: 90px; 182 | he\ight: 50px; 183 | } 184 | #header dt, #header p { 185 | font-family: Arial, Helvetica, sans-serif; 186 | letter-spacing: 0.3em; 187 | } 188 | #header a { 189 | color: #334; 190 | } 191 | #main .nav { 192 | padding-bottom: 0.5em; 193 | border-bottom: 3px solid #eee; 194 | margin-bottom: 1em; 195 | margin-left: -8%; 196 | } 197 | .nav dt , .nav dd, .nav dd ul, .nav li { 198 | display: inline; 199 | } 200 | .nav dt { 201 | font-weight: bold; 202 | } 203 | .nav li { 204 | font-weight: bold; 205 | padding-right: 0.5em; 206 | } 207 | .nav a { 208 | font-weight: normal; 209 | } 210 | #footer { 211 | padding: 1em 5% 1em 10%; 212 | border-top: 3px double #fff; 213 | text-align: right; 214 | } 215 | #main { 216 | color: #000; 217 | background-color: #fff; 218 | padding: 1em 26% 1em 12%; 219 | } 220 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/default_html4.css: -------------------------------------------------------------------------------- 1 | /* default HTML 4 CSSStyleSheet 2 | from http://www.w3.org/TR/2004/CR-CSS21-20040225/sample.html */ 3 | html, address, blockquote, body, dd, div, dl, dt, fieldset, form, frame, frameset, h1, h2, h3, h4, h5, h6, noframes, ol, p, ul, center, dir, hr, menu, pre { 4 | display: block 5 | } 6 | li { 7 | display: list-item 8 | } 9 | head { 10 | display: none 11 | } 12 | table { 13 | display: table 14 | } 15 | tr { 16 | display: table-row 17 | } 18 | thead { 19 | display: table-header-group 20 | } 21 | tbody { 22 | display: table-row-group 23 | } 24 | tfoot { 25 | display: table-footer-group 26 | } 27 | col { 28 | display: table-column 29 | } 30 | colgroup { 31 | display: table-column-group 32 | } 33 | td, th { 34 | display: table-cell 35 | } 36 | caption { 37 | display: table-caption 38 | } 39 | th { 40 | font-weight: bolder; 41 | text-align: center 42 | } 43 | caption { 44 | text-align: center 45 | } 46 | body { 47 | margin: 8px; 48 | line-height: 1.12 49 | } 50 | h1 { 51 | font-size: 2em; 52 | margin: .67em 0 53 | } 54 | h2 { 55 | font-size: 1.5em; 56 | margin: .75em 0 57 | } 58 | h3 { 59 | font-size: 1.17em; 60 | margin: .83em 0 61 | } 62 | h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu { 63 | margin: 1.12em 0 64 | } 65 | h5 { 66 | font-size: .83em; 67 | margin: 1.5em 0 68 | } 69 | h6 { 70 | font-size: .75em; 71 | margin: 1.67em 0 72 | } 73 | h1, h2, h3, h4, h5, h6, b, strong { 74 | font-weight: bolder 75 | } 76 | blockquote { 77 | margin-left: 40px; 78 | margin-right: 40px 79 | } 80 | i, cite, em, var, address { 81 | font-style: italic 82 | } 83 | pre, tt, code, kbd, samp { 84 | font-family: monospace 85 | } 86 | pre { 87 | white-space: pre 88 | } 89 | button, textarea, input, object, select { 90 | display: inline-block 91 | } 92 | big { 93 | font-size: 1.17em 94 | } 95 | small, sub, sup { 96 | font-size: .83em 97 | } 98 | sub { 99 | vertical-align: sub 100 | } 101 | sup { 102 | vertical-align: super 103 | } 104 | table { 105 | border-spacing: 2px; 106 | } 107 | thead, tbody, tfoot { 108 | vertical-align: middle 109 | } 110 | td, th { 111 | vertical-align: inherit 112 | } 113 | s, strike, del { 114 | text-decoration: line-through 115 | } 116 | hr { 117 | border: 1px inset 118 | } 119 | ol, ul, dir, menu, dd { 120 | margin-left: 40px 121 | } 122 | ol { 123 | list-style-type: decimal 124 | } 125 | ol ul, ul ol, ul ul, ol ol { 126 | margin-top: 0; 127 | margin-bottom: 0 128 | } 129 | u, ins { 130 | text-decoration: underline 131 | } 132 | br:before { 133 | content: "\A" 134 | 135 | } 136 | :before, :after { 137 | white-space: pre-line 138 | } 139 | center { 140 | text-align: center 141 | } 142 | abbr, acronym { 143 | font-variant: small-caps; 144 | letter-spacing: 0.1em 145 | } 146 | :link, :visited { 147 | text-decoration: underline 148 | } 149 | :focus { 150 | outline: thin dotted invert 151 | } 152 | /* Begin bidirectionality settings (do not change) */ 153 | BDO[DIR="ltr"] { 154 | direction: ltr; 155 | unicode-bidi: bidi-override 156 | } 157 | BDO[DIR="rtl"] { 158 | direction: rtl; 159 | unicode-bidi: bidi-override 160 | } 161 | *[DIR="ltr"] { 162 | direction: ltr; 163 | unicode-bidi: embed 164 | } 165 | *[DIR="rtl"] { 166 | direction: rtl; 167 | unicode-bidi: embed 168 | } 169 | @media print { 170 | h1 { 171 | page-break-before: always 172 | } 173 | h1, h2, h3, h4, h5, h6 { 174 | page-break-after: avoid 175 | } 176 | ul, ol, dl { 177 | page-break-before: avoid 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/hacks.css: -------------------------------------------------------------------------------- 1 | .normal {background-color: gray;} 2 | .backslash {bac\kground-color: gray;} 3 | html>body .childselector {background-color: green;} 4 | html>/**/body .childselector-with-comment {background-color: orange} 5 | .colon-default2, x:default 6 | html>/**/body .colon-default, x:default 7 | *:not(hr) .not-hr {background-color: red;} 8 | * html .ie-only-1 {background-color: blue;} 9 | *+html .ie-only-2 {background-color: blue;} 10 | *+html .ie-only-3 {background-color: blue;} 11 | html:first-child .first-child-2 {background-color: red;} 12 | /* does not work as CSSUnknownRule read: 13 | @mediaall { .mediaall { background-color: red; }} 14 | */ 15 | .not-class:not([class='XXX']) {background-color: red;} 16 | @media all and (min-width: 0) { .mediaquery { background-color: red;} } 17 | 18 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/html.css: -------------------------------------------------------------------------------- 1 | body { color: red } 2 | 3 | body { color: blue } 4 | body { color: pink } 5 | 6 | body { color: green } 7 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/html20.css: -------------------------------------------------------------------------------- 1 | 2 | BODY { 3 | margin: 1em; 4 | font-family: serif; 5 | line-height: 1.1; 6 | background: white; 7 | color: black 8 | } 9 | 10 | H1, H2, H3, H4, H5, H6, P, UL, OL, DIR, MENU, DIV, 11 | DT, DD, ADDRESS, BLOCKQUOTE, PRE, BR, HR { display: block } 12 | 13 | B, STRONG, I, EM, CITE, VAR, TT, CODE, KBD, SAMP, 14 | IMG, SPAN { display: inline } 15 | 16 | LI { display: list-item } 17 | 18 | H1, H2, H3, H4 { margin-top: 1em; margin-bottom: 1em } 19 | H5, H6 { margin-top: 1em } 20 | H1 { text-align: center } 21 | H1, H2, H4, H6 { font-weight: bold } 22 | H3, H5 { font-style: italic } 23 | 24 | H1 { font-size: xx-large } 25 | H2 { font-size: x-large } 26 | H3 { font-size: large } 27 | 28 | B, STRONG { font-weight: bolder } /* relative to the parent */ 29 | I, CITE, EM, VAR, ADDRESS, BLOCKQUOTE { font-style: italic } 30 | PRE, TT, CODE, KBD, SAMP { font-family: monospace } 31 | 32 | PRE { white-space: pre } 33 | 34 | ADDRESS { margin-left: 3em } 35 | BLOCKQUOTE { margin-left: 3em; margin-right: 3em } 36 | 37 | UL, DIR { list-style: disc } 38 | OL { list-style: decimal } 39 | MENU { margin: 0 } /* tight formatting */ 40 | LI { margin-left: 3em } 41 | 42 | DT { margin-bottom: 0 } 43 | DD { margin-top: 0; margin-left: 3em } 44 | 45 | HR { border-top: solid } /* 'border-bottom' could also have been used */ 46 | 47 | A:link { color: blue } /* unvisited link */ 48 | A:visited { color: red } /* visited links */ 49 | A:active { color: lime } /* active links */ 50 | 51 | /* setting the anchor border around IMG elements 52 | requires contextual selectors */ 53 | 54 | A:link IMG { border: 2px solid blue } 55 | A:visited IMG { border: 2px solid red } 56 | A:active IMG { border: 2px solid lime } 57 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/html40.css: -------------------------------------------------------------------------------- 1 | @charset "US-ASCII"; 2 | 3 | ADDRESS, 4 | BLOCKQUOTE, 5 | BODY, DD, DIV, 6 | DL, DT, 7 | FIELDSET, FORM, 8 | FRAME, FRAMESET, 9 | H1, H2, H3, H4, 10 | H5, H6, IFRAME, 11 | NOFRAMES, 12 | OBJECT, OL, P, 13 | UL, APPLET, 14 | CENTER, DIR, 15 | HR, MENU, PRE { display: block } 16 | LI { display: list-item } 17 | HEAD { display: none } 18 | TABLE { display: table } 19 | TR { display: table-row } 20 | THEAD { display: table-header-group } 21 | TBODY { display: table-row-group } 22 | TFOOT { display: table-footer-group } 23 | COL { display: table-column } 24 | COLGROUP { display: table-column-group } 25 | TD, TH { display: table-cell } 26 | CAPTION { display: table-caption } 27 | TH { font-weight: bolder; text-align: center } 28 | CAPTION { text-align: center } 29 | BODY { padding: 8px; line-height: 1.33 } 30 | H1 { font-size: 2em; margin: .67em 0 } 31 | H2 { font-size: 1.5em; margin: .83em 0 } 32 | H3 { font-size: 1.17em; margin: 1em 0 } 33 | H4, P, 34 | BLOCKQUOTE, UL, 35 | FIELDSET, FORM, 36 | OL, DL, DIR, 37 | MENU { margin: 1.33em 0 } 38 | H5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0 } 39 | H6 { font-size: .67em; margin: 2.33em 0 } 40 | H1, H2, H3, H4, 41 | H5, H6, B, 42 | STRONG { font-weight: bolder } 43 | BLOCKQUOTE { margin-left: 40px; margin-right: 40px } 44 | I, CITE, EM, 45 | VAR, ADDRESS { font-style: italic } 46 | PRE, TT, CODE, 47 | KBD, SAMP { font-family: monospace } 48 | PRE { white-space: pre } 49 | BIG { font-size: 1.17em } 50 | SMALL, SUB, SUP { font-size: .83em } 51 | SUB { vertical-align: sub } 52 | SUP { vertical-align: super } 53 | S, STRIKE, DEL { text-decoration: line-through } 54 | HR { border: 1px inset } 55 | OL, UL, DIR, 56 | MENU, DD { margin-left: 40px } 57 | OL { list-style-type: decimal } 58 | OL UL, UL OL, 59 | UL UL, OL OL { margin-top: 0; margin-bottom: 0 } 60 | U, INS { text-decoration: underline } 61 | CENTER { text-align: center } 62 | BR:before { content: "\A" } 63 | 64 | /* An example of style for HTML 4.0's ABBR/ACRONYM elements */ 65 | 66 | ABBR, ACRONYM { font-variant: small-caps; letter-spacing: 0.1em } 67 | A[href] { text-decoration: underline } 68 | :focus { outline: thin dotted invert } 69 | 70 | 71 | /* Begin bidirectionality settings (do not change) */ 72 | 73 | BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override } 74 | BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override } 75 | 76 | *[DIR="ltr"] { direction: ltr; unicode-bidi: embed } 77 | *[DIR="rtl"] { direction: rtl; unicode-bidi: embed } 78 | 79 | /* Elements that are block-level in HTML4 */ 80 | ADDRESS, BLOCKQUOTE, BODY, DD, DIV, DL, DT, FIELDSET, 81 | FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, IFRAME, 82 | NOSCRIPT, NOFRAMES, OBJECT, OL, P, UL, APPLET, CENTER, 83 | DIR, HR, MENU, PRE, LI, TABLE, TR, THEAD, TBODY, TFOOT, 84 | COL, COLGROUP, TD, TH, CAPTION 85 | { unicode-bidi: embed } 86 | /* End bidi settings */ 87 | 88 | 89 | @media print { 90 | @page { margin: 10% } 91 | H1, H2, H3, 92 | H4, H5, H6 { page-break-after: avoid; page-break-inside: avoid } 93 | BLOCKQUOTE, 94 | PRE { page-break-inside: avoid } 95 | UL, OL, DL { page-break-before: avoid } 96 | } 97 | 98 | @media speech { 99 | H1, H2, H3, 100 | H4, H5, H6 { voice-family: paul, male; stress: 20; richness: 90 } 101 | H1 { pitch: x-low; pitch-range: 90 } 102 | H2 { pitch: x-low; pitch-range: 80 } 103 | H3 { pitch: low; pitch-range: 70 } 104 | H4 { pitch: medium; pitch-range: 60 } 105 | H5 { pitch: medium; pitch-range: 50 } 106 | H6 { pitch: medium; pitch-range: 40 } 107 | LI, DT, DD { pitch: medium; richness: 60 } 108 | DT { stress: 80 } 109 | PRE, CODE, TT { pitch: medium; pitch-range: 0; stress: 0; richness: 80 } 110 | EM { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } 111 | STRONG { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } 112 | DFN { pitch: high; pitch-range: 60; stress: 60 } 113 | S, STRIKE { richness: 0 } 114 | I { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } 115 | B { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } 116 | U { richness: 0 } 117 | A:link { voice-family: harry, male } 118 | A:visited { voice-family: betty, female } 119 | A:active { voice-family: betty, female; pitch-range: 80; pitch: x-high } 120 | } 121 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/cssutils/tests/sheets/images/example.gif -------------------------------------------------------------------------------- /cssutils/tests/sheets/import.css: -------------------------------------------------------------------------------- 1 | @import "import/import2.css"; 2 | .import { 3 | /* ./import.css */ 4 | background-image: url(images/example.gif) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/import/images2/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/cssutils/tests/sheets/import/images2/example2.gif -------------------------------------------------------------------------------- /cssutils/tests/sheets/import/import-impossible.css: -------------------------------------------------------------------------------- 1 | @namespace "y"; 2 | @media tv {} 3 | .import4 { 4 | background: url(images2/example2.gif); 5 | } 6 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/import/import2.css: -------------------------------------------------------------------------------- 1 | @import "../import3.css"; 2 | @import "import-impossible.css" print; 3 | .import2 { 4 | /* sheets/import2.css */ 5 | background: url(http://example.com/images/example.gif); 6 | background: url(//example.com/images/example.gif); 7 | background: url(/images/example.gif); 8 | background: url(images2/example.gif); 9 | background: url(./images2/example.gif); 10 | background: url(../images/example.gif); 11 | background: url(./../images/example.gif); 12 | } -------------------------------------------------------------------------------- /cssutils/tests/sheets/import3.css: -------------------------------------------------------------------------------- 1 | /* import3 */ 2 | .import3 { 3 | /* from ./import/../import3.css */ 4 | background: url(images/example3.gif); 5 | background: url(./images/example3.gif); 6 | background: url(import/images2/example2.gif); 7 | background: url(./import/images2/example2.gif); 8 | background: url(import/images2/../../images/example3.gif); 9 | } -------------------------------------------------------------------------------- /cssutils/tests/sheets/multiple-values.css: -------------------------------------------------------------------------------- 1 | foo { 2 | a: 1, 2 3, bar 4 3 | } 4 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/page_test.css: -------------------------------------------------------------------------------- 1 | @page pageStyle { size: 21.0cm 29.7cm; } 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/simple.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("fineprint.css") print; 3 | @import url(bogus.css); 4 | @import url( bogus.css ); 5 | 6 | @media screen { 7 | 8 | @three-dee { 9 | @background-lighting { 10 | azimuth: 30deg; 11 | elevation: 190deg; 12 | } 13 | H1 { color: red } 14 | } 15 | 16 | h1 { foo: bar } 17 | h2 { thing: 1; whatsit: 2px } 18 | h3 { foo: 2 ! important } 19 | 20 | } 21 | 22 | h9 { } 23 | 24 | b1 & b2 { } 25 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/single-color.css: -------------------------------------------------------------------------------- 1 | @media aural { 2 | BLOCKQUOTE:after { content: url("beautiful-music.wav") } 3 | } 4 | a { 5 | content: url("other.wav") 6 | } 7 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/t-HACKS.css: -------------------------------------------------------------------------------- 1 | /* http://centricle.com/ref/css/filters/?highlight_columns=true */ 2 | @import 'styles1.css'; 3 | @import "styles2.css"; 4 | @import url(styles3.css); 5 | @import url('styles4.css'); 6 | @import url("styles5.css"); 7 | @import "null?\"\{"; 8 | @import "styles6.css"; 9 | 10 | a { 11 | color: red; 12 | voice-family:"\"}\""; 13 | voice-family:inherit; 14 | color: green; 15 | } 16 | b { color: red; 17 | c\olor:green; } 18 | c { color: red; 19 | /*/*/color:green;/* */ } 20 | /* NS4 only, should not work: */ 21 | d { color: green; 22 | /*/*//*/color:red;/* */ } 23 | 24 | e1{content:"\"/*"} 25 | e2{color:green} 26 | /* THIS SHOULD WORK??? */ 27 | /* \*/ 28 | div{color:green} 29 | /* */ 30 | 31 | div#test { } 32 | head:first-child+body div { } 33 | 34 | 35 | @media all{/* rules */} 36 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/test-unicode.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/cssutils/tests/sheets/test-unicode.css -------------------------------------------------------------------------------- /cssutils/tests/sheets/test.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/cssutils/tests/sheets/test.css -------------------------------------------------------------------------------- /cssutils/tests/sheets/u_simple.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/cssutils/tests/sheets/u_simple.css -------------------------------------------------------------------------------- /cssutils/tests/sheets/v_simple.css: -------------------------------------------------------------------------------- 1 | h1 { foo: alpha; bar:beta } 2 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/var/start.css: -------------------------------------------------------------------------------- 1 | @import "vars.css"; 2 | @import "vars2.css"; 3 | @import "use.css"; 4 | 5 | @variables { 6 | TEST: 1px; 7 | T2: 'T2' 8 | } 9 | 10 | a { 11 | left: var(T2); 12 | } 13 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/var/use.css: -------------------------------------------------------------------------------- 1 | a { 2 | content: var(TEST) 3 | } 4 | a { 5 | content: var(TEST) 6 | } 7 | a { 8 | content: var(TEST) 9 | } 10 | a { 11 | content: var(TEST) 12 | } 13 | a { 14 | content: var(TEST) 15 | } 16 | a { 17 | content: var(TEST) 18 | } 19 | a { 20 | content: var(TEST) 21 | } 22 | a { 23 | content: var(TEST) 24 | } 25 | a { 26 | content: var(TEST) 27 | } 28 | a { 29 | content: var(TEST) 30 | } 31 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/var/vars.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | TEST: 1px; 3 | T2: 'T2' 4 | } 5 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/var/vars2.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | TEST: 'VARS2' 3 | } 4 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/vars.css: -------------------------------------------------------------------------------- 1 | @import "varsimport.css"; 2 | 3 | @variables { 4 | c2: "c2 own file (OVERWRITTEN)"; 5 | c3: "c1 own file" 6 | } 7 | 8 | a1 { 9 | content: var(c1); 10 | } 11 | a2 { 12 | content: var(c2); 13 | } 14 | a3 { 15 | content: var(c3); 16 | } 17 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/varsimport.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | c1: "c1 imported"; 3 | c2: "c2 imported (SHOULD BE OVERWRITTEN)"; 4 | } 5 | 6 | .import1 { 7 | content: var(c1); 8 | } 9 | .import2 { 10 | content: var(c2); 11 | } 12 | .import3 { 13 | /* not defined here! */ 14 | content: var(c3); 15 | } 16 | -------------------------------------------------------------------------------- /cssutils/tests/sheets/yuck.css: -------------------------------------------------------------------------------- 1 | E[class~="hipster"][thing~="bob"]#myid { att1: hi; foo: bar } 2 | E:lang(c)#unique 3 | E.hipster#myid { att1: hi; foo: bar } 4 | -------------------------------------------------------------------------------- /cssutils/tests/test_csscharsetrule.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.CSSCharsetRule""" 2 | 3 | import xml.dom 4 | 5 | import pytest 6 | 7 | import cssutils.css 8 | 9 | from . import test_cssrule 10 | 11 | 12 | class TestCSSCharsetRule(test_cssrule.TestCSSRule): 13 | def _setup_rule(self): 14 | self.r = cssutils.css.CSSCharsetRule() 15 | self.rRO = cssutils.css.CSSCharsetRule(readonly=True) 16 | self.r_type = cssutils.css.CSSCharsetRule.CHARSET_RULE 17 | self.r_typeString = 'CHARSET_RULE' 18 | 19 | def test_init(self): 20 | "CSSCharsetRule.__init__()" 21 | super().test_init() 22 | assert self.r.encoding is None 23 | assert '' == self.r.cssText 24 | 25 | with pytest.raises(xml.dom.InvalidModificationErr): 26 | self.r._setCssText('xxx') 27 | 28 | def test_InvalidModificationErr(self): 29 | "CSSCharsetRule InvalidModificationErr" 30 | self._test_InvalidModificationErr('@charset') 31 | 32 | def test_init_encoding(self): 33 | "CSSCharsetRule.__init__(encoding)" 34 | for enc in (None, 'UTF-8', 'utf-8', 'iso-8859-1', 'ascii'): 35 | r = cssutils.css.CSSCharsetRule(enc) 36 | if enc is None: 37 | assert r.encoding is None 38 | assert '' == r.cssText 39 | else: 40 | assert enc.lower() == r.encoding 41 | assert '@charset "%s";' % enc.lower() == r.cssText 42 | 43 | for enc in (' ascii ', ' ascii', 'ascii '): 44 | with pytest.raises(xml.dom.SyntaxErr, match="Syntax Error"): 45 | cssutils.css.CSSCharsetRule(enc) 46 | 47 | for enc in ('unknown',): 48 | with pytest.raises(xml.dom.SyntaxErr, match=r"Unknown \(Python\) encoding"): 49 | cssutils.css.CSSCharsetRule(enc) 50 | 51 | def test_encoding(self): 52 | "CSSCharsetRule.encoding" 53 | for enc in ('UTF-8', 'utf-8', 'iso-8859-1', 'ascii'): 54 | self.r.encoding = enc 55 | assert enc.lower() == self.r.encoding 56 | assert '@charset "%s";' % enc.lower() == self.r.cssText 57 | 58 | for enc in (None, ' ascii ', ' ascii', 'ascii '): 59 | with pytest.raises(xml.dom.SyntaxErr, match="Syntax Error"): 60 | self.r.encoding = enc 61 | 62 | for enc in ('unknown',): 63 | with pytest.raises(xml.dom.SyntaxErr, match=r"Unknown \(Python\) encoding"): 64 | self.r.encoding = enc 65 | 66 | def test_cssText(self): 67 | """CSSCharsetRule.cssText 68 | 69 | setting cssText is ok to use @CHARSET or other but a file 70 | using parse MUST use ``@charset "ENCODING";`` 71 | """ 72 | tests = { 73 | '@charset "utf-8";': None, 74 | "@charset 'utf-8';": '@charset "utf-8";', 75 | } 76 | self.do_equal_r(tests) 77 | self.do_equal_p(tests) # also parse 78 | 79 | tests = { 80 | # token is "@charset " with space! 81 | '@charset;"': xml.dom.InvalidModificationErr, 82 | '@CHARSET "UTF-8";': xml.dom.InvalidModificationErr, 83 | '@charset "";': xml.dom.SyntaxErr, 84 | '''@charset /*1*/"utf-8"/*2*/;''': xml.dom.SyntaxErr, 85 | '''@charset /*1*/"utf-8";''': xml.dom.SyntaxErr, 86 | '''@charset "utf-8"/*2*/;''': xml.dom.SyntaxErr, 87 | '@charset { utf-8 }': xml.dom.SyntaxErr, 88 | '@charset "utf-8"': xml.dom.SyntaxErr, 89 | '@charset a;': xml.dom.SyntaxErr, 90 | '@charset /**/;': xml.dom.SyntaxErr, 91 | # trailing content 92 | '@charset "utf-8";s': xml.dom.SyntaxErr, 93 | '@charset "utf-8";/**/': xml.dom.SyntaxErr, 94 | '@charset "utf-8"; ': xml.dom.SyntaxErr, 95 | # comments do not work in this rule! 96 | '@charset "utf-8"/*1*//*2*/;': xml.dom.SyntaxErr, 97 | } 98 | self.do_raise_r(tests) 99 | 100 | def test_repr(self): 101 | "CSSCharsetRule.__repr__()" 102 | self.r.encoding = 'utf-8' 103 | assert 'utf-8' in repr(self.r) 104 | 105 | def test_reprANDstr(self): 106 | "CSSCharsetRule.__repr__(), .__str__()" 107 | encoding = 'utf-8' 108 | 109 | s = cssutils.css.CSSCharsetRule(encoding=encoding) 110 | 111 | assert encoding in str(s) 112 | 113 | s2 = eval(repr(s)) 114 | assert isinstance(s2, s.__class__) 115 | assert encoding == s2.encoding 116 | -------------------------------------------------------------------------------- /cssutils/tests/test_csscomment.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.CSSComment""" 2 | 3 | import xml 4 | 5 | import cssutils.css 6 | 7 | from . import test_cssrule 8 | 9 | 10 | class TestCSSComment(test_cssrule.TestCSSRule): 11 | def _setup_rule(self): 12 | self.r = cssutils.css.CSSComment() 13 | self.rRO = cssutils.css.CSSComment(readonly=True) 14 | self.r_type = cssutils.css.CSSComment.COMMENT 15 | self.r_typeString = 'COMMENT' 16 | 17 | def test_csstext(self): 18 | "CSSComment.cssText" 19 | tests = { 20 | '/*öäü߀ÖÄÜ*/': '/*\xf6\xe4\xfc\xdf\u20ac\xd6\xc4\xdc*/', 21 | '/*x*/': None, 22 | '/* x */': None, 23 | '/*\t12\n*/': None, 24 | '/* /* */': None, 25 | '/* \\*/': None, 26 | '/*"*/': None, 27 | '''/*" 28 | */''': None, 29 | '/** / ** //*/': None, 30 | } 31 | self.do_equal_r(tests) # set cssText 32 | tests.update({ 33 | '/*x': '/*x*/', 34 | '\n /*': '/**/', 35 | }) 36 | self.do_equal_p(tests) # parse 37 | 38 | tests = { 39 | '/* */ ': xml.dom.InvalidModificationErr, 40 | '/* *//**/': xml.dom.InvalidModificationErr, 41 | '/* */1': xml.dom.InvalidModificationErr, 42 | '/* */ */': xml.dom.InvalidModificationErr, 43 | ' */ /* ': xml.dom.InvalidModificationErr, 44 | '*/': xml.dom.InvalidModificationErr, 45 | '@x /* x */': xml.dom.InvalidModificationErr, 46 | } 47 | self.do_raise_r(tests) # set cssText 48 | # no raising of error possible? 49 | # self.do_raise_p(tests) # parse 50 | 51 | def test_InvalidModificationErr(self): 52 | "CSSComment.cssText InvalidModificationErr" 53 | self._test_InvalidModificationErr('/* comment */') 54 | 55 | def test_reprANDstr(self): 56 | "CSSComment.__repr__(), .__str__()" 57 | text = '/* test */' 58 | 59 | s = cssutils.css.CSSComment(cssText=text) 60 | 61 | s2 = eval(repr(s)) 62 | assert isinstance(s2, s.__class__) 63 | assert text == s2.cssText 64 | -------------------------------------------------------------------------------- /cssutils/tests/test_cssproperties.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.cssproperties.""" 2 | 3 | import pytest 4 | 5 | import cssutils.css 6 | import cssutils.profiles 7 | 8 | 9 | class TestCSSProperties: 10 | def test_toDOMname(self): 11 | "cssproperties _toDOMname(CSSname)" 12 | _toDOMname = cssutils.css.cssproperties._toDOMname 13 | 14 | assert 'color' == _toDOMname('color') 15 | assert 'fontStyle' == _toDOMname('font-style') 16 | assert 'MozOpacity' == _toDOMname('-moz-opacity') 17 | assert 'UNKNOWN' == _toDOMname('UNKNOWN') 18 | assert 'AnUNKNOWN' == _toDOMname('-anUNKNOWN') 19 | 20 | def test_toCSSname(self): 21 | "cssproperties _toCSSname(DOMname)" 22 | _toCSSname = cssutils.css.cssproperties._toCSSname 23 | 24 | assert 'color' == _toCSSname('color') 25 | assert 'font-style' == _toCSSname('fontStyle') 26 | assert '-moz-opacity' == _toCSSname('MozOpacity') 27 | assert 'UNKNOWN' == _toCSSname('UNKNOWN') 28 | assert '-anUNKNOWN' == _toCSSname('AnUNKNOWN') 29 | 30 | def test_CSS2Properties(self): 31 | "CSS2Properties" 32 | CSS2Properties = cssutils.css.cssproperties.CSS2Properties 33 | assert isinstance(property(), type(CSS2Properties.color)) 34 | assert sum(len(x) for x in list(cssutils.profiles.properties.values())) == len( 35 | CSS2Properties._properties 36 | ) 37 | 38 | c2 = CSS2Properties() 39 | # CSS2Properties has simplified implementation return always None 40 | assert c2.color is None 41 | assert c2.__setattr__('color', 1) is None 42 | assert c2.__delattr__('color') is None 43 | # only defined properties 44 | with pytest.raises(AttributeError): 45 | c2.__getattribute__('UNKNOWN') 46 | -------------------------------------------------------------------------------- /cssutils/tests/test_cssrulelist.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.CSSRuleList""" 2 | 3 | import pytest 4 | 5 | import cssutils 6 | 7 | 8 | class TestCSSRuleList: 9 | def test_init(self): 10 | "CSSRuleList.__init__()" 11 | r = cssutils.css.CSSRuleList() 12 | assert 0 == r.length 13 | assert r.item(2) is None 14 | 15 | # subclasses list but all setting options like append, extend etc 16 | # need to be added to an instance of this class by a using class! 17 | with pytest.raises(NotImplementedError): 18 | r.append(1) 19 | 20 | def test_rulesOfType(self): 21 | "CSSRuleList.rulesOfType()" 22 | s = cssutils.parseString( 23 | ''' 24 | /*c*/ 25 | @namespace "a"; 26 | a { color: red} 27 | b { left: 0 }''' 28 | ) 29 | 30 | c = list(s.cssRules.rulesOfType(cssutils.css.CSSRule.COMMENT)) 31 | assert 1 == len(c) 32 | assert '/*c*/' == c[0].cssText 33 | 34 | r = list(s.cssRules.rulesOfType(cssutils.css.CSSRule.STYLE_RULE)) 35 | assert 2 == len(r) 36 | assert 'b {\n left: 0\n }' == r[1].cssText 37 | -------------------------------------------------------------------------------- /cssutils/tests/test_cssutilsimport.py: -------------------------------------------------------------------------------- 1 | """Testcase for cssutils imports""" 2 | 3 | import cssutils 4 | 5 | 6 | class TestCSSutilsImport: 7 | def test_import_all(self): 8 | "from cssutils import *" 9 | namespace = {} 10 | exec('from cssutils import *', namespace) 11 | del namespace['__builtins__'] 12 | exp = { 13 | 'CSSParser': cssutils.CSSParser, 14 | 'CSSSerializer': cssutils.CSSSerializer, 15 | 'css': cssutils.css, 16 | 'stylesheets': cssutils.stylesheets, 17 | } 18 | assert namespace == exp 19 | -------------------------------------------------------------------------------- /cssutils/tests/test_domimplementation.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.DOMImplementation""" 2 | 3 | import warnings 4 | import xml.dom 5 | import xml.dom.minidom 6 | 7 | import pytest 8 | 9 | import cssutils 10 | 11 | 12 | @pytest.fixture 13 | def domimpl(): 14 | return cssutils.DOMImplementationCSS() 15 | 16 | 17 | class TestDOMImplementation: 18 | def test_createCSSStyleSheet(self, domimpl): 19 | "DOMImplementationCSS.createCSSStyleSheet()" 20 | title, media = 'Test Title', cssutils.stylesheets.MediaList('all') 21 | with warnings.catch_warnings(): 22 | warnings.simplefilter('ignore') 23 | sheet = domimpl.createCSSStyleSheet(title, media) 24 | assert isinstance(sheet, cssutils.css.CSSStyleSheet) 25 | assert title == sheet.title 26 | assert media == sheet.media 27 | 28 | def test_createDocument(self, domimpl): 29 | "DOMImplementationCSS.createDocument()" 30 | doc = domimpl.createDocument(None, None, None) 31 | assert isinstance(doc, xml.dom.minidom.Document) 32 | 33 | def test_createDocumentType(self, domimpl): 34 | "DOMImplementationCSS.createDocumentType()" 35 | doctype = domimpl.createDocumentType('foo', 'bar', 'raboof') 36 | assert isinstance(doctype, xml.dom.minidom.DocumentType) 37 | 38 | def test_hasFeature(self, domimpl): 39 | "DOMImplementationCSS.hasFeature()" 40 | tests = [ 41 | ('css', '1.0'), 42 | ('css', '2.0'), 43 | ('stylesheets', '1.0'), 44 | ('stylesheets', '2.0'), 45 | ] 46 | for name, version in tests: 47 | assert domimpl.hasFeature(name, version) 48 | -------------------------------------------------------------------------------- /cssutils/tests/test_helper.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.helper""" 2 | 3 | from cssutils.helper import normalize, string, stringvalue, uri, urivalue 4 | 5 | 6 | class TestHelper: 7 | def test_normalize(self): 8 | "helper._normalize()" 9 | tests = { 10 | 'abcdefg ABCDEFG äöü߀ AÖÜ': r'abcdefg abcdefg äöü߀ aöü', 11 | r'\ga\Ga\\\ ': r'gaga\ ', 12 | r'0123456789': r'0123456789', 13 | r'"\x"': r'"x"', 14 | # unicode escape seqs should have been done by 15 | # the tokenizer... 16 | } 17 | for test, exp in list(tests.items()): 18 | assert normalize(test) == exp 19 | # static too 20 | assert normalize(test) == exp 21 | 22 | def test_string(self): 23 | "helper.string()" 24 | assert '"x"' == string('x') 25 | assert '"1 2ä€"' == string('1 2ä€') 26 | assert r'''"'"''' == string("'") 27 | assert r'"\""' == string('"') 28 | # \n = 0xa, \r = 0xd, \f = 0xc 29 | assert r'"\a "' == string( 30 | ''' 31 | ''' 32 | ) 33 | assert r'"\c "' == string('\f') 34 | assert r'"\d "' == string('\r') 35 | assert r'"\d \a "' == string('\r\n') 36 | 37 | def test_stringvalue(self): 38 | "helper.stringvalue()" 39 | assert 'x' == stringvalue('"x"') 40 | assert '"' == stringvalue('"\\""') 41 | assert r'x' == stringvalue(r"\x ") 42 | 43 | # escapes should have been done by tokenizer 44 | # so this shoule not happen at all: 45 | assert r'a' == stringvalue(r"\a ") 46 | 47 | def test_uri(self): 48 | "helper.uri()" 49 | assert 'url(x)' == uri('x') 50 | assert 'url("(")' == uri('(') 51 | assert 'url(")")' == uri(')') 52 | assert 'url(" ")' == uri(' ') 53 | assert 'url(";")' == uri(';') 54 | assert 'url(",")' == uri(',') 55 | assert 'url("x)x")' == uri('x)x') 56 | 57 | def test_urivalue(self): 58 | "helper.urivalue()" 59 | assert 'x' == urivalue('url(x)') 60 | assert 'x' == urivalue('url("x")') 61 | assert ')' == urivalue('url(")")') 62 | -------------------------------------------------------------------------------- /cssutils/tests/test_marginrule.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.CSSPageRule""" 2 | 3 | import xml.dom 4 | 5 | import pytest 6 | 7 | import cssutils 8 | 9 | from . import test_cssrule 10 | 11 | 12 | class TestMarginRule(test_cssrule.TestCSSRule): 13 | def _setup_rule(self): 14 | self.r = cssutils.css.MarginRule() 15 | self.rRO = cssutils.css.MarginRule(readonly=True) 16 | self.r_type = cssutils.css.MarginRule.MARGIN_RULE 17 | self.r_typeString = 'MARGIN_RULE' 18 | 19 | def test_init(self): 20 | "MarginRule.__init__()" 21 | 22 | r = cssutils.css.MarginRule() 23 | assert r.margin is None 24 | assert r.atkeyword is None 25 | assert r._keyword is None 26 | assert r.style.cssText == '' 27 | assert r.cssText == '' 28 | 29 | r = cssutils.css.MarginRule(margin='@TOP-left') 30 | assert r.margin == '@top-left' 31 | assert r.atkeyword == '@top-left' 32 | assert r._keyword == '@TOP-left' 33 | assert r.style.cssText == '' 34 | assert r.cssText == '' 35 | 36 | with pytest.raises(xml.dom.InvalidModificationErr): 37 | cssutils.css.MarginRule('@x') 38 | 39 | def test_InvalidModificationErr(self): 40 | "MarginRule.cssText InvalidModificationErr" 41 | # TODO: !!! 42 | 43 | # self._test_InvalidModificationErr(u'@top-left') 44 | # tests = { 45 | # u'@x {}': xml.dom.InvalidModificationErr, 46 | # } 47 | # self.do_raise_r(tests) 48 | 49 | def test_incomplete(self): 50 | "MarginRule (incomplete)" 51 | # must be inside @page as not valid outside 52 | tests = { 53 | '@page { @top-left { ': '', # no } and no content 54 | '@page { @top-left { /*1*/ ': '', # no } and no content 55 | '@page { @top-left { color: red': '@page {\n @top-left {\n color: red\n }\n }', 56 | } 57 | self.do_equal_p(tests) # parse 58 | 59 | def test_cssText(self): 60 | tests = { 61 | '@top-left {}': '', 62 | '@top-left { /**/ }': '', 63 | '@top-left { color: red }': '@top-left {\n color: red\n }', 64 | '@top-left{color:red;}': '@top-left {\n color: red\n }', 65 | '@top-left{color:red}': '@top-left {\n color: red\n }', 66 | '@top-left { color: red; left: 0 }': '@top-left {\n color: red;\n left: 0\n }', 67 | } 68 | self.do_equal_r(tests) 69 | 70 | # TODO 71 | tests.update({ 72 | # false selector 73 | # u'@top-left { color:': xml.dom.SyntaxErr, # no } 74 | # u'@top-left { color': xml.dom.SyntaxErr, # no } 75 | # u'@top-left {': xml.dom.SyntaxErr, # no } 76 | # u'@top-left': xml.dom.SyntaxErr, # no } 77 | # u'@top-left;': xml.dom.SyntaxErr, # no } 78 | }) 79 | 80 | # self.do_raise_r(tests) # set cssText 81 | 82 | def test_reprANDstr(self): 83 | "MarginRule.__repr__(), .__str__()" 84 | margin = '@top-left' 85 | 86 | s = cssutils.css.MarginRule(margin=margin, style='left: 0') 87 | 88 | assert margin in str(s) 89 | 90 | s2 = eval(repr(s)) 91 | assert isinstance(s2, s.__class__) 92 | assert margin == s2.margin 93 | -------------------------------------------------------------------------------- /cssutils/tests/test_mediaquery.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.stylesheets.MediaQuery""" 2 | 3 | import xml.dom 4 | 5 | import pytest 6 | 7 | import cssutils.stylesheets 8 | 9 | from . import basetest 10 | 11 | 12 | class TestMediaQuery(basetest.BaseTestCase): 13 | def setup_method(self): 14 | self.r = cssutils.stylesheets.MediaQuery() 15 | 16 | def test_mediaText(self): 17 | "MediaQuery.mediaText" 18 | tests = { 19 | 'all': None, 20 | 'braille': None, 21 | 'embossed': None, 22 | 'handheld': None, 23 | 'print': None, 24 | 'projection': None, 25 | 'screen': None, 26 | 'speech': None, 27 | 'tty': None, 28 | 'tv': None, 29 | 'ALL': None, 30 | 'a\\ll': None, 31 | 'not tv': None, 32 | 'n\\ot t\\v': None, 33 | 'only tv': None, 34 | '\\only \\tv': None, 35 | 'PRINT': None, 36 | 'NOT PRINT': None, 37 | 'ONLY PRINT': None, 38 | 'tv and (color)': None, 39 | 'not tv and (color)': None, 40 | 'only tv and (color)': None, 41 | 'print and(color)': 'print and (color)', 42 | } 43 | self.do_equal_r(tests, att='mediaText') 44 | 45 | tests = { 46 | '': xml.dom.SyntaxErr, 47 | 'two values': xml.dom.SyntaxErr, 48 | 'or even three': xml.dom.SyntaxErr, 49 | 'aural': xml.dom.SyntaxErr, # a dimension 50 | '3d': xml.dom.SyntaxErr, # a dimension 51 | } 52 | self.do_raise_r(tests, att='_setMediaText') 53 | 54 | def test_mediaType(self): 55 | "MediaQuery.mediaType" 56 | mq = cssutils.stylesheets.MediaQuery() 57 | 58 | assert '' == mq.mediaText 59 | 60 | for mt in cssutils.stylesheets.MediaQuery.MEDIA_TYPES: 61 | mq.mediaType = mt 62 | assert mq.mediaType == mt 63 | mq.mediaType = mt.upper() 64 | assert mq.mediaType == mt.upper() 65 | 66 | mt = '3D-UNKOwn-MEDIAtype0123' 67 | # mq.mediaType = mt 68 | with pytest.raises(xml.dom.SyntaxErr): 69 | mq._setMediaType(mt) 70 | # self.assertRaises(xml.dom.InvalidCharacterErr, mq._setMediaType, mt) 71 | 72 | def test_comments(self): 73 | "MediaQuery.mediaText comments" 74 | tests = { 75 | 'all': None, 76 | 'print': None, 77 | 'not print': None, 78 | 'only print': None, 79 | 'print and (color)': None, 80 | 'print and (color) and (width)': None, 81 | 'print and (color: 2)': None, 82 | 'print and (min-width: 100px)': None, 83 | 'print and (min-width: 100px) and (color: red)': None, 84 | 'not print and (min-width: 100px)': None, 85 | 'only print and (min-width: 100px)': None, 86 | '/*1*/ tv /*2*/': None, 87 | '/*0*/ only /*1*/ tv /*2*/': None, 88 | '/*0* /not /*1*/ tv /*2*/': None, 89 | '/*x*/ only /*x*/ print /*x*/ and /*x*/ (/*x*/ ' 90 | 'min-width /*x*/: /*x*/ 100px /*x*/)': None, 91 | 'print and/*1*/(color)': 'print and /*1*/ (color)', 92 | } 93 | self.do_equal_r(tests, att='mediaText') 94 | 95 | def test_reprANDstr(self): 96 | "MediaQuery.__repr__(), .__str__()" 97 | mediaText = 'tv and (color)' 98 | s = cssutils.stylesheets.MediaQuery(mediaText=mediaText) 99 | assert mediaText in str(s) 100 | s2 = eval(repr(s)) 101 | assert mediaText == s2.mediaText 102 | assert isinstance(s2, s.__class__) 103 | -------------------------------------------------------------------------------- /cssutils/tests/test_scripts_csscombine.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.scripts.csscombine""" 2 | 3 | import cssutils 4 | from cssutils.script import csscombine 5 | 6 | from . import basetest 7 | 8 | 9 | class TestCSSCombine: 10 | C = '@namespace s2"uri";s2|sheet-1{top:1px}s2|sheet-2{top:2px}proxy{top:3px}' 11 | 12 | def test_combine(self): 13 | "scripts.csscombine()" 14 | 15 | # path, SHOULD be keyword argument! 16 | csspath = basetest.get_sheet_filename('csscombine-proxy.css') 17 | combined = csscombine(csspath) 18 | assert combined == self.C.encode() 19 | combined = csscombine(path=csspath, targetencoding='ascii') 20 | assert combined == ('@charset "ascii";' + self.C).encode() 21 | 22 | # url 23 | cssurl = cssutils.helper.path2url(csspath) 24 | combined = csscombine(url=cssurl) 25 | assert combined == self.C.encode() 26 | combined = csscombine(url=cssurl, targetencoding='ascii') 27 | assert combined == ('@charset "ascii";' + self.C).encode() 28 | 29 | # cssText 30 | # TODO: really need binary or can handle str too? 31 | f = open(csspath, mode="rb") 32 | cssText = f.read() 33 | f.close() 34 | combined = csscombine(cssText=cssText, href=cssurl) 35 | assert combined == self.C.encode() 36 | combined = csscombine(cssText=cssText, href=cssurl, targetencoding='ascii') 37 | assert combined == ('@charset "ascii";' + self.C).encode() 38 | 39 | def test_combine_resolveVariables(self): 40 | "scripts.csscombine(minify=..., resolveVariables=...)" 41 | # no actual imports but checking if minify and resolveVariables work 42 | cssText = ''' 43 | @variables { 44 | c: #0f0; 45 | } 46 | a { 47 | color: var(c); 48 | } 49 | ''' 50 | # default minify 51 | assert ( 52 | csscombine(cssText=cssText, resolveVariables=False) 53 | == b'@variables{c:#0f0}a{color:var(c)}' 54 | ) 55 | assert csscombine(cssText=cssText) == b'a{color:#0f0}' 56 | 57 | # no minify 58 | assert ( 59 | csscombine(cssText=cssText, minify=False, resolveVariables=False) 60 | == b'@variables {\n c: #0f0\n }\na {\n color: var(c)\n }' 61 | ) 62 | assert ( 63 | csscombine(cssText=cssText, minify=False) == b'a {\n color: #0f0\n }' 64 | ) 65 | -------------------------------------------------------------------------------- /cssutils/tests/test_selectorlist.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.selectorlist.SelectorList.""" 2 | 3 | import xml.dom 4 | 5 | import pytest 6 | 7 | import cssutils 8 | from cssutils.css.selectorlist import SelectorList 9 | 10 | from . import basetest 11 | 12 | 13 | class TestSelectorList(basetest.BaseTestCase): 14 | def setup_method(self): 15 | self.r = SelectorList() 16 | 17 | def test_init(self): 18 | "SelectorList.__init__() and .length" 19 | s = SelectorList() 20 | assert 0 == s.length 21 | 22 | s = SelectorList('a, b') 23 | assert 2 == s.length 24 | assert 'a, b' == s.selectorText 25 | 26 | s = SelectorList(selectorText='a') 27 | assert 1 == s.length 28 | assert 'a' == s.selectorText 29 | 30 | s = SelectorList(selectorText=('p|a', {'p': 'uri'})) # n-dict 31 | assert 1 == s.length 32 | assert 'p|a' == s.selectorText 33 | 34 | s = SelectorList(selectorText=('p|a', (('p', 'uri'),))) # n-tuples 35 | assert 1 == s.length 36 | assert 'p|a' == s.selectorText 37 | 38 | def test_parentRule(self): 39 | "Selector.parentRule" 40 | 41 | def check(style): 42 | assert style == style.selectorList.parentRule 43 | for sel in style.selectorList: 44 | assert style.selectorList == sel.parent 45 | 46 | style = cssutils.css.CSSStyleRule('a, b') 47 | check(style) 48 | 49 | # add new selector 50 | style.selectorList.append(cssutils.css.Selector('x')) 51 | check(style) 52 | 53 | # replace selectorList 54 | style.selectorList = cssutils.css.SelectorList('x') 55 | check(style) 56 | 57 | # replace selectorText 58 | style.selectorText = 'x, y' 59 | check(style) 60 | 61 | def test_appendSelector(self): 62 | "SelectorList.appendSelector() and .length" 63 | s = SelectorList() 64 | s.appendSelector('a') 65 | assert 1 == s.length 66 | 67 | with pytest.raises(xml.dom.InvalidModificationErr): 68 | s.appendSelector('b,') 69 | assert 1 == s.length 70 | 71 | assert 'a' == s.selectorText 72 | 73 | s.append('b') 74 | assert 2 == s.length 75 | assert 'a, b' == s.selectorText 76 | 77 | s.append('a') 78 | assert 2 == s.length 79 | assert 'b, a' == s.selectorText 80 | 81 | # __setitem__ 82 | with pytest.raises(IndexError): 83 | s.__setitem__(4, 'x') 84 | s[1] = 'c' 85 | assert 2 == s.length 86 | assert 'b, c' == s.selectorText 87 | # TODO: remove duplicates? 88 | # s[0] = 'c' 89 | # self.assertEqual(1, s.length) 90 | # self.assertEqual(u'c', s.selectorText) 91 | 92 | s = SelectorList() 93 | s.appendSelector(('p|a', {'p': 'uri', 'x': 'xxx'})) 94 | assert 'p|a' == s.selectorText 95 | # x gets lost as not used 96 | with pytest.raises(xml.dom.NamespaceErr): 97 | s.append('x|a') 98 | # not set at all 99 | with pytest.raises(xml.dom.NamespaceErr): 100 | s.append('y|a') 101 | # but p is retained 102 | s.append('p|b') 103 | assert 'p|a, p|b' == s.selectorText 104 | 105 | def test_selectorText(self): 106 | "SelectorList.selectorText" 107 | s = SelectorList() 108 | s.selectorText = 'a, b' 109 | assert 'a, b' == s.selectorText 110 | with pytest.raises(xml.dom.SyntaxErr): 111 | s._setSelectorText(',') 112 | # not changed as invalid! 113 | assert 'a, b' == s.selectorText 114 | 115 | tests = { 116 | '*': None, 117 | '/*1*/*': None, 118 | '/*1*/*, a': None, 119 | 'a, b': None, 120 | 'a ,b': 'a, b', 121 | 'a , b': 'a, b', 122 | 'a, b, c': 'a, b, c', 123 | '#a, x#a, .b, x.b': '#a, x#a, .b, x.b', 124 | ('[p|a], p|*', (('p', 'uri'),)): '[p|a], p|*', 125 | } 126 | # do not parse as not complete 127 | self.do_equal_r(tests, att='selectorText') 128 | 129 | tests = { 130 | 'x|*': xml.dom.NamespaceErr, 131 | '': xml.dom.SyntaxErr, 132 | ' ': xml.dom.SyntaxErr, 133 | ',': xml.dom.SyntaxErr, 134 | 'a,': xml.dom.SyntaxErr, 135 | ',a': xml.dom.SyntaxErr, 136 | '/* 1 */,a': xml.dom.SyntaxErr, 137 | } 138 | # only set as not complete 139 | self.do_raise_r(tests, att='_setSelectorText') 140 | 141 | def test_reprANDstr(self): 142 | "SelectorList.__repr__(), .__str__()" 143 | sel = ('a, p|b', {'p': 'uri'}) 144 | 145 | s = cssutils.css.SelectorList(selectorText=sel) 146 | 147 | assert sel[0] in str(s) 148 | 149 | s2 = eval(repr(s)) 150 | assert isinstance(s2, s.__class__) 151 | assert sel[0] == s2.selectorText 152 | -------------------------------------------------------------------------------- /cssutils/tests/test_settings.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.settings""" 2 | 3 | import cssutils 4 | import cssutils.settings 5 | 6 | 7 | class TestSettings: 8 | def test_set(self): 9 | "settings.set()" 10 | cssutils.ser.prefs.useMinified() 11 | text = ( 12 | 'a {filter: progid:DXImageTransform.Microsoft.BasicImage( rotation = 90 )}' 13 | ) 14 | 15 | assert cssutils.parseString(text).cssText == b'' 16 | 17 | cssutils.settings.set('DXImageTransform.Microsoft', True) 18 | assert ( 19 | cssutils.parseString(text).cssText 20 | == b'a{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=90)}' 21 | ) 22 | -------------------------------------------------------------------------------- /cssutils/tests/test_stylesheet.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.stylesheets.StyleSheet""" 2 | 3 | import cssutils 4 | 5 | 6 | class TestStyleSheet: 7 | def test_init(self): 8 | "StyleSheet.__init__()" 9 | s = cssutils.stylesheets.StyleSheet() 10 | 11 | assert s.type == 'text/css' 12 | assert s.href is None 13 | assert s.media is None 14 | assert s.title == '' 15 | assert s.ownerNode is None 16 | assert s.parentStyleSheet is None 17 | assert s.alternate is False 18 | assert s.disabled is False 19 | 20 | s = cssutils.stylesheets.StyleSheet( 21 | type='unknown', 22 | href='test.css', 23 | media=None, 24 | title='title', 25 | ownerNode=None, 26 | parentStyleSheet=None, 27 | alternate=True, 28 | disabled=True, 29 | ) 30 | 31 | assert s.type == 'unknown' 32 | assert s.href == 'test.css' 33 | assert s.media is None 34 | assert s.title == 'title' 35 | assert s.ownerNode is None 36 | assert s.parentStyleSheet is None 37 | assert s.alternate 38 | assert s.disabled 39 | -------------------------------------------------------------------------------- /cssutils/tests/test_x.py: -------------------------------------------------------------------------------- 1 | """Testcases for cssutils.css.CSSValue and CSSPrimitiveValue.""" 2 | 3 | import xml.dom 4 | 5 | import pytest 6 | 7 | import cssutils 8 | 9 | 10 | class TestX: 11 | @pytest.mark.xfail(reason="not implemented") 12 | def test_priority(self): 13 | "Property.priority" 14 | s = cssutils.parseString('a { color: red }') 15 | assert s.cssText == b'a {\n color: red\n }' 16 | 17 | assert '' == s.cssRules[0].style.getPropertyPriority('color') 18 | 19 | s = cssutils.parseString('a { color: red !important }') 20 | assert 'a {\n color: red !important\n }' == s.cssText 21 | assert 'important' == s.cssRules[0].style.getPropertyPriority('color') 22 | 23 | cssutils.log.raiseExceptions = True 24 | p = cssutils.css.Property('color', 'red', '') 25 | assert p.priority == '' 26 | p = cssutils.css.Property('color', 'red', '!important') 27 | assert p.priority == 'important' 28 | with pytest.raises(xml.dom.SyntaxErr): 29 | cssutils.css.Property('color', 'red', 'x') 30 | 31 | cssutils.log.raiseExceptions = False 32 | p = cssutils.css.Property('color', 'red', '!x') 33 | assert p.priority == 'x' 34 | p = cssutils.css.Property('color', 'red', '!x') 35 | assert p.priority == 'x' 36 | cssutils.log.raiseExceptions = True 37 | 38 | # invalid but kept! 39 | # cssutils.log.raiseExceptions = False 40 | s = cssutils.parseString('a { color: red !x }') 41 | assert 'a {\n color: red !x\n }' == s.cssText 42 | assert 'x' == s.cssRules[0].style.getPropertyPriority('color') 43 | -------------------------------------------------------------------------------- /docs/backlog.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | backlog 3 | ======== 4 | 5 | CSS3 color module 6 | ----------------- 7 | :prio: 1 8 | 9 | - named colors:: 10 | 11 | Black = #000000 Green = #008000 12 | Silver = #C0C0C0 Lime = #00FF00 13 | Gray = #808080 Olive = #808000 14 | White = #FFFFFF Yellow = #FFFF00 15 | Maroon = #800000 Navy = #000080 16 | Red = #FF0000 Blue = #0000FF 17 | Purple = #800080 Teal = #008080 18 | Fuchsia = #FF00FF Aqua = #00FFFF 19 | 20 | - "transparent" 21 | - SVG colors? 22 | - "currentColor" 23 | - "flavor"? 24 | - System Colors are DEPRECATED! 25 | 26 | Fix profile. 27 | 28 | 29 | Selector 30 | -------- 31 | :prio: 1 32 | 33 | - CSS2XPath converter -> use lxml.cssselector 34 | - optimize selector parsing? 35 | - **add query functionality** 36 | + specificity DONE 37 | + cascade 38 | 39 | 40 | CSSValue 41 | -------- 42 | :prio: 3 43 | 44 | **this feature may be implemented later as CSSOM defines the complete thing differently** 45 | 46 | implement RGBColor, Rect and Counter 47 | 48 | serializer 49 | ~~~~~~~~~~ 50 | - add preference option how color values should be serializer: 51 | 52 | ser.prefs.COLORS_HEX, also the DEFAULT? 53 | e.g. #123, so short form is possible 54 | ignored for rgba() 55 | ser.prefs.COLORS_HEXFULL 56 | e.g. #112233, so always 6digit hex 57 | ignored for rgba() 58 | ser.prefs.COLORS_RGB_INTEGER 59 | e.g. rgb(1.1, 55, 255), so range from 0-255 60 | also for rgba() 61 | ser.prefs.COLORS_RGB_PERCENTAGE 62 | e.g. rgb(10%, 20%, 100%), so range from 0 to 100% 63 | also for rgba() 64 | ser.prefs.COLORS_FROM_SOURCE 65 | use colors as used in CSS Source 66 | 67 | additionally: 68 | ser.prefs.NAMED_COLORS 69 | e.g. white for #fff or rgb(100%, 100%, 100%) 70 | 71 | - **refactor**: all preferences values should be constansts like above 72 | 73 | CSS2Properties 74 | -------------- 75 | :prio: 3 76 | 77 | needs to be implemented fully, setting of margin: 1px sets actually marginTop, marginLeft etc 78 | 79 | 80 | performance 81 | ----------- 82 | :prio: 3 83 | 84 | 85 | serializer 86 | ---------- 87 | :prio: 2 88 | 89 | - prettyprint convinience serializer? 90 | - XML serializer to be able to handle CSS with XSLT, schemas etc? 91 | - different coding styles? 92 | 93 | 94 | LinkStyle, DocumentStyle 95 | ------------------------ 96 | :prio: 3 97 | 98 | :: 99 | 100 | // Introduced in DOM Level 2: 101 | interface LinkStyle { 102 | readonly attribute StyleSheet sheet; 103 | }; 104 | // Introduced in DOM Level 2: 105 | interface DocumentStyle { 106 | readonly attribute StyleSheetList styleSheets; 107 | }; 108 | -------------------------------------------------------------------------------- /docs/codec.rst: -------------------------------------------------------------------------------- 1 | CSS codec 2 | ========= 3 | .. automodule:: cssutils.codec 4 | :members: 5 | :inherited-members: 6 | 7 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | extensions = [ 4 | 'sphinx.ext.autodoc', 5 | 'jaraco.packaging.sphinx', 6 | ] 7 | 8 | master_doc = "index" 9 | html_theme = "furo" 10 | 11 | # Link dates and other references in the changelog 12 | extensions += ['rst.linker'] 13 | link_files = { 14 | '../NEWS.rst': dict( 15 | using=dict(GH='https://github.com'), 16 | replace=[ 17 | dict( 18 | pattern=r'(Issue #|\B#)(?P\d+)', 19 | url='{package_url}/issues/{issue}', 20 | ), 21 | dict( 22 | pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', 23 | with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', 24 | ), 25 | dict( 26 | pattern=r'PEP[- ](?P\d+)', 27 | url='https://peps.python.org/pep-{pep_number:0>4}/', 28 | ), 29 | dict( 30 | pattern=r'(Bitbucket #)(?P\d+)', 31 | url='https://web.archive.org/web/20200701035501/' 32 | 'https://bitbucket.org/cthedot/cssutils/issues/{bb_issue}/', 33 | ), 34 | ], 35 | ) 36 | } 37 | 38 | # Be strict about any broken references 39 | nitpicky = True 40 | nitpick_ignore: list[tuple[str, str]] = [] 41 | 42 | # Include Python intersphinx mapping to prevent failures 43 | # jaraco/skeleton#51 44 | extensions += ['sphinx.ext.intersphinx'] 45 | intersphinx_mapping = { 46 | 'python': ('https://docs.python.org/3', None), 47 | } 48 | 49 | # Preserve authored syntax for defaults 50 | autodoc_preserve_defaults = True 51 | 52 | # Add support for linking usernames, PyPI projects, Wikipedia pages 53 | github_url = 'https://github.com/' 54 | extlinks = { 55 | 'user': (f'{github_url}%s', '@%s'), 56 | 'pypi': ('https://pypi.org/project/%s', '%s'), 57 | 'wiki': ('https://wikipedia.org/wiki/%s', '%s'), 58 | } 59 | extensions += ['sphinx.ext.extlinks'] 60 | 61 | # local 62 | 63 | extensions += ['jaraco.tidelift'] 64 | -------------------------------------------------------------------------------- /docs/encutils.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | ``encutils`` module 3 | ======================== 4 | 5 | .. automodule:: encutils 6 | :members: 7 | 8 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 2 2 | 3 | .. _changes: 4 | 5 | History 6 | ******* 7 | 8 | .. include:: ../NEWS (links).rst 9 | -------------------------------------------------------------------------------- /docs/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 3959ab43557d8b7928363666c42e7be9 4 | tags: fbb0d17656682115ca4d033fb2f83ba1 5 | -------------------------------------------------------------------------------- /docs/html/_2to3 py3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | TODO — cssutils 0.9.8 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 |

TODO

51 |
52 |
errors in test_codec?
53 |

src3_codec3 => src_codec3!!!

54 |

codec3 #478

55 |
56 |
57 |
try:
58 |
59 |
if self.streamwriter is not None:
60 |
self.streamwriter.errors = errors
61 |
62 |
63 |
64 |
except AttributeError as e:
65 |
print(‘FAIL’)
66 |
67 |
68 |
69 |
70 | 71 | 72 |
73 |
74 |
75 |
76 |
77 |

This Page

78 | 82 | 94 | 95 |
96 |
97 |
98 |
99 | 111 | 115 | 116 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/backlog.txt: -------------------------------------------------------------------------------- 1 | ======== 2 | backlog 3 | ======== 4 | 5 | CSS3 color module 6 | ----------------- 7 | :prio: 1 8 | 9 | implement rgba() 10 | 11 | - named colors:: 12 | 13 | Black = #000000 Green = #008000 14 | Silver = #C0C0C0 Lime = #00FF00 15 | Gray = #808080 Olive = #808000 16 | White = #FFFFFF Yellow = #FFFF00 17 | Maroon = #800000 Navy = #000080 18 | Red = #FF0000 Blue = #0000FF 19 | Purple = #800080 Teal = #008080 20 | Fuchsia = #FF00FF Aqua = #00FFFF 21 | 22 | - "transparent" 23 | - rgba(R, G, B, opacity): opacity in range (0,1) 24 | - hsl(), hsla() ? 25 | - SVG colors? 26 | - "currentColor" 27 | - System Colors are DEPRECATED! 28 | - "flavor"? 29 | 30 | 31 | Selector 32 | -------- 33 | :prio: 1 34 | 35 | - CSS2XPath converter -> use lxml.cssselector 36 | - optimize selector parsing? 37 | - **add query functionality** 38 | + specificity DONE 39 | + cascade 40 | 41 | 42 | CSSValue 43 | -------- 44 | :prio: 3 45 | 46 | **this feature may be implemented later as CSSOM defines the complete thing differently** 47 | 48 | implement RGBColor, Rect and Counter 49 | 50 | serializer 51 | ~~~~~~~~~~ 52 | - add preference option how color values should be serializer: 53 | 54 | ser.prefs.COLORS_HEX, also the DEFAULT? 55 | e.g. #123, so short form is possible 56 | ignored for rgba() 57 | ser.prefs.COLORS_HEXFULL 58 | e.g. #112233, so always 6digit hex 59 | ignored for rgba() 60 | ser.prefs.COLORS_RGB_INTEGER 61 | e.g. rgb(1.1, 55, 255), so range from 0-255 62 | also for rgba() 63 | ser.prefs.COLORS_RGB_PERCENTAGE 64 | e.g. rgb(10%, 20%, 100%), so range from 0 to 100% 65 | also for rgba() 66 | ser.prefs.COLORS_FROM_SOURCE 67 | use colors as used in CSS Source 68 | 69 | additionally: 70 | ser.prefs.NAMED_COLORS 71 | e.g. white for #fff or rgb(100%, 100%, 100%) 72 | 73 | - **refactor**: all preferences values should be constansts like above 74 | 75 | CSS2Properties 76 | -------------- 77 | :prio: 3 78 | 79 | needs to be implemented fully, setting of margin: 1px sets actually marginTop, marginLeft etc 80 | 81 | 82 | performance 83 | ----------- 84 | :prio: 3 85 | 86 | 87 | serializer 88 | ---------- 89 | :prio: 2 90 | 91 | - prettyprint convinience serializer? 92 | - XML serializer to be able to handle CSS with XSLT, schemas etc? 93 | - different coding styles? 94 | 95 | 96 | LinkStyle, DocumentStyle 97 | ------------------------ 98 | :prio: 3 99 | 100 | :: 101 | 102 | // Introduced in DOM Level 2: 103 | interface LinkStyle { 104 | readonly attribute StyleSheet sheet; 105 | }; 106 | // Introduced in DOM Level 2: 107 | interface DocumentStyle { 108 | readonly attribute StyleSheetList styleSheets; 109 | }; 110 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/codec.txt: -------------------------------------------------------------------------------- 1 | CSS codec 2 | ========= 3 | .. automodule:: cssutils.codec 4 | :members: 5 | :inherited-members: 6 | 7 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/cssutils.txt: -------------------------------------------------------------------------------- 1 | ==================== 2 | package ``cssutils`` 3 | ==================== 4 | :version: $Id: cssutils.txt 1609 2009-01-03 21:07:17Z cthedot $ 5 | 6 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/encutils.txt: -------------------------------------------------------------------------------- 1 | ======================== 2 | ``encutils`` module 3 | ======================== 4 | 5 | .. automodule:: encutils 6 | :members: 7 | 8 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/implementation.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | implementation status 3 | ===================== 4 | cssutils partly implements the DOM Level 2 Style Stylesheets and DOM Level 2 CSS interfaces. DOM Level 2 Core and DOM Level 2 Views are not implemented. 5 | 6 | See the relevant specification on the W3C site for details about usage, cssutils tries to implement these as close as possible. 7 | 8 | DOM Level 2 CSS implementation details 9 | ====================================== 10 | All classes are in package ``cssutils.css`` which is directly available from ``cssutils``. 11 | 12 | :css.CSSStyleSheet: DONE (except some details) 13 | :css.CSSRuleList: DONE, based partly on Python list 14 | :css.CSSRule: DONE, but useful really only as a superclass for individual rule classes like CSSCharsetRule 15 | :css.CSSCharsetRule: DONE 16 | :css.CSSFontFaceRule: DONE (from v0.9.4a4. Was removed in CSS 2.1 but is used in some products (Prince XML) and seems useful after all) 17 | :css.CSSImportRule: DONE 18 | :css.CSSMediaRule: DONE 19 | :css.CSSNamespaceRule: DONE 20 | :css.CSSPageRule: DONE 21 | :css.CSSStyleRule: DONE (except some details) 22 | :css.CSSUnknownRule: DONE (mostly) 23 | :css.CSSComment: (No official DOM) Similar to other CSSRule subclasses. 24 | :css.SelectorList: (No official DOM) A custom list of css.Selector objects in a css.CSSStyleRule. 25 | :css.Selector: (No official DOM) A simple selector in a css.SelectorList of a css.CSSStyleRule . Also implements parts of the Selectors Working Draft namely the namespace matching possibilities e.g. ``xsl|match``. 26 | :css.CSSStyleDeclaration: DONE 27 | :css.CSS2Properties: Partly implemented by CSSStyleDeclaration (since cssutils v0.9.2) 28 | :Property: (No official DOM) A single CSS property with name, value and priority attributes. A list of these form a CSSStyleDeclaration and are retrievable via CSSStyleDeclaration.getProperties(name) 29 | :css.CSSValue: DONE 30 | :css.CSSPrimitiveValue: DONE 31 | :css.CSSValueList: DONE 32 | :css.RGBColor: Waits development of CSSOM 33 | :css.Rect: Waits development of CSSOM 34 | :css.Counter: Waits development of CSSOM 35 | 36 | 37 | DOM Level 2 Stylesheets implementation details 38 | ============================================== 39 | All classes are in package ``cssutils.stylesheets`` which is directly available from ``cssutils``. 40 | 41 | :stylesheets.StyleSheet: DONE mostly 42 | :stylesheets.StyleSheetList: DONE, based on Python list 43 | :stylesheets.MediaList: DONE, contains stylesheets.MediaQuery objects in contrast to the official DOM 2 which did define simple strings. Partly implements a Python list. 44 | :stylesheets.MediaQuery: DONE. Forms the MediaList entries. 45 | 46 | 47 | cssutils scripts 48 | ================ 49 | Additional cssutils helpers installed as scripts. 50 | 51 | :csscapture: Capture all CSS style sheets for a given URI, normally from an HTML page. 52 | :csscombine: Resolve @import definitions and cut down on HTTP requests. 53 | :cssparse: Command line parser. -------------------------------------------------------------------------------- /docs/html/_sources/docs/logging.txt: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.errorhandler 2 | 3 | .. index:: 4 | single: log, cssutils.log 5 | object: cssutils.log 6 | 7 | logging 8 | ======= 9 | 10 | A global logger is used throughout the library. You may configure it or even replace it with your own. Customizing the default log should be sufficient for most purposes though. 11 | 12 | The default logger is available as ``cssutils.log``. It has the following methods which are basically the same as defined for standard ``logging`` loggers: 13 | 14 | * ``log.setLevel(level)`` and ``log.getEffectiveLevel()``, example:: 15 | 16 | import logging 17 | cssutils.log.setLevel(logging.FATAL) 18 | 19 | * ``log.addHandler(h)`` and ``log.removeHandler(h)`` 20 | * ``log.*(msg)`` where ``*`` is one of ``debug``, ``info``, ``warn``, ``error`` or ``fatal``/``critical`` 21 | 22 | Additional method: ``cssutils.log.setLog(newlog)``: To replace the default log which sends output to ``stderr``. 23 | 24 | 25 | See also :meth:`cssutils.css.Property.validate` for details on how properties log. -------------------------------------------------------------------------------- /docs/html/_sources/docs/parse.txt: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.parse 2 | 3 | =========== 4 | parsing CSS 5 | =========== 6 | Options to parse a given stylesheet: Get an instance of :class:`cssutils.CSSParser` and use the provided ``parse*`` methods or for simpler parsing use the ``parse*`` `convienience functions`_. 7 | 8 | 9 | 10 | Convienience Functions 11 | ====================== 12 | Shortcuts for initializing a new :class:`cssutils.CSSParser` and use its ``parse*`` methods. Parsing a stylesheet this way does not raise any exceptions if an error occurs but parses CSS as defined in the specifications. If you need advanced parser handline use :class:`cssutils.CSSParser` directly. 13 | 14 | ``parseString`` 15 | --------------- 16 | .. autofunction:: cssutils.parseString(cssText, encoding=None, href=None, media=None, title=None) 17 | 18 | ``parseFile`` 19 | ------------- 20 | .. autofunction:: cssutils.parseFile(filename, encoding=None, href=None, media=None, title=None) 21 | 22 | ``parseUrl`` 23 | ------------ 24 | :: cssutils.parseUrl(href, encoding=None, media=None, title=None) 25 | 26 | 27 | Working with inline styles 28 | ========================== 29 | ``parseStyle`` 30 | -------------- 31 | .. autofunction:: cssutils.parseStyle 32 | 33 | 34 | ``CSSParser`` 35 | ============= 36 | The parser is reusable. 37 | 38 | .. autoclass:: cssutils.CSSParser 39 | :members: 40 | :inherited-members: 41 | 42 | The URL Fetcher 43 | --------------- 44 | If you want to control how imported stylesheets are read you may define a custom URL fetcher (e.g. would be needed on Google AppEngine as urllib2, which is normally used, is not available. A GAE specific fetcher is included in cssutils from 0.9.5a1 though.) 45 | 46 | A custom URL fetcher may be used during parsing via ``CSSParser.setFetcher(fetcher)`` (or as an init parameter). The so customized parser is reusable. The fetcher is called when an ``@import`` rule is found and the referenced stylesheet is about to be retrieved. 47 | 48 | Example:: 49 | 50 | def fetcher(url): 51 | return 'ascii', '/*test*/' 52 | 53 | parser = cssutils.CSSParser(fetcher=fetcher) 54 | parser.parse... 55 | 56 | Example 2 with a fetcher returning a unicode string:: 57 | 58 | def fetcher(url): 59 | return None, u'/*test*/' 60 | 61 | parser = cssutils.CSSParser(fetcher=fetcher) 62 | parser.parse... 63 | 64 | To omit parsing of imported sheets just define a fetcher like ``lambda url: None`` (A single ``None`` is sufficient but returning ``None, None`` would be clearer). 65 | 66 | You may also define a fetcher which overrides the internal encoding for imported sheets with a fetcher that returns a (normally HTTP) encoding depending e.g on the URL. 67 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/profiles.txt: -------------------------------------------------------------------------------- 1 | ========= 2 | profiles 3 | ========= 4 | 5 | .. index:: 6 | single: profile 7 | 8 | ``cssutils.profile`` 9 | ==================== 10 | A global object ``cssutils.profile`` is used for validation of all properties. It is an instance of :class:`cssutils.profiles.Profiles`. Add or remove new profile definitions here. 11 | 12 | Most important method is :meth:`cssutils.profiles.Profiles.addProfile` (use ``cssutils.profile.addProfile``) to add new properties to cssutils and the setting of ``defaultProfiles``. 13 | 14 | 15 | 16 | Example of how to add a new profile:: 17 | 18 | >>> import cssutils 19 | >>> sheet = cssutils.parseString('x { -test-custommacro: x }') 20 | >>> print sheet.cssRules[0].style.getProperties()[0].valid 21 | False 22 | >>> M1 = { 23 | ... 'testvalue': 'x' 24 | ... } 25 | >>> P1 = { 26 | ... '-test-tokenmacro': '({num}{w}){1,2}', 27 | ... '-test-macro': '{ident}|{percentage}', 28 | ... '-test-custommacro': '{testvalue}', 29 | ... # custom validation function 30 | ... '-test-funcval': lambda(v): int(v) > 0 31 | ... } 32 | >>> cssutils.profile.addProfile('test', P1, M1) 33 | >>> sheet = cssutils.parseString('x { -test-custommacro: x }') 34 | >>> print sheet.cssRules[0].style.getProperties()[0].valid 35 | True 36 | 37 | An additional per CSSStyleSheet setting of a profile may be added soon. 38 | 39 | **Please note: This might change again, but only slightly as it has been refactored in 0.9.6a2.** 40 | 41 | 42 | .. index:: 43 | single: defaultProfiles 44 | single: macros 45 | single: properties 46 | 47 | ``cssutils.profiles.macros`` and ``cssutils.profiles.properties`` 48 | ================================================================= 49 | Two dictionaries which contain macro and property definitions for the predefined property profiles. 50 | 51 | Both use the additional macros defined in ``Profiles._TOKEN_MACROS`` and ``Profiles._MACROS`` which contain basic macros for definition of new properties. Things like `ident`, `name` or `hexcolor` are defined there and may be used in any new property definition as these two macro sets defined in ``Profiles`` are added to any custom macro definition given. You may overwrite these basic macros with your own macros or simply define your own macros and use only these. 52 | 53 | Use ``cssutils.profiles.macros`` if you need any other predefined macro or ``cssutils.profiles.properties`` if you want to add any known property to your custom property profile. 54 | 55 | 56 | ``cssutils.profiles.Profiles`` 57 | ============================== 58 | .. autoclass:: cssutils.profiles.Profiles 59 | :members: 60 | :inherited-members: 61 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/serialize.txt: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.serialize 2 | 3 | .. index:: 4 | single: ser, cssutils.ser 5 | object: cssutils.ser 6 | 7 | =============== 8 | serializing CSS 9 | =============== 10 | To serialize any stylesheet use:: 11 | 12 | print sheet.cssText 13 | 14 | Also most other objects have a similar property which contains the *text* content of each object. Some use a slightly different name (e.g. ``selectorText``) but all use the global serializer:: 15 | 16 | >>> sheet = cssutils.parseString('a, b { color: green }') 17 | >>> print sheet.cssRules[0].cssText 18 | a, b { 19 | color: green 20 | } 21 | >>> print sheet.cssRules[0].selectorText 22 | a, b 23 | >>> print sheet.cssRules[0].selectorList[1].selectorText 24 | b 25 | 26 | 27 | .. _Preferences: 28 | 29 | .. index:: 30 | single: cssutils.ser.prefs 31 | object: cssutils.ser.prefs 32 | 33 | ``Preferences`` 34 | =============== 35 | Quite a few preferences of the cssutils serializer may be tweaked. 36 | 37 | To set a preference use:: 38 | 39 | cssutils.ser.prefs.PREFNAME = NEWVALUE 40 | 41 | Preferences are always used *globally*, so for all stylesheets until preferences are set again. 42 | 43 | 44 | .. autoclass:: cssutils.serialize.Preferences 45 | :members: 46 | :inherited-members: 47 | 48 | 49 | 50 | ``CSSSerializer`` 51 | ================= 52 | There is a single global serializer used throughout the library. You may configure it by setting specific Preferences_ or completely replace it with your own. 53 | 54 | A custom serializer must implement all methods the default one provides. Easiest would be to subclass :class:`cssutils.serialize.CSSSerializer`. 55 | 56 | To set a new serializer, use:: 57 | 58 | cssutils.setSerializer(serializer) 59 | 60 | You may also set :attr:`cssutils.ser` directly but the above method is the preferred one. 61 | 62 | For most cases adjusting the :attr:`cssutils.ser.prefs` of the default serializer should be sufficient though. 63 | 64 | .. autoclass:: cssutils.serialize.CSSSerializer 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/settings.txt: -------------------------------------------------------------------------------- 1 | ==================== 2 | extra settings 3 | ==================== 4 | A single and **experimental** setting currently only: 5 | 6 | It is possible to at least parse sheets with Microsoft only property values for 7 | ``filter`` which start with ``progid:DXImageTransform.Microsoft.[...](``. 8 | 9 | To enable these you need to set:: 10 | 11 | >>> from cssutils import settings 12 | >>> settings.set('DXImageTransform.Microsoft', True) 13 | >>> cssutils.ser.prefs.useMinified() 14 | >>> text = 'a {filter: progid:DXImageTransform.Microsoft.BasicImage( rotation = 90 )}' 15 | >>> print cssutils.parseString(text).cssText 16 | a{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=90)} 17 | >>> 18 | 19 | This currently is a **major hack** but if you like to minimize sheets in the wild which use this kind of CSS cssutils at least can parse and reserialize them. 20 | Also you cannot reset this change until you restart your program. 21 | 22 | As these filter values are case sensitive there are in no way normalized either. 23 | Neither are values like ``expression(...)`` or ``alpha(...)`` anymore which are 24 | parsable without settings this specific switch. 25 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/stylesheets.txt: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.stylesheets 2 | 3 | ================================ 4 | Package ``cssutils.stylesheets`` 5 | ================================ 6 | Classes implementing Document Object Model Level 2 Style Sheets http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/stylesheets.html 7 | 8 | 9 | ``StyleSheet`` 10 | ============== 11 | .. autoclass:: cssutils.stylesheets.StyleSheet 12 | :members: 13 | :inherited-members: 14 | 15 | ``StyleSheetList`` 16 | ================== 17 | .. autoclass:: cssutils.stylesheets.StyleSheetList 18 | :members: item, length 19 | 20 | ``MediaQuery`` 21 | ============== 22 | .. autoclass:: cssutils.stylesheets.MediaQuery 23 | :members: 24 | :inherited-members: 25 | 26 | ``MediaList`` 27 | ============= 28 | .. autoclass:: cssutils.stylesheets.MediaList 29 | :members: 30 | :inherited-members: 31 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/utilities.txt: -------------------------------------------------------------------------------- 1 | ==================== 2 | utilities 3 | ==================== 4 | A few utility functions which may help when working with CSS stylesheets. 5 | 6 | 7 | 8 | ``getUrls`` 9 | ----------- 10 | .. autofunction:: cssutils.getUrls 11 | 12 | ``replaceUrls`` 13 | --------------- 14 | .. autofunction:: cssutils.replaceUrls 15 | 16 | ``resolveImports`` 17 | ------------------ 18 | .. autofunction:: cssutils.resolveImports 19 | -------------------------------------------------------------------------------- /docs/html/_sources/docs/variables.txt: -------------------------------------------------------------------------------- 1 | ======================== 2 | CSS Variables 3 | ======================== 4 | .. module:: cssutils.css 5 | 6 | See :class:`cssutils.serialize.Preferences` about a way to replace all variable references with their actual values while serializing. As CSSVariables are not implemented in any (?) browser or other user agent cssutils preprocesses these. So you are able to use variables in your stylesheets without having to worry about UA support. 7 | 8 | TODO: example 9 | -------------------------------------------------------------------------------- /docs/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | ============================================= 2 | cssutils 3 | ============================================= 4 | --------------------------------------------- 5 | CSS Cascading Style Sheets library for Python 6 | --------------------------------------------- 7 | :version release: |release| 8 | 9 | Contents 10 | ======== 11 | .. toctree:: 12 | :maxdepth: 3 13 | 14 | About 15 | Changelog 16 | Migration from older version 17 | 18 | parsing 19 | utilities 20 | Experimental Settings 21 | serializing 22 | `cssutils.css` package 23 | `cssutils.stylesheets` package 24 | CSS properties profiles (used for validation) 25 | CSS variables 26 | Logging 27 | Scripts 28 | CSS Codec 29 | `encutils` module 30 | 31 | Backlog 32 | 33 | 34 | 35 | See also 36 | ======== 37 | * `cssutils at Google Code `__ 38 | * `cssutils discussion group `__ 39 | * `cssutils at cthedot `__ 40 | 41 | 42 | Indices and tables 43 | ================== 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | 48 | -------------------------------------------------------------------------------- /docs/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/docs/html/_static/file.png -------------------------------------------------------------------------------- /docs/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/docs/html/_static/minus.png -------------------------------------------------------------------------------- /docs/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/docs/html/_static/plus.png -------------------------------------------------------------------------------- /docs/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #303030 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/html/docs/codec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | CSS codec — cssutils 0.9.8 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |

CSS codec

59 |

Python codec for CSS.

60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 |

Previous topic

69 |

scripts

71 |

Next topic

72 |

encutils module

74 |

This Page

75 | 79 | 91 | 92 |
93 |
94 |
95 |
96 | 114 | 118 | 119 | -------------------------------------------------------------------------------- /docs/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Search — cssutils 0.9.8 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 104 | 105 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to |project| documentation! 2 | =================================== 3 | 4 | .. sidebar-links:: 5 | :home: 6 | :pypi: 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | history 12 | Migration from older version 13 | known issues 14 | 15 | parsing 16 | utilities 17 | Experimental Settings 18 | serializing 19 | `cssutils.css` package 20 | `cssutils.stylesheets` package 21 | CSS properties profiles (used for validation) 22 | CSS variables 23 | Logging 24 | Scripts 25 | CSS Codec 26 | `encutils` module 27 | 28 | Backlog 29 | 30 | 31 | .. tidelift-referral-banner:: 32 | 33 | 34 | Indices and tables 35 | ================== 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | -------------------------------------------------------------------------------- /docs/known issues.rst: -------------------------------------------------------------------------------- 1 | Known Issues 2 | ============ 3 | 4 | - validation is not complete. Properties using ``calc()`` for example will be reported invalid but may well be valid. They are *wellformed* however and will be parsed and serialized properly. 5 | 6 | - comments may not survive parsing in all cases 7 | 8 | - ``CSSStyleSheet.cssText`` is a serialized byte string (**not** unicode string) currently. This may change in the future. 9 | 10 | - ``CSS2Properties`` not implemented completely (setting a shorthand property does not set related properties like setting margin does not set margin-left etc). Also the return values are not as defined in the specification as no normalizing is done yet. Prefer to use ``style['property']`` over ``style.property``. 11 | 12 | - The ``seq`` attribute of most classes does not hinder you to add invalid items. It will probably become readonly. **Never write to it!** 13 | 14 | **Content of ``seq`` will most likely change completely, seq is more of an internal property and should not be used in client code yet** 15 | 16 | - although cssutils tries to preserve CSS hacks not all are (and some - mainly syntactically invalid ones - will probably never be). The following hacks are known to **not** be preserved: 17 | 18 | star hack (without any whitespace) 19 | ``*html`` syntactically invalid 20 | star7 hack (without any whitespace) 21 | ``html*#test-span`` (IMHO invalidated by the missing WS between html and "*") 22 | 23 | The main problem for cssutils users is that some stylesheets in the wild are not parsable without loosing some information, a pretty print for these sheets is simply not possible with cssutils (actually with hardly any css parser...). 24 | 25 | Generally **syntactically valid (wellformed) stylesheets** should be preserved completely (otherwise it will be a bug in cssutils itself). Invalid stylesheets will probably loose some information like to above ``*html`` hack. Most of these hacks may be rewritten while still be working, e.g. ``* html`` should work same to ``*html``. Until cssutils 0.9.5b2 the invalid IE-specific CSS hack using ``$propertyname`` was preserved but its usage was already discouraged (and if e.g. specifying ``color`` and ``$color`` these properties are **not the same** for cssutils (but are for IE...). 26 | **These kind of invalid hacks are not kept during parsing anymore since cssutils 0.9.5b3!** 27 | In almost any case it is possible to use at least syntactically valid CSS while still working around different browser implementations. 28 | 29 | - when PyXML is installed not all tests may run through (see issue #34 for details) as PyXMLs implementation of ``xml.dom.DOMException`` differs from the default (minidom and I guess others) implemtation. Nothing really to worry about... 30 | -------------------------------------------------------------------------------- /docs/logging.rst: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.errorhandler 2 | 3 | .. index:: 4 | single: log, cssutils.log 5 | pair: object; cssutils.log 6 | 7 | logging 8 | ======= 9 | 10 | A global logger is used throughout the library. You may configure it or even replace it with your own. Customizing the default log should be sufficient for most purposes though. 11 | 12 | The default logger is available as ``cssutils.log``. It has the following methods which are basically the same as defined for standard ``logging`` loggers: 13 | 14 | * ``log.setLevel(level)`` and ``log.getEffectiveLevel()``, example:: 15 | 16 | import logging 17 | cssutils.log.setLevel(logging.FATAL) 18 | 19 | * ``log.addHandler(h)`` and ``log.removeHandler(h)`` 20 | * ``log.*(msg)`` where ``*`` is one of ``debug``, ``info``, ``warn``, ``error`` or ``fatal``/``critical`` 21 | 22 | Additional method: ``cssutils.log.setLog(newlog)``: To replace the default log which sends output to ``stderr``. 23 | 24 | 25 | See also :meth:`cssutils.css.Property.validate` for details on how properties log. 26 | -------------------------------------------------------------------------------- /docs/parse.rst: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.parse 2 | 3 | =========== 4 | parsing CSS 5 | =========== 6 | Options to parse a given stylesheet: Get an instance of :class:`cssutils.CSSParser` and use the provided ``parse*`` methods or for simpler parsing use the ``parse*`` `convienience functions`_. 7 | 8 | 9 | 10 | Convienience Functions 11 | ====================== 12 | Shortcuts for initializing a new :class:`cssutils.CSSParser` and use its ``parse*`` methods. Parsing a stylesheet this way does not raise any exceptions if an error occurs but parses CSS as defined in the specifications. If you need advanced parser handline use :class:`cssutils.CSSParser` directly. 13 | 14 | ``parseString`` 15 | --------------- 16 | .. autofunction:: cssutils.parseString(cssText, encoding=None, href=None, media=None, title=None, validate=None) 17 | 18 | ``parseFile`` 19 | ------------- 20 | .. autofunction:: cssutils.parseFile(filename, encoding=None, href=None, media=None, title=None, validate=None) 21 | 22 | ``parseUrl`` 23 | ------------ 24 | .. autofunction:: cssutils.parseUrl(href, encoding=None, media=None, title=None, validate=None) 25 | 26 | 27 | Working with inline styles 28 | ========================== 29 | ``parseStyle`` 30 | -------------- 31 | .. autofunction:: cssutils.parseStyle(cssText, encoding='utf-8') 32 | 33 | 34 | ``CSSParser`` 35 | ============= 36 | The parser is reusable. 37 | 38 | .. autoclass:: cssutils.CSSParser 39 | :members: 40 | :inherited-members: 41 | 42 | 43 | The URL Fetcher 44 | --------------- 45 | If you want to control how imported stylesheets are read you may define a custom URL fetcher (e.g. would be needed on Google AppEngine as urllib2, which is normally used, is not available. A GAE specific fetcher is included in cssutils from 0.9.5a1 though.) 46 | 47 | A custom URL fetcher may be used during parsing via ``CSSParser.setFetcher(fetcher)`` (or as an init parameter). The so customized parser is reusable. The fetcher is called when an ``@import`` rule is found and the referenced stylesheet is about to be retrieved. 48 | 49 | Example:: 50 | 51 | def fetcher(url): 52 | return 'ascii', '/*test*/' 53 | 54 | parser = cssutils.CSSParser(fetcher=fetcher) 55 | parser.parse... 56 | 57 | Example 2 with a fetcher returning a unicode string:: 58 | 59 | def fetcher(url): 60 | return None, u'/*test*/' 61 | 62 | parser = cssutils.CSSParser(fetcher=fetcher) 63 | parser.parse... 64 | 65 | To omit parsing of imported sheets just define a fetcher like ``lambda url: None`` (A single ``None`` is sufficient but returning ``None, None`` would be clearer). 66 | 67 | You may also define a fetcher which overrides the internal encoding for imported sheets with a fetcher that returns a (normally HTTP) encoding depending e.g on the URL. 68 | -------------------------------------------------------------------------------- /docs/profiles.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | profiles 3 | ========= 4 | 5 | .. index:: 6 | single: profile 7 | 8 | ``cssutils.profile`` 9 | ==================== 10 | A global object ``cssutils.profile`` is used for validation of all properties. It is an instance of :class:`cssutils.profiles.Profiles`. Add or remove new profile definitions here. 11 | 12 | Most important method is :meth:`cssutils.profiles.Profiles.addProfile` (use ``cssutils.profile.addProfile``) to add new properties to cssutils and the setting of ``defaultProfiles``. 13 | 14 | 15 | 16 | Example of how to add a new profile:: 17 | 18 | >>> import cssutils 19 | >>> sheet = cssutils.parseString('x { -test-custommacro: x }') 20 | >>> print sheet.cssRules[0].style.getProperties()[0].valid 21 | False 22 | >>> M1 = { 23 | ... 'testvalue': 'x' 24 | ... } 25 | >>> P1 = { 26 | ... '-test-tokenmacro': '({num}{w}){1,2}', 27 | ... '-test-macro': '{ident}|{percentage}', 28 | ... '-test-custommacro': '{testvalue}', 29 | ... # custom validation function 30 | ... '-test-funcval': lambda(v): int(v) > 0 31 | ... } 32 | >>> cssutils.profile.addProfile('test', P1, M1) 33 | >>> sheet = cssutils.parseString('x { -test-custommacro: x }') 34 | >>> print sheet.cssRules[0].style.getProperties()[0].valid 35 | True 36 | 37 | An additional per CSSStyleSheet setting of a profile may be added soon. 38 | 39 | **Please note: This might change again, but only slightly as it has been refactored in 0.9.6a2.** 40 | 41 | 42 | .. index:: 43 | single: defaultProfiles 44 | single: macros 45 | single: properties 46 | 47 | ``cssutils.profiles.macros`` and ``cssutils.profiles.properties`` 48 | ================================================================= 49 | Two dictionaries which contain macro and property definitions for the predefined property profiles. 50 | 51 | Both use the additional macros defined in ``Profiles._TOKEN_MACROS`` and ``Profiles._MACROS`` which contain basic macros for definition of new properties. Things like `ident`, `name` or `hexcolor` are defined there and may be used in any new property definition as these two macro sets defined in ``Profiles`` are added to any custom macro definition given. You may overwrite these basic macros with your own macros or simply define your own macros and use only these. 52 | 53 | Use ``cssutils.profiles.macros`` if you need any other predefined macro or ``cssutils.profiles.properties`` if you want to add any known property to your custom property profile. 54 | 55 | 56 | ``cssutils.profiles.Profiles`` 57 | ============================== 58 | .. autoclass:: cssutils.profiles.Profiles 59 | :members: 60 | :inherited-members: 61 | -------------------------------------------------------------------------------- /docs/serialize.rst: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.serialize 2 | 3 | .. index:: 4 | single: ser, cssutils.ser 5 | pair: object; cssutils.ser 6 | 7 | =============== 8 | serializing CSS 9 | =============== 10 | To serialize any stylesheet use:: 11 | 12 | print sheet.cssText 13 | 14 | Also most other objects have a similar property which contains the *text* content of each object. Some use a slightly different name (e.g. ``selectorText``) but all use the global serializer:: 15 | 16 | >>> sheet = cssutils.parseString('a, b { color: green }') 17 | >>> print sheet.cssRules[0].cssText 18 | a, b { 19 | color: green 20 | } 21 | >>> print sheet.cssRules[0].selectorText 22 | a, b 23 | >>> print sheet.cssRules[0].selectorList[1].selectorText 24 | b 25 | 26 | 27 | .. _Preferences: 28 | 29 | .. index:: 30 | single: cssutils.ser.prefs 31 | pair: object; cssutils.ser.prefs 32 | 33 | ``Preferences`` 34 | =============== 35 | Quite a few preferences of the cssutils serializer may be tweaked. 36 | 37 | To set a preference use:: 38 | 39 | cssutils.ser.prefs.PREFNAME = NEWVALUE 40 | 41 | Preferences are always used *globally*, so for all stylesheets until preferences are set again. 42 | 43 | 44 | .. autoclass:: cssutils.serialize.Preferences 45 | :members: 46 | :inherited-members: 47 | 48 | 49 | 50 | ``CSSSerializer`` 51 | ================= 52 | There is a single global serializer used throughout the library. You may configure it by setting specific Preferences_ or completely replace it with your own. 53 | 54 | A custom serializer must implement all methods the default one provides. Easiest would be to subclass :class:`cssutils.serialize.CSSSerializer`. 55 | 56 | To set a new serializer, use:: 57 | 58 | cssutils.setSerializer(serializer) 59 | 60 | You may also set ``cssutils.ser`` directly but the above method is the preferred one. 61 | 62 | For most cases adjusting the ``cssutils.ser.prefs`` of the default serializer should be sufficient though. 63 | 64 | .. autoclass:: cssutils.serialize.CSSSerializer 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/settings.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | extra settings 3 | ==================== 4 | A single and **experimental** setting currently only: 5 | 6 | It is possible to at least parse sheets with Microsoft only property values for 7 | ``filter`` which start with ``progid:DXImageTransform.Microsoft.[...](``. 8 | 9 | To enable these you need to set:: 10 | 11 | >>> from cssutils import settings 12 | >>> settings.set('DXImageTransform.Microsoft', True) 13 | >>> cssutils.ser.prefs.useMinified() 14 | >>> text = 'a {filter: progid:DXImageTransform.Microsoft.BasicImage( rotation = 90 )}' 15 | >>> print cssutils.parseString(text).cssText 16 | a{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=90)} 17 | >>> 18 | 19 | This currently is a **major hack** but if you like to minimize sheets in the wild which use this kind of CSS cssutils at least can parse and reserialize them. 20 | Also you cannot reset this change until you restart your program. 21 | 22 | As these filter values are case sensitive there are in no way normalized either. 23 | Neither are values like ``expression(...)`` or ``alpha(...)`` anymore which are 24 | parsable without settings this specific switch. 25 | -------------------------------------------------------------------------------- /docs/stylesheets.rst: -------------------------------------------------------------------------------- 1 | .. module:: cssutils.stylesheets 2 | 3 | ================================ 4 | Package ``cssutils.stylesheets`` 5 | ================================ 6 | Classes implementing Document Object Model Level 2 Style Sheets http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/stylesheets.html 7 | 8 | 9 | ``StyleSheet`` 10 | ============== 11 | .. autoclass:: cssutils.stylesheets.StyleSheet 12 | :members: 13 | :inherited-members: 14 | 15 | ``StyleSheetList`` 16 | ================== 17 | .. autoclass:: cssutils.stylesheets.StyleSheetList 18 | :members: item, length 19 | 20 | ``MediaQuery`` 21 | ============== 22 | .. autoclass:: cssutils.stylesheets.MediaQuery 23 | :members: 24 | :inherited-members: 25 | 26 | ``MediaList`` 27 | ============= 28 | .. autoclass:: cssutils.stylesheets.MediaList 29 | :members: 30 | :inherited-members: 31 | -------------------------------------------------------------------------------- /docs/utilities.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | utilities 3 | ==================== 4 | A few utility functions which may help when working with CSS stylesheets. 5 | 6 | 7 | 8 | ``getUrls`` 9 | ----------- 10 | .. autofunction:: cssutils.getUrls 11 | 12 | ``replaceUrls`` 13 | --------------- 14 | .. autofunction:: cssutils.replaceUrls 15 | 16 | ``resolveImports`` 17 | ------------------ 18 | .. autofunction:: cssutils.resolveImports 19 | -------------------------------------------------------------------------------- /docs/variables.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | CSS Variables 3 | ======================== 4 | 5 | See :class:`cssutils.serialize.Preferences` about a way to replace all variable references with their actual values while serializing. As CSSVariables are not implemented in any (?) browser or other user agent cssutils preprocesses these. So you are able to use variables in your stylesheets without having to worry about UA support. 6 | 7 | TODO: example 8 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/examples/__init__.py -------------------------------------------------------------------------------- /examples/build.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import cssutils 4 | 5 | cssutils.log.setLevel(logging.FATAL) 6 | 7 | 8 | EXPOUT = '''@charset "ascii"; 9 | @import "sheets/import.css"; 10 | /* a comment with umlaut \\E4 */ 11 | @namespace xhtml "http://www.w3.org/1999/xhtml"; 12 | @namespace atom "http://www.w3.org/2005/Atom"; 13 | xhtml|a { 14 | color: green !important; 15 | background: #fff; 16 | margin: 1em 17 | } 18 | atom|title { 19 | color: #000 !important 20 | } 21 | ''' 22 | EXPERR = """Property: Found valid "CSS Level 2.1" value: red [4:19: color] 23 | Property: Found valid "CSS Level 2.1" value: #fff [4:30: background] 24 | Property: Found valid "CSS Level 2.1" value: #000 [1:30: color] 25 | Property: Found valid "CSS Level 2.1" value: url(images/example3.gif) [4:2: background] 26 | Property: Found valid "CSS Level 2.1" value: url(./images/example3.gif) [5:2: background] 27 | Property: Found valid "CSS Level 2.1" value: url(import/images2/example2.gif) [6:2: background] 28 | Property: Found valid "CSS Level 2.1" value: url(./import/images2/example2.gif) [7:2: background] 29 | Property: Found valid "CSS Level 2.1" value: url(import/images2/../../images/example3.gif) [8:2: background] 30 | Property: Found valid "CSS Level 2.1" value: url(images2/example2.gif) [4:2: background] 31 | Property: Found valid "CSS Level 2.1" value: url(http://example.com/images/example.gif) [5:2: background] 32 | Property: Found valid "CSS Level 2.1" value: url(//example.com/images/example.gif) [6:2: background] 33 | Property: Found valid "CSS Level 2.1" value: url(/images/example.gif) [7:2: background] 34 | Property: Found valid "CSS Level 2.1" value: url(images2/example.gif) [8:2: background] 35 | Property: Found valid "CSS Level 2.1" value: url(./images2/example.gif) [9:2: background] 36 | Property: Found valid "CSS Level 2.1" value: url(../images/example.gif) [10:2: background] 37 | Property: Found valid "CSS Level 2.1" value: url(./../images/example.gif) [11:2: background] 38 | Property: Found valid "CSS Level 2.1" value: url(images/example.gif) [4:2: background-image] 39 | """ 40 | 41 | 42 | def main(): 43 | cssutils.log.setLevel(logging.DEBUG) 44 | 45 | css = '''/* a comment with umlaut ä */ 46 | @namespace html "http://www.w3.org/1999/xhtml"; 47 | @variables { BG: #fff } 48 | html|a { color:red; background: var(BG) }''' 49 | sheet = cssutils.parseString(css) 50 | 51 | for rule in sheet: 52 | if rule.type == rule.STYLE_RULE: 53 | # find property 54 | for property in rule.style: 55 | if property.name == 'color': 56 | property.value = 'green' 57 | property.priority = 'IMPORTANT' 58 | break 59 | # or simply: 60 | rule.style['margin'] = '01.0eM' # or: ('1em', 'important') 61 | 62 | sheet.encoding = 'ascii' 63 | sheet.namespaces['xhtml'] = 'http://www.w3.org/1999/xhtml' 64 | sheet.namespaces['atom'] = 'http://www.w3.org/2005/Atom' 65 | sheet.add('atom|title {color: #000000 !important}') 66 | sheet.add('@import "sheets/import.css";') 67 | 68 | print(sheet.cssText) 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /examples/codec.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import pathlib 3 | 4 | import cssutils 5 | 6 | __here__ = pathlib.Path(__file__).parent 7 | 8 | 9 | def main(): 10 | cases = __here__.parent / 'sheets' / 'cases.css' 11 | cssText = codecs.open(cases, encoding='css').read() 12 | sheet = cssutils.parseString(cssText) 13 | print(sheet) 14 | print(sheet.cssText) 15 | 16 | 17 | __name__ == '__main__' and main() 18 | -------------------------------------------------------------------------------- /examples/cssencodings.py: -------------------------------------------------------------------------------- 1 | """ 2 | example how to use encodings 3 | 4 | example css is in default UTF-8 encoding 5 | """ 6 | 7 | from cssutils import CSSParser 8 | 9 | EXPOUT = '''cssText in different encodings, depending on the console some 10 | chars may look broken but are actually not 11 | 12 | @charset "ascii"; 13 | /* some umlauts \\E4 \\F6 \\FC and EURO sign \\20AC */ 14 | a:before { 15 | content: "\\E4 " 16 | } 17 | 18 | @charset "iso-8859-1"; 19 | /* some umlauts \xe4\xf6\xfc and EURO sign \\20AC */ 20 | a:before { 21 | content: "\xe4" 22 | } 23 | 24 | @charset "iso-8859-15"; 25 | /* some umlauts \xe4\xf6\xfc and EURO sign \xa4 */ 26 | a:before { 27 | content: "\xe4" 28 | } 29 | 30 | @charset "utf-8"; 31 | /* some umlauts \xc3\xa4\xc3\xb6\xc3\xbc and EURO sign \xe2\x82\xac */ 32 | a:before { 33 | content: "\xc3\xa4" 34 | } 35 | 36 | /* some umlauts \xc3\xa4\xc3\xb6\xc3\xbc and EURO sign \xe2\x82\xac */ 37 | a:before { 38 | content: "\xc3\xa4" 39 | } 40 | ''' 41 | EXPERR = 'Property: Found valid "CSS Level 2.1" value: "\xe4" [4:8: content]\n' 42 | 43 | 44 | def main(): 45 | css = ''' 46 | /* some umlauts äöü and EURO sign € */ 47 | a:before { 48 | content: "ä"; 49 | }''' 50 | 51 | p = CSSParser() 52 | sheet = p.parseString(css) 53 | 54 | print( 55 | """cssText in different encodings, depending on the console some 56 | chars may look broken but are actually not""" 57 | ) 58 | print() 59 | 60 | sheet.encoding = 'ascii' 61 | print(sheet.cssText) 62 | print() 63 | 64 | sheet.encoding = 'iso-8859-1' 65 | print(sheet.cssText) 66 | print() 67 | 68 | sheet.encoding = 'iso-8859-15' 69 | print(sheet.cssText) 70 | print() 71 | 72 | sheet.encoding = 'utf-8' 73 | print(sheet.cssText) 74 | print() 75 | 76 | # results in default UTF-8 encoding without @charset rule 77 | sheet.encoding = None 78 | print(sheet.cssText) 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /examples/customlog.py: -------------------------------------------------------------------------------- 1 | import io 2 | import logging 3 | 4 | import cssutils 5 | 6 | EXPOUT = "" 7 | EXPERR = """Property: Unknown Property name. [1:5: x] 8 | HTTPError opening url=http://cthedot.de/x: 404 Not Found 9 | CSSImportRule: While processing imported style sheet href=http://cthedot.de/x: IOError('Cannot read Stylesheet.',) 10 | CSSStylesheet: CSSImportRule not allowed here. [1:13: @import] 11 | """ 12 | 13 | 14 | def main(): 15 | mylog = io.StringIO() 16 | h = logging.StreamHandler(mylog) 17 | h.setFormatter(logging.Formatter('%(levelname)s %(message)s')) 18 | cssutils.log.setLevel(logging.INFO) 19 | 20 | cssutils.parseString('a { x: 1; } @import "http://cthedot.de/x";') 21 | 22 | cssutils.log.removeHandler(h) 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /examples/imports.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | import cssutils 4 | 5 | EXPOUT = '''@charset "iso-8859-1"; 6 | @import "1inherit-iso.css"; 7 | 8 | ############################## 9 | 1inherit-iso.css 10 | ############################## 11 | @charset "iso-8859-1"; 12 | @import "2inherit-iso.css"; 13 | /* 1 inherited encoding iso-8859-1 */ 14 | 15 | ==================== 16 | 2inherit-iso.css 17 | ==================== 18 | @charset "iso-8859-1"; 19 | /* 2 inherited encoding iso-8859-1 */ 20 | 21 | 22 | 23 | ''' 24 | EXPERR = '' 25 | 26 | 27 | def main(): 28 | def p(s, len=0): 29 | c = '#=-'[len] * (30 - len * 10) 30 | for r in s.cssRules.rulesOfType(cssutils.css.CSSRule.IMPORT_RULE): 31 | print(c) 32 | print(r.href) 33 | print(c) 34 | print(r.styleSheet.cssText) 35 | print() 36 | p(r.styleSheet, len=len + 1) 37 | print() 38 | 39 | s = cssutils.parseFile(os.path.join('sheets', '1import.css')) 40 | print(s.cssText) 41 | print() 42 | 43 | p(s) 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /examples/minify.py: -------------------------------------------------------------------------------- 1 | EXPOUT = "@variables{c:#0f0}a{color:var(c)}\na{color:#0f0}\n" 2 | EXPERR = 'Property: Found valid "CSS Level 2.1" value: #0f0 [6:9: color]\n' 3 | 4 | 5 | def main(): 6 | import cssutils 7 | import cssutils.script 8 | 9 | css = ''' 10 | @variables { 11 | c: #0f0; 12 | } 13 | a { 14 | color: var(c); 15 | } 16 | ''' 17 | s = cssutils.parseString(css) 18 | 19 | cssutils.ser.prefs.useMinified() 20 | cssutils.ser.prefs.resolveVariables = False 21 | print(s.cssText) 22 | 23 | # reset 24 | cssutils.ser.prefs.resolveVariables = True 25 | print(s.cssText) 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /examples/parse.py: -------------------------------------------------------------------------------- 1 | import xml.dom 2 | 3 | import cssutils 4 | 5 | EXPOUT = '''\n--- source CSS ---\n/* This is a comment */\n body {\n background: white;\n top: red;\n x: 1;\n }\n a { y }\n \n\n--- simple parsing ---\n/* This is a comment */\nbody {\n background: white;\n top: red;\n x: 1\n }\n\n--- CSSParser(raiseExceptions=True) ---\n:::RAISED::: Property: No ":" after name found: y [7:10: ]\n''' 6 | EXPERR = 'Property: Invalid value for "CSS Level 2.1" property: red [4:9: top]\nProperty: Unknown Property name. [5:9: x]\nProperty: No ":" after name found: y [7:10: ]\nProperty: No property value found: y [7:10: ]\nCSSStyleDeclaration: Syntax Error in Property: y \nProperty: Invalid value for "CSS Level 2.1" property: red [4:9: top]\nProperty: Unknown Property name. [5:9: x]\n' 7 | 8 | 9 | def main(): 10 | css = '''/* This is a comment */ 11 | body { 12 | background: white; 13 | top: red; 14 | x: 1; 15 | } 16 | a { y } 17 | ''' 18 | print("\n--- source CSS ---") 19 | print(css) 20 | 21 | print("\n--- simple parsing ---") 22 | c1 = cssutils.parseString(css) 23 | print(c1.cssText) 24 | 25 | print("\n--- CSSParser(raiseExceptions=True) ---") 26 | p = cssutils.CSSParser(raiseExceptions=True) 27 | try: 28 | p.parseString(css) 29 | except xml.dom.DOMException as e: 30 | print(":::RAISED:::", e) 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /examples/properties_with_same_name.py: -------------------------------------------------------------------------------- 1 | """shows CSSStyleDeclaration multivalue property examples""" 2 | 3 | import cssutils 4 | 5 | print("\n**SameNamePropertyList is replaced with style.getProperties() from 0.9.4**") 6 | cssutils.ser.prefs.keepComments = False # remove for now 7 | 8 | cssText = ''' background: white url(paper.png) scroll; /* for all UAs */ 9 | background: white url(ledger.png) fixed; /* for UAs that do fixed backgrounds */ 10 | ''' 11 | print("\n>>> # cssText") 12 | print(cssText) 13 | 14 | 15 | print(">>> style = cssutils.css.CSSStyleDeclaration(cssText=cssText)") 16 | style = cssutils.css.CSSStyleDeclaration(cssText=cssText) 17 | print('>>> print style.cssText') 18 | print(style.cssText) 19 | 20 | print("\n>>> cssutils.ser.prefs.keepAllProperties = True # output all values") 21 | cssutils.ser.prefs.keepAllProperties = True # output all values 22 | print('>>> style.cssText # with keepAllProperties==True:') 23 | print(style.cssText) 24 | print() 25 | 26 | print(">>> # NEW METHOD getProperties") 27 | print(">>> proplist = style.getProperties('background', all=True)") 28 | proplist = style.getProperties('background', all=True) 29 | print(">>> proplist\n", proplist) 30 | print(">>> for prop in proplist: print '\\t', prop.value") 31 | for prop in proplist: 32 | print("\t", prop.value) 33 | print() 34 | 35 | print(">>> # overwrite the current property, to overwrite all iterate over proplist") 36 | print(">>> style.setProperty('background', 'red')") 37 | style.setProperty('background', 'red') 38 | print(">>> style.getPropertyValue('background')") 39 | print(style.getPropertyValue('background')) 40 | print(">>> style.cssText") 41 | print(style.cssText) 42 | -------------------------------------------------------------------------------- /examples/selectors_tolower.py: -------------------------------------------------------------------------------- 1 | import cssutils 2 | 3 | EXPOUT = '''--- ORIGINAL ---\n@charset "ascii";\n @namespace PREfix "uri";\n SOME > WeIrD + selector ~ used here {color: green}\n PREfix|name {color: green}\n \n\n--- SELECTORS TO LOWER CASE (does not simply work for PREfix|name!) ---\n--- CHANGE PREFIX (prefix is not really part of selectorText, URI is! ---\n\n@charset "ascii";\n@namespace lower-case_prefix "uri";\nsome > weird + selector ~ used here {\n color: green\n }\nlower-case_prefix|name {\n color: green\n }\n''' 4 | EXPERR = 'Property: Found valid "CSS Level 2.1" value: green [3:46: color]\nProperty: Found valid "CSS Level 2.1" value: green [4:22: color]\n' 5 | 6 | 7 | def main(): 8 | examplecss = """@charset "ascii"; 9 | @namespace PREfix "uri"; 10 | SOME > WeIrD + selector ~ used here {color: green} 11 | PREfix|name {color: green} 12 | """ 13 | 14 | import logging 15 | 16 | sheet = cssutils.CSSParser(loglevel=logging.DEBUG).parseString(examplecss) 17 | 18 | print("--- ORIGINAL ---") 19 | print(examplecss) 20 | print() 21 | 22 | print("--- SELECTORS TO LOWER CASE (does not simply work for PREfix|name!) ---") 23 | sheet.cssRules[2].selectorText = sheet.cssRules[2].selectorText.lower() 24 | 25 | print("--- CHANGE PREFIX (prefix is not really part of selectorText, URI is! ---") 26 | sheet.cssRules[1].prefix = 'lower-case_prefix' 27 | 28 | print() 29 | print(sheet.cssText) 30 | 31 | 32 | if __name__ == '__main__': 33 | main() 34 | -------------------------------------------------------------------------------- /examples/serialize.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import cssutils 4 | 5 | cssutils.log.setLevel(logging.FATAL) 6 | 7 | 8 | css = r'''@import "example.css"; 9 | a { 10 | color: blue !important; 11 | c\olor: green !important; 12 | c\olor: pink; 13 | color: red; 14 | }''' 15 | 16 | sheet = cssutils.parseString(css) 17 | print("\nORIGINAL CSS:") 18 | print(css) 19 | print("------------") 20 | 21 | print(repr(cssutils.ser.prefs)) 22 | 23 | print("\nCSS Serialized") 24 | print(sheet.cssText) 25 | 26 | print("\nCSS Serialized with ``keepAllProperties`` = False") 27 | cssutils.ser.prefs.keepAllProperties = False 28 | print(sheet.cssText) 29 | 30 | print("\nCSS Serialized with ``defaultPropertyName`` = True") 31 | cssutils.ser.prefs.defaultPropertyName = True 32 | print(sheet.cssText) 33 | 34 | print("\nCSS Serialized with ``defaultPropertyName`` = False") 35 | cssutils.ser.prefs.defaultPropertyName = False 36 | print(sheet.cssText) 37 | 38 | 39 | print('\nBUT: Reading value programmatic uses normalized property') 40 | print(' name and results in actual value only:') 41 | print('\t.getPropertyValue("color") ==', end=' ') 42 | print(sheet.cssRules[1].style.getPropertyValue('color')) 43 | print('\t.getPropertyValue("c\\olor") ==', end=' ') 44 | print(sheet.cssRules[1].style.getPropertyValue(r'c\olor')) 45 | print('\t.getPropertyValue("c\\o\\l\\o\\r") ==', end=' ') 46 | print(sheet.cssRules[1].style.getPropertyValue('c\\o\\l\\o\\r')) 47 | print() 48 | 49 | print( 50 | '\nCSS Serialized with indent = 2*" ", importHrefFormat="string", lineNumbers=True' 51 | ) 52 | cssutils.ser.prefs.indent = 2 * ' ' 53 | # used to set indentation string, default is 4*' ' 54 | cssutils.ser.prefs.importHrefFormat = 'string' 55 | # or 'uri', defaults to the format used in parsed stylesheet 56 | cssutils.ser.prefs.lineNumbers = True 57 | print(sheet.cssText) 58 | 59 | print('\nCSS Serialized with useMinified()') 60 | cssutils.ser.prefs.useMinified() 61 | print(sheet.cssText) 62 | 63 | # OUTPUTS 64 | # 1: @import "example.css"; 65 | # 2: body { 66 | # 3: color: red 67 | # 4: } 68 | -------------------------------------------------------------------------------- /examples/styledeclaration.py: -------------------------------------------------------------------------------- 1 | import cssutils 2 | 3 | 4 | def show(style): 5 | print("style.length ==", style.length) 6 | print("style.item(0) ==", style.item(0)) 7 | print("style.item(1) ==", style.item(1)) 8 | print("style.getProperties('color', all=True) == [") 9 | for x in style.getProperties('color', all=True): 10 | print("\t", x.cssText) 11 | print("\t]") 12 | print("style.getPropertyValue('color') ==", style.getPropertyValue('color'), '\n') 13 | 14 | 15 | styledeclaration = r''' 16 | x:1; 17 | color: yellow; 18 | color: red; 19 | c\olor: green !important; 20 | c\olor: blue; 21 | FONT-FAMILY: serif; 22 | ''' 23 | print("\nGiven styledeclaration:") 24 | print(styledeclaration) 25 | print("------------") 26 | 27 | print("setting cssText") 28 | style = cssutils.css.CSSStyleDeclaration(cssText=styledeclaration) 29 | show(style) 30 | 31 | print("------------") 32 | 33 | # overwrite in any case 34 | print("style.setProperty('color', 'yellow','!important')") 35 | style.setProperty('color', 'yellow', '!important') 36 | show(style) 37 | 38 | # overwrite in any case, even !important 39 | print("style.setProperty('color', 'red')") 40 | style.setProperty('color', 'red') 41 | show(style) 42 | 43 | print("------------") 44 | 45 | # overwrite in any case, even !important 46 | print("style.setProperty('color', 'green', '!important')") 47 | style.setProperty('color', 'green', '!important') 48 | show(style) 49 | 50 | # overwrite in any case, even !important 51 | print("style.setProperty('color', 'blue')") 52 | style.setProperty('color', 'blue') 53 | show(style) 54 | -------------------------------------------------------------------------------- /examples/testutil.py: -------------------------------------------------------------------------------- 1 | """test utils for tests of examples 2 | 3 | A module to test must have: 4 | 5 | - main(): a method 6 | - EXPOUT: string which contains expected output 7 | - EXPERR: string which contains expected error output 8 | """ 9 | 10 | __all__ = ['TestUtil'] 11 | 12 | import io 13 | import logging 14 | import os 15 | import sys 16 | 17 | import cssutils 18 | 19 | 20 | class OutReplacement: 21 | "io.StringIO does not work somehow?!" 22 | 23 | def __init__(self): 24 | self.t = '' 25 | 26 | def write(self, t): 27 | if t.startswith('b') and t.endswith("'"): 28 | t = t[2:-1] 29 | 30 | self.t += t 31 | 32 | def getvalue(self): 33 | self.t = self.t.replace('\\n', '\n').replace('\\\\', '\\') 34 | return self.t 35 | 36 | 37 | class TestUtil: 38 | def __init__(self): 39 | "init out and err to catch both" 40 | self._out = OutReplacement() # StringIO() 41 | sys.stdout = self._out 42 | 43 | self._err = io.StringIO() 44 | hdlr = logging.StreamHandler(self._err) 45 | cssutils.log._log.addHandler(hdlr) 46 | 47 | def end(self, expo, expe): 48 | "return out, err" 49 | sys.stdout = sys.__stdout__ 50 | 51 | out = self._out.getvalue() 52 | err = self._err.getvalue() 53 | 54 | ok = out == expo and err == expe 55 | if not ok: 56 | print() 57 | if out != expo: 58 | print(f'### out:\n{out!r}\n### != expout:\n{expo!r}\n') 59 | else: 60 | print(f'### err:\n{err!r}\n### != experr:\n{expe!r}\n') 61 | return ok 62 | 63 | 64 | modules = 0 65 | errors = 0 66 | 67 | 68 | def mod(module): 69 | global modules, errors 70 | modules += 1 71 | t = TestUtil() 72 | module.main() 73 | ok = t.end(module.EXPOUT, module.EXPERR) 74 | if not ok: 75 | errors += 1 76 | print('---', ok, ':', os.path.basename(module.__file__)) 77 | print() 78 | 79 | 80 | def main(): 81 | global modules, errors 82 | 83 | import build 84 | 85 | mod(build) 86 | import customlog 87 | 88 | mod(customlog) 89 | import imports 90 | 91 | mod(imports) 92 | import parse 93 | 94 | mod(parse) 95 | import selectors_tolower 96 | 97 | mod(selectors_tolower) 98 | 99 | import cssencodings 100 | 101 | mod(cssencodings) 102 | 103 | import minify 104 | 105 | mod(minify) 106 | 107 | print() 108 | print(80 * '-') 109 | print('Ran %i tests (%i errors).' % (modules, errors)) 110 | print('doctests do not work in Python 3 (yet?)!') 111 | 112 | 113 | if __name__ == '__main__': 114 | main() 115 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | # Is the project well-typed? 3 | strict = False 4 | 5 | # Early opt-in even when strict = False 6 | warn_unused_ignores = True 7 | warn_redundant_casts = True 8 | enable_error_code = ignore-without-code 9 | 10 | # Support namespace packages per https://github.com/python/mypy/issues/14057 11 | explicit_package_bases = True 12 | 13 | disable_error_code = 14 | # Disable due to many false positives 15 | overload-overlap, 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=77", 4 | "setuptools_scm[toml]>=3.4.1", 5 | # jaraco/skeleton#174 6 | "coherent.licensed", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [project] 11 | name = "cssutils" 12 | authors = [ 13 | { name = "Christof Hoeke", email = "c@cthedot.de" }, 14 | ] 15 | maintainers = [ 16 | { name = "Jason R. Coombs", email = "jaraco@jaraco.com" }, 17 | ] 18 | description = "A CSS Cascading Style Sheets library for Python" 19 | readme = "README.rst" 20 | classifiers = [ 21 | "Development Status :: 5 - Production/Stable", 22 | "Intended Audience :: Developers", 23 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", 24 | "Programming Language :: Python :: 3", 25 | "Programming Language :: Python :: 3 :: Only", 26 | "Topic :: Internet", 27 | "Topic :: Software Development :: Libraries :: Python Modules", 28 | "Topic :: Text Processing :: Markup :: HTML", 29 | ] 30 | requires-python = ">=3.9" 31 | license = "LGPL-3.0-or-later" 32 | dependencies = [ 33 | "more_itertools", 34 | ] 35 | dynamic = ["version"] 36 | keywords = ["CSS", "Cascading Style Sheets", "CSSParser", "DOM Level 2 Stylesheets", "DOM Level 2 CSS"] 37 | 38 | [project.urls] 39 | Source = "https://github.com/jaraco/cssutils" 40 | 41 | [project.optional-dependencies] 42 | test = [ 43 | # upstream 44 | "pytest >= 6, != 8.1.*", 45 | 46 | # local 47 | 'lxml; python_version < "3.11"', # workaround for #25 48 | "cssselect", 49 | 'importlib_resources; python_version < "3.9"', 50 | "jaraco.test >= 5.1", 51 | ] 52 | 53 | doc = [ 54 | # upstream 55 | "sphinx >= 3.5", 56 | "jaraco.packaging >= 9.3", 57 | "rst.linker >= 1.9", 58 | "furo", 59 | "sphinx-lint", 60 | 61 | # tidelift 62 | "jaraco.tidelift >= 1.4", 63 | 64 | # local 65 | ] 66 | 67 | check = [ 68 | "pytest-checkdocs >= 2.4", 69 | "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", 70 | ] 71 | 72 | cover = [ 73 | "pytest-cov", 74 | ] 75 | 76 | enabler = [ 77 | "pytest-enabler >= 2.2", 78 | ] 79 | 80 | type = [ 81 | # upstream 82 | "pytest-mypy", 83 | 84 | # local 85 | ] 86 | 87 | 88 | [project.scripts] 89 | csscapture = "cssutils.scripts.csscapture:main" 90 | csscombine = "cssutils.scripts.csscombine:main" 91 | cssparse = "cssutils.scripts.cssparse:main" 92 | 93 | [tool.setuptools.packages.find] 94 | exclude = [ 95 | # duplicate exclusions for pypa/setuptools#2688 96 | "docs", 97 | "docs.*", 98 | "examples*", 99 | "sheets", 100 | "sheets.*", 101 | "tools*", 102 | ] 103 | namespaces = true 104 | 105 | 106 | [tool.setuptools_scm] 107 | 108 | 109 | [tool.pytest-enabler.mypy] 110 | # Disabled due to jaraco/skeleton#143 111 | # Also disabled for cssutils 112 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs=dist build .tox .eggs 3 | addopts= 4 | --doctest-modules 5 | --import-mode importlib 6 | consider_namespace_packages=true 7 | filterwarnings= 8 | ## upstream 9 | 10 | # Ensure ResourceWarnings are emitted 11 | default::ResourceWarning 12 | 13 | # realpython/pytest-mypy#152 14 | ignore:'encoding' argument not specified::pytest_mypy 15 | 16 | # python/cpython#100750 17 | ignore:'encoding' argument not specified::platform 18 | 19 | # pypa/build#615 20 | ignore:'encoding' argument not specified::build.env 21 | 22 | # dateutil/dateutil#1284 23 | ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz 24 | 25 | ## end upstream 26 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | [lint] 2 | extend-select = [ 3 | # upstream 4 | 5 | "C901", # complex-structure 6 | "I", # isort 7 | "PERF401", # manual-list-comprehension 8 | 9 | # Ensure modern type annotation syntax and best practices 10 | # Not including those covered by type-checkers or exclusive to Python 3.11+ 11 | "FA", # flake8-future-annotations 12 | "F404", # late-future-import 13 | "PYI", # flake8-pyi 14 | "UP006", # non-pep585-annotation 15 | "UP007", # non-pep604-annotation 16 | "UP010", # unnecessary-future-import 17 | "UP035", # deprecated-import 18 | "UP037", # quoted-annotation 19 | "UP043", # unnecessary-default-type-args 20 | 21 | # local 22 | ] 23 | ignore = [ 24 | # upstream 25 | 26 | # Typeshed rejects complex or non-literal defaults for maintenance and testing reasons, 27 | # irrelevant to this project. 28 | "PYI011", # typed-argument-default-in-stub 29 | # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules 30 | "W191", 31 | "E111", 32 | "E114", 33 | "E117", 34 | "D206", 35 | "D300", 36 | "Q000", 37 | "Q001", 38 | "Q002", 39 | "Q003", 40 | "COM812", 41 | "COM819", 42 | 43 | # local 44 | "E501", # many lines are too long 45 | ] 46 | 47 | [format] 48 | # Enable preview to get hugged parenthesis unwrapping and other nice surprises 49 | # See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 50 | preview = true 51 | # https://docs.astral.sh/ruff/settings/#format_quote-style 52 | quote-style = "preserve" 53 | -------------------------------------------------------------------------------- /sheets/1.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /*äöüß*/ 3 | /* κουρος */ 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sheets/1ascii.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | @import "1inherit-ascii.css"; 3 | @import "1utf.css"; 4 | -------------------------------------------------------------------------------- /sheets/1import.css: -------------------------------------------------------------------------------- 1 | @charset "iso-8859-1"; 2 | @import "1inherit-iso.css"; 3 | 4 | -------------------------------------------------------------------------------- /sheets/1inherit-ascii.css: -------------------------------------------------------------------------------- 1 | /* inherited encoding ascii */ 2 | -------------------------------------------------------------------------------- /sheets/1inherit-iso.css: -------------------------------------------------------------------------------- 1 | @import "2inherit-iso.css"; 2 | /* 1 inherited encoding iso-8859-1 */ 3 | -------------------------------------------------------------------------------- /sheets/1inherit-utf8.css: -------------------------------------------------------------------------------- 1 | /* inherit encoding utf-8 */ 2 | -------------------------------------------------------------------------------- /sheets/1utf.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import "1inherit-utf8.css"; 3 | /* € */ 4 | 5 | -------------------------------------------------------------------------------- /sheets/2inherit-iso.css: -------------------------------------------------------------------------------- 1 | /* 2 inherited encoding iso-8859-1 */ 2 | -------------------------------------------------------------------------------- /sheets/2resolve.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | @namespace "x"; 3 | -------------------------------------------------------------------------------- /sheets/all.css: -------------------------------------------------------------------------------- 1 | /* this sheet should be used in the HTML, deploy.py does resolve 2 | * all imports so that only a single CSS is deployed to cut down 3 | * the number of HTTP requests 4 | */ 5 | @import url(1.css); 6 | @import url(atrule.css); 7 | @import url(simple.css); 8 | @namespace a 'x'; -------------------------------------------------------------------------------- /sheets/atrule.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @three-dee { 3 | @background-lighting { 4 | azimuth: 30deg; 5 | elevation: 190deg; 6 | } 7 | H1 { color: red } 8 | } 9 | @page { 10 | foo: bar 11 | } 12 | -------------------------------------------------------------------------------- /sheets/bad.css: -------------------------------------------------------------------------------- 1 | P { color: yellow; background-color: blue; 1rogue: catch me if you can } 2 | -------------------------------------------------------------------------------- /sheets/basic.css: -------------------------------------------------------------------------------- 1 | { foo: 1.5; bogus: 3, 2, 1; bar-color: #0FEED0; background: #abc; foreground: rgb( 10, 20, 30 ) } 2 | -------------------------------------------------------------------------------- /sheets/cases.css: -------------------------------------------------------------------------------- 1 | @import url("a"); 2 | b{color:red} 3 | a{float:left;bogus:foo(a, url(a))} 4 | b{color:red} 5 | -------------------------------------------------------------------------------- /sheets/csscombine-1.css: -------------------------------------------------------------------------------- 1 | @charset "iso-8859-1"; 2 | /* combined sheet 1 */ 3 | @namespace s1 "uri"; 4 | s1|sheet-1 { 5 | top: 1px 6 | } -------------------------------------------------------------------------------- /sheets/csscombine-2.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; 2 | /* combined sheet 2 */ 3 | @import "1.css"; 4 | @namespace s2 "uri"; 5 | s2|sheet-2 { 6 | top: 2px; 7 | } -------------------------------------------------------------------------------- /sheets/csscombine-proxy.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* proxy sheet were imported sheets should be combined */ 3 | /* non-ascii chars: öäü */ 4 | @import "csscombine-1.css"; 5 | @import url(csscombine-2.css); 6 | @namespace other "other"; 7 | proxy { top: 3px } 8 | -------------------------------------------------------------------------------- /sheets/cthedot_default.css: -------------------------------------------------------------------------------- 1 | /* default stylesheet for all variations */ 2 | .jsonly { display: none; } 3 | 4 | .stylenav { 5 | text-align: right; 6 | margin-top: -1em; 7 | } 8 | 9 | html, body { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | body { 14 | font: normal 90%/1.1 Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; 15 | color: #fff; 16 | background-color: #344; 17 | } 18 | 19 | h1, h2, h3, h4, h5, h6 { 20 | font: normal 2.4em Verdana, "Lucida Grande", Arial, Helvetica, sans-serif; 21 | } 22 | h1, h2, h3, caption { 23 | color: #a00; 24 | margin: 0.3em -0.4em 0.5em -0.5em; 25 | } 26 | h2, h3, caption { 27 | margin: 0.3em -1.3em 0.3em 0; 28 | } 29 | h2 { 30 | font-size: 1.5em; 31 | border-right: 1.3em solid #677; 32 | border-bottom: 1px dotted #677; 33 | padding-left: 0.1em; 34 | padding-bottom: 0.1em; 35 | margin-top: 2.1em; 36 | margin-bottom: 0.4em; 37 | } 38 | h3 { 39 | font-size: 0.9em; 40 | font-weight: bold; 41 | text-transform: uppercase; 42 | margin-top: 1.2em; 43 | margin-bottom: 0.1em; 44 | } 45 | caption { 46 | font-size: 1.05em; 47 | text-align: left; 48 | margin-bottom: 0.2em; 49 | } 50 | h4 { 51 | font-size: 0.9em; 52 | font-weight: bold; 53 | margin: 1.2em 0 0; 54 | } 55 | h5, h6 { 56 | font-size: 1em; 57 | margin: 0; 58 | } 59 | h6 { 60 | font-size: 0.9em; 61 | } 62 | 63 | p, ol, ul, dl { 64 | line-height: 1.3; 65 | margin-top: 0; 66 | margin-bottom: 1em; 67 | } 68 | ul { 69 | list-style-type: square; 70 | } 71 | ul.code { 72 | line-height: 1.3; 73 | } 74 | li, dd { 75 | margin-bottom: 0.3em; 76 | } 77 | dt { 78 | font-weight: bold; 79 | } 80 | pre, code { 81 | color: #00a; 82 | line-height: 1.4; 83 | font-size: 1.1em; 84 | } 85 | table code, table pre { 86 | font-size: 1.3em; 87 | } 88 | .deprecated { 89 | color: #888; 90 | } 91 | table { 92 | font-size: 0.9em; 93 | border-collapse: collapse; 94 | } 95 | tr { 96 | vertical-align: top; 97 | line-height: 1.3; 98 | } 99 | td, th { 100 | text-align: left; 101 | padding: 0.4em 0.5em; 102 | border-bottom: 1px dotted #667; 103 | } 104 | td.center { 105 | text-align: center; 106 | } 107 | 108 | tr:hover, li:hover { 109 | background-color: #f8f8f8; 110 | } 111 | 112 | 113 | acronym, .explain { 114 | border-bottom: 1px dotted #344; 115 | } 116 | a { 117 | text-decoration: none; 118 | color: #fff; 119 | border-bottom: 1px solid #aaa; 120 | } 121 | #main a { 122 | color: #a00; 123 | } 124 | a:visited { 125 | color: #eee; 126 | } 127 | #main a:visited { 128 | color: #344; 129 | } 130 | a:hover { 131 | text-decoration: underline; 132 | color: #fff; 133 | } 134 | #main a:hover { 135 | background-color: #f5f8ff; 136 | } 137 | #main a:active { 138 | color: #fff; 139 | background-color: #abb; 140 | } 141 | 142 | label { 143 | display: block; 144 | padding: 0.5em 0 0.1em; 145 | } 146 | input, textarea { 147 | font: bold 1em Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; 148 | background-color: #eee; 149 | width: 100%; 150 | border: 1px inset; 151 | } 152 | #submit, textarea { 153 | margin-bottom: 1.5em; 154 | } 155 | #submit { 156 | font-weight: bold; 157 | color: #00a; 158 | background-color: #fff; 159 | border: 1px outset; 160 | margin-top: 2em; 161 | } 162 | input:focus, input:hover, textarea:focus, textarea:hover { 163 | font-weight: bold; 164 | background-color: #fff; 165 | border-style: solid; 166 | } 167 | #submit:hover, #submit:focus { 168 | background-color: #eee; 169 | border-style: solid; 170 | } 171 | #submit:active { 172 | border-style: inset; 173 | } 174 | 175 | #header { 176 | padding: 1.1em 5% 0; 177 | padding: 40px 5% 0; 178 | color: #334; 179 | background: #fff url(/img/header_r.jpg) no-repeat; 180 | border-bottom: 1px solid #344; 181 | height: 90px; 182 | he\ight: 50px; 183 | } 184 | #header dt, #header p { 185 | font-family: Arial, Helvetica, sans-serif; 186 | letter-spacing: 0.3em; 187 | } 188 | #header a { 189 | color: #334; 190 | } 191 | #main .nav { 192 | padding-bottom: 0.5em; 193 | border-bottom: 3px solid #eee; 194 | margin-bottom: 1em; 195 | margin-left: -8%; 196 | } 197 | .nav dt , .nav dd, .nav dd ul, .nav li { 198 | display: inline; 199 | } 200 | .nav dt { 201 | font-weight: bold; 202 | } 203 | .nav li { 204 | font-weight: bold; 205 | padding-right: 0.5em; 206 | } 207 | .nav a { 208 | font-weight: normal; 209 | } 210 | #footer { 211 | padding: 1em 5% 1em 10%; 212 | border-top: 3px double #fff; 213 | text-align: right; 214 | } 215 | #main { 216 | color: #000; 217 | background-color: #fff; 218 | padding: 1em 26% 1em 12%; 219 | } 220 | -------------------------------------------------------------------------------- /sheets/default_html4.css: -------------------------------------------------------------------------------- 1 | /* default HTML 4 CSSStyleSheet 2 | from http://www.w3.org/TR/2004/CR-CSS21-20040225/sample.html */ 3 | html, address, blockquote, body, dd, div, dl, dt, fieldset, form, frame, frameset, h1, h2, h3, h4, h5, h6, noframes, ol, p, ul, center, dir, hr, menu, pre { 4 | display: block 5 | } 6 | li { 7 | display: list-item 8 | } 9 | head { 10 | display: none 11 | } 12 | table { 13 | display: table 14 | } 15 | tr { 16 | display: table-row 17 | } 18 | thead { 19 | display: table-header-group 20 | } 21 | tbody { 22 | display: table-row-group 23 | } 24 | tfoot { 25 | display: table-footer-group 26 | } 27 | col { 28 | display: table-column 29 | } 30 | colgroup { 31 | display: table-column-group 32 | } 33 | td, th { 34 | display: table-cell 35 | } 36 | caption { 37 | display: table-caption 38 | } 39 | th { 40 | font-weight: bolder; 41 | text-align: center 42 | } 43 | caption { 44 | text-align: center 45 | } 46 | body { 47 | margin: 8px; 48 | line-height: 1.12 49 | } 50 | h1 { 51 | font-size: 2em; 52 | margin: .67em 0 53 | } 54 | h2 { 55 | font-size: 1.5em; 56 | margin: .75em 0 57 | } 58 | h3 { 59 | font-size: 1.17em; 60 | margin: .83em 0 61 | } 62 | h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu { 63 | margin: 1.12em 0 64 | } 65 | h5 { 66 | font-size: .83em; 67 | margin: 1.5em 0 68 | } 69 | h6 { 70 | font-size: .75em; 71 | margin: 1.67em 0 72 | } 73 | h1, h2, h3, h4, h5, h6, b, strong { 74 | font-weight: bolder 75 | } 76 | blockquote { 77 | margin-left: 40px; 78 | margin-right: 40px 79 | } 80 | i, cite, em, var, address { 81 | font-style: italic 82 | } 83 | pre, tt, code, kbd, samp { 84 | font-family: monospace 85 | } 86 | pre { 87 | white-space: pre 88 | } 89 | button, textarea, input, object, select { 90 | display: inline-block 91 | } 92 | big { 93 | font-size: 1.17em 94 | } 95 | small, sub, sup { 96 | font-size: .83em 97 | } 98 | sub { 99 | vertical-align: sub 100 | } 101 | sup { 102 | vertical-align: super 103 | } 104 | table { 105 | border-spacing: 2px; 106 | } 107 | thead, tbody, tfoot { 108 | vertical-align: middle 109 | } 110 | td, th { 111 | vertical-align: inherit 112 | } 113 | s, strike, del { 114 | text-decoration: line-through 115 | } 116 | hr { 117 | border: 1px inset 118 | } 119 | ol, ul, dir, menu, dd { 120 | margin-left: 40px 121 | } 122 | ol { 123 | list-style-type: decimal 124 | } 125 | ol ul, ul ol, ul ul, ol ol { 126 | margin-top: 0; 127 | margin-bottom: 0 128 | } 129 | u, ins { 130 | text-decoration: underline 131 | } 132 | br:before { 133 | content: "\A" 134 | 135 | } 136 | :before, :after { 137 | white-space: pre-line 138 | } 139 | center { 140 | text-align: center 141 | } 142 | abbr, acronym { 143 | font-variant: small-caps; 144 | letter-spacing: 0.1em 145 | } 146 | :link, :visited { 147 | text-decoration: underline 148 | } 149 | :focus { 150 | outline: thin dotted invert 151 | } 152 | /* Begin bidirectionality settings (do not change) */ 153 | BDO[DIR="ltr"] { 154 | direction: ltr; 155 | unicode-bidi: bidi-override 156 | } 157 | BDO[DIR="rtl"] { 158 | direction: rtl; 159 | unicode-bidi: bidi-override 160 | } 161 | *[DIR="ltr"] { 162 | direction: ltr; 163 | unicode-bidi: embed 164 | } 165 | *[DIR="rtl"] { 166 | direction: rtl; 167 | unicode-bidi: embed 168 | } 169 | @media print { 170 | h1 { 171 | page-break-before: always 172 | } 173 | h1, h2, h3, h4, h5, h6 { 174 | page-break-after: avoid 175 | } 176 | ul, ol, dl { 177 | page-break-before: avoid 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /sheets/hacks.css: -------------------------------------------------------------------------------- 1 | .normal {background-color: gray;} 2 | .backslash {bac\kground-color: gray;} 3 | html>body .childselector {background-color: green;} 4 | html>/**/body .childselector-with-comment {background-color: orange} 5 | .colon-default2, x:default 6 | html>/**/body .colon-default, x:default 7 | *:not(hr) .not-hr {background-color: red;} 8 | * html .ie-only-1 {background-color: blue;} 9 | *+html .ie-only-2 {background-color: blue;} 10 | *+html .ie-only-3 {background-color: blue;} 11 | html:first-child .first-child-2 {background-color: red;} 12 | /* does not work as CSSUnknownRule read: 13 | @mediaall { .mediaall { background-color: red; }} 14 | */ 15 | .not-class:not([class='XXX']) {background-color: red;} 16 | @media all and (min-width: 0) { .mediaquery { background-color: red;} } 17 | 18 | -------------------------------------------------------------------------------- /sheets/html.css: -------------------------------------------------------------------------------- 1 | body { color: red } 2 | 3 | body { color: blue } 4 | body { color: pink } 5 | 6 | body { color: green } 7 | -------------------------------------------------------------------------------- /sheets/html20.css: -------------------------------------------------------------------------------- 1 | 2 | BODY { 3 | margin: 1em; 4 | font-family: serif; 5 | line-height: 1.1; 6 | background: white; 7 | color: black 8 | } 9 | 10 | H1, H2, H3, H4, H5, H6, P, UL, OL, DIR, MENU, DIV, 11 | DT, DD, ADDRESS, BLOCKQUOTE, PRE, BR, HR { display: block } 12 | 13 | B, STRONG, I, EM, CITE, VAR, TT, CODE, KBD, SAMP, 14 | IMG, SPAN { display: inline } 15 | 16 | LI { display: list-item } 17 | 18 | H1, H2, H3, H4 { margin-top: 1em; margin-bottom: 1em } 19 | H5, H6 { margin-top: 1em } 20 | H1 { text-align: center } 21 | H1, H2, H4, H6 { font-weight: bold } 22 | H3, H5 { font-style: italic } 23 | 24 | H1 { font-size: xx-large } 25 | H2 { font-size: x-large } 26 | H3 { font-size: large } 27 | 28 | B, STRONG { font-weight: bolder } /* relative to the parent */ 29 | I, CITE, EM, VAR, ADDRESS, BLOCKQUOTE { font-style: italic } 30 | PRE, TT, CODE, KBD, SAMP { font-family: monospace } 31 | 32 | PRE { white-space: pre } 33 | 34 | ADDRESS { margin-left: 3em } 35 | BLOCKQUOTE { margin-left: 3em; margin-right: 3em } 36 | 37 | UL, DIR { list-style: disc } 38 | OL { list-style: decimal } 39 | MENU { margin: 0 } /* tight formatting */ 40 | LI { margin-left: 3em } 41 | 42 | DT { margin-bottom: 0 } 43 | DD { margin-top: 0; margin-left: 3em } 44 | 45 | HR { border-top: solid } /* 'border-bottom' could also have been used */ 46 | 47 | A:link { color: blue } /* unvisited link */ 48 | A:visited { color: red } /* visited links */ 49 | A:active { color: lime } /* active links */ 50 | 51 | /* setting the anchor border around IMG elements 52 | requires contextual selectors */ 53 | 54 | A:link IMG { border: 2px solid blue } 55 | A:visited IMG { border: 2px solid red } 56 | A:active IMG { border: 2px solid lime } 57 | -------------------------------------------------------------------------------- /sheets/html40.css: -------------------------------------------------------------------------------- 1 | @charset "US-ASCII"; 2 | 3 | ADDRESS, 4 | BLOCKQUOTE, 5 | BODY, DD, DIV, 6 | DL, DT, 7 | FIELDSET, FORM, 8 | FRAME, FRAMESET, 9 | H1, H2, H3, H4, 10 | H5, H6, IFRAME, 11 | NOFRAMES, 12 | OBJECT, OL, P, 13 | UL, APPLET, 14 | CENTER, DIR, 15 | HR, MENU, PRE { display: block } 16 | LI { display: list-item } 17 | HEAD { display: none } 18 | TABLE { display: table } 19 | TR { display: table-row } 20 | THEAD { display: table-header-group } 21 | TBODY { display: table-row-group } 22 | TFOOT { display: table-footer-group } 23 | COL { display: table-column } 24 | COLGROUP { display: table-column-group } 25 | TD, TH { display: table-cell } 26 | CAPTION { display: table-caption } 27 | TH { font-weight: bolder; text-align: center } 28 | CAPTION { text-align: center } 29 | BODY { padding: 8px; line-height: 1.33 } 30 | H1 { font-size: 2em; margin: .67em 0 } 31 | H2 { font-size: 1.5em; margin: .83em 0 } 32 | H3 { font-size: 1.17em; margin: 1em 0 } 33 | H4, P, 34 | BLOCKQUOTE, UL, 35 | FIELDSET, FORM, 36 | OL, DL, DIR, 37 | MENU { margin: 1.33em 0 } 38 | H5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0 } 39 | H6 { font-size: .67em; margin: 2.33em 0 } 40 | H1, H2, H3, H4, 41 | H5, H6, B, 42 | STRONG { font-weight: bolder } 43 | BLOCKQUOTE { margin-left: 40px; margin-right: 40px } 44 | I, CITE, EM, 45 | VAR, ADDRESS { font-style: italic } 46 | PRE, TT, CODE, 47 | KBD, SAMP { font-family: monospace } 48 | PRE { white-space: pre } 49 | BIG { font-size: 1.17em } 50 | SMALL, SUB, SUP { font-size: .83em } 51 | SUB { vertical-align: sub } 52 | SUP { vertical-align: super } 53 | S, STRIKE, DEL { text-decoration: line-through } 54 | HR { border: 1px inset } 55 | OL, UL, DIR, 56 | MENU, DD { margin-left: 40px } 57 | OL { list-style-type: decimal } 58 | OL UL, UL OL, 59 | UL UL, OL OL { margin-top: 0; margin-bottom: 0 } 60 | U, INS { text-decoration: underline } 61 | CENTER { text-align: center } 62 | BR:before { content: "\A" } 63 | 64 | /* An example of style for HTML 4.0's ABBR/ACRONYM elements */ 65 | 66 | ABBR, ACRONYM { font-variant: small-caps; letter-spacing: 0.1em } 67 | A[href] { text-decoration: underline } 68 | :focus { outline: thin dotted invert } 69 | 70 | 71 | /* Begin bidirectionality settings (do not change) */ 72 | 73 | BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override } 74 | BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override } 75 | 76 | *[DIR="ltr"] { direction: ltr; unicode-bidi: embed } 77 | *[DIR="rtl"] { direction: rtl; unicode-bidi: embed } 78 | 79 | /* Elements that are block-level in HTML4 */ 80 | ADDRESS, BLOCKQUOTE, BODY, DD, DIV, DL, DT, FIELDSET, 81 | FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, IFRAME, 82 | NOSCRIPT, NOFRAMES, OBJECT, OL, P, UL, APPLET, CENTER, 83 | DIR, HR, MENU, PRE, LI, TABLE, TR, THEAD, TBODY, TFOOT, 84 | COL, COLGROUP, TD, TH, CAPTION 85 | { unicode-bidi: embed } 86 | /* End bidi settings */ 87 | 88 | 89 | @media print { 90 | @page { margin: 10% } 91 | H1, H2, H3, 92 | H4, H5, H6 { page-break-after: avoid; page-break-inside: avoid } 93 | BLOCKQUOTE, 94 | PRE { page-break-inside: avoid } 95 | UL, OL, DL { page-break-before: avoid } 96 | } 97 | 98 | @media speech { 99 | H1, H2, H3, 100 | H4, H5, H6 { voice-family: paul, male; stress: 20; richness: 90 } 101 | H1 { pitch: x-low; pitch-range: 90 } 102 | H2 { pitch: x-low; pitch-range: 80 } 103 | H3 { pitch: low; pitch-range: 70 } 104 | H4 { pitch: medium; pitch-range: 60 } 105 | H5 { pitch: medium; pitch-range: 50 } 106 | H6 { pitch: medium; pitch-range: 40 } 107 | LI, DT, DD { pitch: medium; richness: 60 } 108 | DT { stress: 80 } 109 | PRE, CODE, TT { pitch: medium; pitch-range: 0; stress: 0; richness: 80 } 110 | EM { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } 111 | STRONG { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } 112 | DFN { pitch: high; pitch-range: 60; stress: 60 } 113 | S, STRIKE { richness: 0 } 114 | I { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } 115 | B { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } 116 | U { richness: 0 } 117 | A:link { voice-family: harry, male } 118 | A:visited { voice-family: betty, female } 119 | A:active { voice-family: betty, female; pitch-range: 80; pitch: x-high } 120 | } 121 | -------------------------------------------------------------------------------- /sheets/images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/sheets/images/example.gif -------------------------------------------------------------------------------- /sheets/import.css: -------------------------------------------------------------------------------- 1 | @import "import/import2.css"; 2 | .import { 3 | /* ./import.css */ 4 | background-image: url(images/example.gif) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /sheets/import/images2/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/sheets/import/images2/example2.gif -------------------------------------------------------------------------------- /sheets/import/import-impossible.css: -------------------------------------------------------------------------------- 1 | @namespace "y"; 2 | @media tv {} 3 | .import4 { 4 | background: url(images2/example2.gif); 5 | } 6 | -------------------------------------------------------------------------------- /sheets/import/import2.css: -------------------------------------------------------------------------------- 1 | @import "../import3.css"; 2 | @import "import-impossible.css" print; 3 | .import2 { 4 | /* sheets/import2.css */ 5 | background: url(http://example.com/images/example.gif); 6 | background: url(//example.com/images/example.gif); 7 | background: url(/images/example.gif); 8 | background: url(images2/example.gif); 9 | background: url(./images2/example.gif); 10 | background: url(../images/example.gif); 11 | background: url(./../images/example.gif); 12 | } -------------------------------------------------------------------------------- /sheets/import3.css: -------------------------------------------------------------------------------- 1 | /* import3 */ 2 | .import3 { 3 | /* from ./import/../import3.css */ 4 | background: url(images/example3.gif); 5 | background: url(./images/example3.gif); 6 | background: url(import/images2/example2.gif); 7 | background: url(./import/images2/example2.gif); 8 | background: url(import/images2/../../images/example3.gif); 9 | } -------------------------------------------------------------------------------- /sheets/multiple-values.css: -------------------------------------------------------------------------------- 1 | foo { 2 | a: 1, 2 3, bar 4 3 | } 4 | -------------------------------------------------------------------------------- /sheets/page_test.css: -------------------------------------------------------------------------------- 1 | @page pageStyle { size: 21.0cm 29.7cm; } 2 | -------------------------------------------------------------------------------- /sheets/simple.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("fineprint.css") print; 3 | @import url(bogus.css); 4 | @import url( bogus.css ); 5 | 6 | @media screen { 7 | 8 | @three-dee { 9 | @background-lighting { 10 | azimuth: 30deg; 11 | elevation: 190deg; 12 | } 13 | H1 { color: red } 14 | } 15 | 16 | h1 { foo: bar } 17 | h2 { thing: 1; whatsit: 2px } 18 | h3 { foo: 2 ! important } 19 | 20 | } 21 | 22 | h9 { } 23 | 24 | b1 & b2 { } 25 | -------------------------------------------------------------------------------- /sheets/single-color.css: -------------------------------------------------------------------------------- 1 | @media aural { 2 | BLOCKQUOTE:after { content: url("beautiful-music.wav") } 3 | } 4 | a { 5 | content: url("other.wav") 6 | } 7 | -------------------------------------------------------------------------------- /sheets/t-HACKS.css: -------------------------------------------------------------------------------- 1 | /* http://centricle.com/ref/css/filters/?highlight_columns=true */ 2 | @import 'styles1.css'; 3 | @import "styles2.css"; 4 | @import url(styles3.css); 5 | @import url('styles4.css'); 6 | @import url("styles5.css"); 7 | @import "null?\"\{"; 8 | @import "styles6.css"; 9 | 10 | a { 11 | color: red; 12 | voice-family:"\"}\""; 13 | voice-family:inherit; 14 | color: green; 15 | } 16 | b { color: red; 17 | c\olor:green; } 18 | c { color: red; 19 | /*/*/color:green;/* */ } 20 | /* NS4 only, should not work: */ 21 | d { color: green; 22 | /*/*//*/color:red;/* */ } 23 | 24 | e1{content:"\"/*"} 25 | e2{color:green} 26 | /* THIS SHOULD WORK??? */ 27 | /* \*/ 28 | div{color:green} 29 | /* */ 30 | 31 | div#test { } 32 | head:first-child+body div { } 33 | 34 | 35 | @media all{/* rules */} 36 | -------------------------------------------------------------------------------- /sheets/test-unicode.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/sheets/test-unicode.css -------------------------------------------------------------------------------- /sheets/test.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/sheets/test.css -------------------------------------------------------------------------------- /sheets/u_simple.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaraco/cssutils/35b7dbad85b9638f62ae91a8a4879c448766ca13/sheets/u_simple.css -------------------------------------------------------------------------------- /sheets/v_simple.css: -------------------------------------------------------------------------------- 1 | h1 { foo: alpha; bar:beta } 2 | -------------------------------------------------------------------------------- /sheets/var/start.css: -------------------------------------------------------------------------------- 1 | @import "vars.css"; 2 | @import "vars2.css"; 3 | @import "use.css"; 4 | 5 | @variables { 6 | TEST: 1px; 7 | T2: 'T2' 8 | } 9 | 10 | a { 11 | left: var(T2); 12 | } 13 | -------------------------------------------------------------------------------- /sheets/var/use.css: -------------------------------------------------------------------------------- 1 | a { 2 | content: var(TEST) 3 | } 4 | a { 5 | content: var(TEST) 6 | } 7 | a { 8 | content: var(TEST) 9 | } 10 | a { 11 | content: var(TEST) 12 | } 13 | a { 14 | content: var(TEST) 15 | } 16 | a { 17 | content: var(TEST) 18 | } 19 | a { 20 | content: var(TEST) 21 | } 22 | a { 23 | content: var(TEST) 24 | } 25 | a { 26 | content: var(TEST) 27 | } 28 | a { 29 | content: var(TEST) 30 | } 31 | -------------------------------------------------------------------------------- /sheets/var/vars.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | TEST: 1px; 3 | T2: 'T2' 4 | } 5 | -------------------------------------------------------------------------------- /sheets/var/vars2.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | TEST: 'VARS2' 3 | } 4 | -------------------------------------------------------------------------------- /sheets/vars.css: -------------------------------------------------------------------------------- 1 | @import "varsimport.css"; 2 | 3 | @variables { 4 | c2: "c2 own file (OVERWRITTEN)"; 5 | c3: "c1 own file" 6 | } 7 | 8 | a1 { 9 | content: var(c1); 10 | } 11 | a2 { 12 | content: var(c2); 13 | } 14 | a3 { 15 | content: var(c3); 16 | } 17 | -------------------------------------------------------------------------------- /sheets/varsimport.css: -------------------------------------------------------------------------------- 1 | @variables { 2 | c1: "c1 imported"; 3 | c2: "c2 imported (SHOULD BE OVERWRITTEN)"; 4 | } 5 | 6 | .import1 { 7 | content: var(c1); 8 | } 9 | .import2 { 10 | content: var(c2); 11 | } 12 | .import3 { 13 | /* not defined here! */ 14 | content: var(c3); 15 | } 16 | -------------------------------------------------------------------------------- /sheets/yuck.css: -------------------------------------------------------------------------------- 1 | E[class~="hipster"][thing~="bob"]#myid { att1: hi; foo: bar } 2 | E:lang(c)#unique 3 | E.hipster#myid { att1: hi; foo: bar } 4 | -------------------------------------------------------------------------------- /tools/speed.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | do = """ 4 | import cssutils 5 | css = ''' 6 | @font-face { 7 | font-family: 'WebFont'; 8 | src: url('myfont.eot'); /* IE6-8 */ 9 | src: local('xxx'), 10 | url('myfont.woff') format('woff'), /* FF3.6, IE9 */ 11 | url('myfont.ttf') format('truetype'); /* Saf3+,Chrome,FF3.5,Opera10+ */ 12 | } 13 | a { 14 | /**//**//**//**//**//**//**//**/ 15 | /**//**//**//**//**//**//**//**/ 16 | color: red; 17 | display: none; 18 | position: absolute; 19 | left: 1px; 20 | top: 2px; 21 | background: 1px url(x) no-repeat left top; 22 | padding: 1px 1px 2px 5cm; 23 | font: normal 1px/5em Arial, sans-serif; 24 | 25 | } ''' 26 | p = cssutils.CSSParser(parseComments=False) 27 | sheet = p.parseString(10*css) 28 | """ 29 | t = timeit.Timer(do) # outside the try/except 30 | try: 31 | print(t.timeit(20)) # or t.repeat(...) 32 | except Exception: 33 | print(t.print_exc()) 34 | -------------------------------------------------------------------------------- /towncrier.toml: -------------------------------------------------------------------------------- 1 | [tool.towncrier] 2 | title_format = "{version}" 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [testenv] 2 | description = perform primary checks (tests, style, types, coverage) 3 | deps = 4 | setenv = 5 | PYTHONWARNDEFAULTENCODING = 1 6 | commands = 7 | pytest {posargs} 8 | usedevelop = True 9 | extras = 10 | test 11 | check 12 | cover 13 | enabler 14 | type 15 | 16 | [testenv:diffcov] 17 | description = run tests and check that diff from main is covered 18 | deps = 19 | {[testenv]deps} 20 | diff-cover 21 | commands = 22 | pytest {posargs} --cov-report xml 23 | diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html 24 | diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 25 | 26 | [testenv:docs] 27 | description = build the documentation 28 | extras = 29 | doc 30 | test 31 | changedir = docs 32 | commands = 33 | python -m sphinx -W --keep-going . {toxinidir}/build/html 34 | python -m sphinxlint 35 | 36 | [testenv:finalize] 37 | description = assemble changelog and tag a release 38 | skip_install = True 39 | deps = 40 | towncrier 41 | jaraco.develop >= 7.23 42 | pass_env = * 43 | commands = 44 | python -m jaraco.develop.finalize 45 | 46 | 47 | [testenv:release] 48 | description = publish the package to PyPI and GitHub 49 | skip_install = True 50 | deps = 51 | build 52 | twine>=3 53 | jaraco.develop>=7.1 54 | pass_env = 55 | TWINE_PASSWORD 56 | GITHUB_TOKEN 57 | setenv = 58 | TWINE_USERNAME = {env:TWINE_USERNAME:__token__} 59 | commands = 60 | python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" 61 | python -m build 62 | python -m twine upload dist/* 63 | python -m jaraco.develop.create-github-release 64 | --------------------------------------------------------------------------------