1
0
Fork 0
inkscape/share/extensions/tests/test_css.py
Daniel Baumann 02d935e272
Adding upstream version 1.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 23:40:13 +02:00

299 lines
12 KiB
Python

# coding=utf-8
"""
Test CSS Selectors Implementation
This unit test is a heavily modified version, adapted to SVG, originating from
CSSSelect2 (BSD).
"""
import pytest
from lxml import etree
from inkex.elements._parser import load_svg
from inkex.styles import ConditionalStyle
document = load_svg("tests/data/svg/ids.svg").getroot()
ALL_IDS = [el.get("id") for el in document.iter() if not isinstance(el, etree._Comment)]
@pytest.mark.parametrize(
"selector, result",
(
("*", ALL_IDS),
("g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
("g g", ["outer-g", "tspan-g", "foobar-g"]),
("g, g g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
("g , g g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
("rect[width]", ["rect1"]),
('rect[width="5"]', ["rect1"]),
('rect[style*="black"]', ["rect1"]),
('rect[style*=""]', []),
('rect[style^="fill"]', ["rect1", "rect2"]),
('rect[style^=""]', []),
('rect[style$=";"]', ["rect1", "rect2"]),
('rect[style$=""]', []),
('g[foobar~="bc"]', ["foobar-g"]),
('g[foobar~="cde"]', ["foobar-g"]),
('[foobar~="ab bc"]', []),
('[foobar~=""]', []),
('[foobar~=" \t"]', []),
('g[foobar~="cd"]', []),
('rect[style="fill:BlAck;"]', []),
('rect[style="fill:BlAck;" s]', []),
('rect[style="fill:BlAck;" i]', ["rect1"]),
('rect[style*="BlAck"]', []),
('rect[style*="BlAck" s]', []),
('rect[style*="BlAck" i]', ["rect1"]),
('rect[style^="fILl"]', []),
('rect[style^="fiLl" s]', []),
('rect[style^="fiLl" i]', ["rect1", "rect2"]),
('rect[style$="Black;"]', []),
('rect[style$="Black;" S]', []),
('rect[style$="Black;" I]', ["rect1"]),
('g[foobar~="BC"]', []),
('g[foobar~="BC" s]', []),
('g[foobar~="BC" i]', ["foobar-g"]),
# Attribute values are case sensitive…
('*[lang|="En"]', ["second-tspan"]),
('[lang|="En-us"]', ["second-tspan"]),
('*[lang|="en"]', []),
('[lang|="en-US"]', []),
('*[lang|="e"]', []),
# … but :lang() is not.
(":lang(EN)", ["second-tspan", "tspan-g"]),
("*:lang(en-US)", ["second-tspan", "tspan-g"]),
(":lang(En)", ["second-tspan", "tspan-g"]),
(":lang(e)", []),
(':lang("en-US")', ["second-tspan", "tspan-g"]),
# pytest.param(
# ':lang("*-US")', ['second-tspan', 'tspan-g'], marks=pytest.mark.xfail),
# pytest.param(
# ':lang(\\*-US)', ['second-tspan', 'tspan-g'], marks=pytest.mark.xfail),
(":lang(en /* English */, fr /* French */)", ["second-tspan", "tspan-g"]),
("tspan:nth-child(3)", ["third-tspan"]),
("tspan:nth-child(10)", []),
("tspan:nth-child(2n)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
("tspan:nth-child(even)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
("tspan:nth-child(+2n+0)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
(
"tspan:nth-child(2n+1)",
["first-tspan", "third-tspan", "fifth-tspan", "seventh-tspan"],
),
(
"tspan:nth-child(odd)",
["first-tspan", "third-tspan", "fifth-tspan", "seventh-tspan"],
),
("tspan:nth-child(2n+4)", ["fourth-tspan", "sixth-tspan"]),
("tspan:nth-child(3n+1)", ["first-tspan", "fourth-tspan", "seventh-tspan"]),
(
"text > tspan:nth-child(2n of text tspan[my-attr=test])",
["third-tspan", "fifth-tspan"],
),
("tspan:nth-last-child(1)", ["seventh-tspan"]),
("tspan:nth-last-child(0)", []),
("tspan:nth-last-child(2n+2)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
("tspan:nth-last-child(even)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
("tspan:nth-last-child(2n+4)", ["second-tspan", "fourth-tspan"]),
(":nth-last-child(1 of [my-attr=test])", ["fifth-tspan"]),
("text:first-of-type", ["first-text"]),
("text:nth-child(1)", []),
("text:nth-of-type(2)", ["second-text"]),
(":nth-of-type(1 of .e)", ["first-text"]),
("text:nth-last-of-type(2)", ["first-text"]),
(":nth-last-of-type(1 of .e)", ["second-text"]),
("mask:only-child", ["foobar-mask"]),
("g:only-child", ["tspan-g"]),
("g *:only-child", ["tspan-g", "link-element", "foobar-mask"]),
(
"pattern *:only-of-type",
["pattern-textPath", "link-element", "radialGradient"],
),
("pattern:only-of-type", ["paragraph"]),
(
"tspan:empty",
[
"third-tspan",
"fourth-tspan",
"fifth-tspan",
"sixth-tspan",
"seventh-tspan",
],
),
(
"tspan:EMpty",
[
"third-tspan",
"fourth-tspan",
"fifth-tspan",
"sixth-tspan",
"seventh-tspan",
],
),
(":root", ["svg"]),
("svg:root", ["svg"]),
("tspan:root", []),
("* :root", []),
(".a", ["first-text"]),
(".b", ["first-text"]),
("*.a", ["first-text"]),
("text.a", ["first-text"]),
(".c", ["first-text", "third-tspan", "fourth-tspan"]),
("*.c", ["first-text", "third-tspan", "fourth-tspan"]),
("text *.c", ["third-tspan", "fourth-tspan"]),
("text tspan.c", ["third-tspan", "fourth-tspan"]),
("tspan ~ tspan.c", ["third-tspan", "fourth-tspan"]),
("text > tspan.c", ["third-tspan", "fourth-tspan"]),
("#first-tspan", ["first-tspan"]),
("tspan#first-tspan", ["first-tspan"]),
("*#first-tspan", ["first-tspan"]),
("tspan g", ["tspan-g"]),
("tspan > g", ["tspan-g"]),
("g > g", ["outer-g", "foobar-g"]),
("g>.c", ["first-text"]),
("g > .c", ["first-text"]),
("g + g", ["foobar-g"]),
("stop ~ stop", ["stop2-radialGradient"]),
('circle[shape="circle"] ~ circle', ["circle-nohref"]),
("text#first-text tspan:last-child", ["seventh-tspan"]),
("text#first-text *:last-child", ["tspan-g", "seventh-tspan"]),
("#outer-g:first-child", ["outer-g"]),
(
"#outer-g :first-child",
[
"rect1",
"first-tspan",
"tspan-g",
"pattern-p",
"link-element",
"stop-radialGradient",
"circle-href",
],
),
(":not(*)", []),
("linearGradient:not([href])", ["linearGradient-nohref"]),
(
"text :Not([class])",
[
"first-tspan",
"second-tspan",
"tspan-g",
"fifth-tspan",
"sixth-tspan",
"seventh-tspan",
],
),
("tspan:not(:nth-child(odd), #second-tspan)", ["fourth-tspan", "sixth-tspan"]),
("tspan:not(tspan)", []),
(":is(*)", ALL_IDS),
(":is(g)", ["layer", "outer-g", "tspan-g", "foobar-g"]),
(
":is(g, radialGradient)",
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
),
(":is(:::wrong)", []),
(
":is(g, :::wrong, radialGradient)",
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
),
("g :is(g, g)", ["outer-g", "tspan-g", "foobar-g"]),
("tspan:is(.c)", ["third-tspan", "fourth-tspan"]),
('stop:is([offset="1"])', ["stop2-radialGradient"]),
("g:is(:not(#outer-g))", ["layer", "tspan-g", "foobar-g"]),
("g:is(g::before)", []),
(":where(*)", ALL_IDS),
(":where(g)", ["layer", "outer-g", "tspan-g", "foobar-g"]),
(
":where(g, radialGradient)",
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
),
(":where(:::wrong)", []),
(
":where(g, :::wrong, radialGradient)",
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
),
("g :where(g, g)", ["outer-g", "tspan-g", "foobar-g"]),
("tspan:where(.c)", ["third-tspan", "fourth-tspan"]),
('stop:where([offset="1"])', ["stop2-radialGradient"]),
("g:where(:not(#outer-g))", ["layer", "tspan-g", "foobar-g"]),
("g:where(g::before)", []),
("pattern:has(stop)", ["paragraph"]),
("pattern:has(radialGradient stop)", ["paragraph"]),
("pattern:has(> radialGradient)", ["paragraph"]),
("text:has(> g)", []),
("text:has(stop, tspan)", ["first-text"]),
("text:has(stop, radialGradient)", []),
("text:has(+ pattern)", ["first-text"]),
("text:has(~ text)", ["first-text"]),
("text:has(>a, ~ text)", ["first-text"]),
("text:has(a,text, tspan )", ["first-text"]),
("text:has(*)", ["first-text"]),
("text:has(:not(tspan))", ["first-text"]),
("text:has( > :not( tspan ))", []),
("text:has(:not(tspan, g))", []),
# Invalid characters in XPath element names, should not crash
(r"di\a0 v", []),
(r"g\[", []),
(r"[h\a0 ref]", []),
(r"[h\]ref]", []),
(":link", ["link", "link-element"]),
(":any-link", ["link", "link-element"]),
(":local-link", ["link", "link-element"]),
(":visited", []),
(":hover", []),
(":active", []),
(":focus", []),
(":target", []),
(":enabled", []),
(":disabled", []),
(":checked", []),
# Check that comments inside the selector are not a problem
("a:not([href]), g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
("a:not([href]) /* test */, g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
("a:not([href]), /* test */ g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
("/* test */a:not([href]),g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
("a:not([href]) , g g/* test */", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
(
"/* test */a:not([href]), /* test */ g g",
["outer-g", "tspan-g", "nolink", "foobar-g"],
),
(
"/* test */a:not([href])/* test */,g g",
["outer-g", "tspan-g", "nolink", "foobar-g"],
),
(
"/* test */ a:not([href]), g/* test */ g",
["outer-g", "tspan-g", "nolink", "foobar-g"],
),
(
"a:not([href]) /* test */,/* test */g g",
["outer-g", "tspan-g", "nolink", "foobar-g"],
),
(":is() p", []),
("*~rect", ["rect2", "rect3"]),
("radialGradient *", ["stop-radialGradient", "stop2-radialGradient"]),
("radialGradient > :is()", []),
("pattern:has(stop, :is())", ["paragraph"]),
("svg|pattern:has(stop, :is())", ["paragraph"]),
('g[inkscape|groupmode="layer"][svg|id="layer"]', ["layer"]),
('g[inkscape|groupmode="layer"][id="layer"]', ["layer"]),
('g[groupmode="layer"][id="layer"]', []),
(
"#paragraph :last-of-type",
[
"pattern-textPath",
"pattern-p2",
"link-element",
"nolink",
"radialGradient",
"stop2-radialGradient",
],
),
),
)
def test_select(selector, result):
"""Main selectors test"""
style = ConditionalStyle(selector)
found = [el.get("id", "nil") for el in style.all_matches(document)]
assert found == result, found