Skip to content

Commit 8740497

Browse files
committed
Switch parser backend to tinycss2
1 parent 78ed29c commit 8740497

8 files changed

Lines changed: 26 additions & 49 deletions

File tree

dict2css/__init__.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,15 @@
3131

3232
# stdlib
3333
from io import TextIOBase
34-
from typing import IO, Any, Dict, Mapping, MutableMapping, Sequence, Union, cast
34+
from typing import IO, Any, Dict, Iterable, List, Mapping, MutableMapping, Sequence, Union, cast
3535

3636
# 3rd party
37+
import tinycss2
38+
import tinycss2.ast
3739
from domdf_python_tools.paths import PathPlus
3840
from domdf_python_tools.typing import PathLike
3941
from domdf_python_tools.words import TAB
4042

41-
try:
42-
# 3rd party
43-
import css_parser # type: ignore
44-
except ImportError: # pragma: no cover
45-
import cssutils as css_parser # type: ignore
46-
4743
# this package
4844
from dict2css.helpers import em, px, rem # noqa: F401
4945
from dict2css.serializer import CSSSerializer
@@ -258,44 +254,46 @@ def loads(styles: str) -> MutableMapping[str, MutableMapping[str, Any]]:
258254
:return: The style sheet as a dictionary.
259255
"""
260256

261-
parser = css_parser.CSSParser(validate=False)
262-
stylesheet: css_parser.css.CSSStyleSheet = parser.parseString(styles)
257+
stylesheet = tinycss2.parse_blocks_contents(styles, skip_comments=True, skip_whitespace=True)
263258

264259
styles_dict: MutableMapping[str, MutableMapping[str, Any]] = {}
265260

266-
def parse_style(style: css_parser.css.CSSStyleDeclaration) -> MutableMapping[str, Property]:
261+
def parse_style(style: List[tinycss2.ast.Node]) -> MutableMapping[str, Property]:
267262
style_dict: Dict[str, Property] = {}
268263

269-
prop: css_parser.css.Property
270-
for prop in style.children():
271-
if prop.priority:
272-
style_dict[prop.name] = (prop.value, prop.priority)
264+
prop: Union[tinycss2.ast.ParseError, tinycss2.ast.Declaration]
265+
for prop in tinycss2.parse_declaration_list(style, skip_comments=True, skip_whitespace=True):
266+
if isinstance(prop, tinycss2.ast.ParseError):
267+
raise ValueError(prop)
268+
269+
if prop.important:
270+
style_dict[prop.name.strip()] = (_serialize(prop.value), IMPORTANT)
273271
else:
274-
style_dict[prop.name] = prop.value
272+
style_dict[prop.name.strip()] = _serialize(prop.value)
275273

276274
return style_dict
277275

278-
rule: css_parser.css.CSSRule
279-
for rule in stylesheet.cssRules:
280-
if isinstance(rule, css_parser.css.CSSStyleRule):
281-
styles_dict[rule.selectorText] = parse_style(rule.style)
282-
283-
elif isinstance(rule, css_parser.css.CSSMediaRule):
284-
styles_dict[f"@media {rule.media.mediaText}"] = {}
276+
rule: tinycss2.ast.Node
277+
for rule in stylesheet:
278+
if isinstance(rule, tinycss2.ast.QualifiedRule):
279+
styles_dict[_serialize(rule.prelude)] = parse_style(rule.content)
285280

286-
for child in rule.cssRules:
287-
styles_dict[f"@media {rule.media.mediaText}"][child.selectorText] = parse_style(child.style)
281+
elif isinstance(rule, tinycss2.ast.AtRule):
282+
at_rule_styles = styles_dict[f"@{rule.at_keyword} {_serialize(rule.prelude)}"] = at_rule_styles
288283

289-
elif isinstance(rule, (css_parser.css.CSSComment)): # pragma: no cover
290-
# Ignore these classes
291-
pass
284+
for child in tinycss2.parse_blocks_contents(rule.content, skip_comments=True, skip_whitespace=True):
285+
at_rule_styles[_serialize(child.prelude)] = parse_style(child.content)
292286

293287
else:
294288
raise NotImplementedError(rule)
295289

296290
return styles_dict
297291

298292

293+
def _serialize(nodes: Iterable[tinycss2.ast.Node]):
294+
return tinycss2.serialize(nodes).strip()
295+
296+
299297
def load(fp: Union[PathLike, IO]) -> MutableMapping[str, MutableMapping[str, Any]]:
300298
r"""
301299
Parse a cascading style sheet from the given file and return its dictionary representation.

dict2css/serializer.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,6 @@
3535
# 3rd party
3636
from domdf_python_tools.words import TAB
3737

38-
try:
39-
# 3rd party
40-
import css_parser # type: ignore
41-
except ImportError: # pragma: no cover
42-
# 3rd party
43-
import cssutils as css_parser # type: ignore
44-
4538
__all__ = ["CSSSerializer"]
4639

4740

doc-source/conf.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,5 @@ def setup(app):
101101

102102
nitpicky = True
103103
latex_elements["preamble"] = "\\raggedbottom\n\\widowpenalty10000"
104-
ignore_missing_xrefs = ["^css_parser\\."]
105104
autosummary_widths_builders = ["latex"]
106105
changelog_sections_numbered = False

pyproject.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,6 @@ reformat = true
139139

140140
[tool.snippet-fmt.languages.json]
141141

142-
[tool.dep_checker]
143-
allowed_unused = [ "cssutils",]
144-
145142
[tool.dependency-dash."requirements.txt"]
146143
order = 10
147144

repo_helper.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ classifiers:
4242
sphinx_conf_epilogue:
4343
- nitpicky = True
4444
- latex_elements["preamble"] = "\\raggedbottom\n\\widowpenalty10000"
45-
- ignore_missing_xrefs = ["^css_parser\\."]
4645
- autosummary_widths_builders = ["latex"]
4746
- changelog_sections_numbered = False
4847

@@ -57,6 +56,3 @@ extra_sphinx_extensions:
5756
- sphinx_toolbox_experimental.missing_xref
5857
- sphinx_toolbox_experimental.changelog
5958
- latex_mu
60-
61-
tox_unmanaged:
62-
- testenv

requirements.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
# css-parser>=1.0.6
2-
# Later cssutils polluted by AI and the new licence-breaking chardet
3-
cssutils<=2.11.0,>=2.2.0
41
domdf-python-tools>=2.2.0
2+
tinycss2>=1.2.1

tests/test_dict2css.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import Dict, Mapping, MutableMapping
33

44
# 3rd party
5-
import cssutils # type: ignore
65
import pytest
76
from coincidence.regressions import AdvancedDataRegressionFixture, AdvancedFileRegressionFixture
87
from domdf_python_tools.paths import PathPlus

tox.ini

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,4 @@ setenv =
225225
deps = -r{toxinidir}/tests/requirements.txt
226226
commands =
227227
python --version
228-
python -m pip uninstall css-parser -y
229228
python -m pytest --cov=dict2css -r aR tests/ {posargs}
230-
python -m pip install "css-parser>=1.0.6"
231-
python -m pytest --cov=dict2css -r aR tests/ --cov-append {posargs}

0 commit comments

Comments
 (0)