Skip to content

🐛 fix(convert): css_to_xpath empty value with i flag - #1086

Merged
gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:fix/css-xpath-empty-i
Oct 3, 2026
Merged

gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:fix/css-xpath-empty-i

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

css_to_xpath translated an attribute selector with an empty value and the i flag, such as [d="" i], into translate(@d, ...) = '', which also matches elements without the attribute because translate() of an absent attribute is the empty string.

The translation now requires the attribute before comparing its folded value, since XPath 1.0 turns an empty node-set into the empty string that translate() then returns, while Selectors 4 matches [att=val] only on elements that have att, and its i flag changes only how the value compares. It now selects the same elements as select().

Neither cssselect 1.5.0 nor css-to-xpath 0.1.0 parses the i flag; both raise a syntax error on [d="" i].

closes #1040

@gaborbernat gaborbernat added the bug Something isn't working label Oct 3, 2026
@gaborbernat
gaborbernat force-pushed the fix/css-xpath-empty-i branch from 60f7c7b to ab8ab59 Compare October 3, 2026 10:03
@codspeed

codspeed Bot commented Oct 3, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 581 untouched benchmarks
⏩ 32 skipped benchmarks1


Comparing gaborbernat:fix/css-xpath-empty-i (ab8ab59) with main (83d8942)

Open in CodSpeed

Footnotes

  1. 32 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@gaborbernat
gaborbernat merged commit 2ec0b0a into tox-dev:main Oct 3, 2026
55 checks passed
@gaborbernat
gaborbernat deleted the fix/css-xpath-empty-i branch October 3, 2026 15:11
gaborbernat added a commit that referenced this pull request Oct 3, 2026
`css_to_xpath` translated `[att|="" i]`, and `p[lang|=""]` through
HTML's [case-insensitive attribute
list](https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors),
into `translate(@att, ...) = '' or starts-with(...)`. [`string()` of an
empty
node-set](https://www.w3.org/TR/1999/REC-xpath-19991116/#function-string)
is the empty string, so the expression also matched elements without the
attribute, while [Selectors
4](https://www.w3.org/TR/selectors-4/#attribute-representation) matches
`[att|=val]` only on elements that have `att`.

The translation now requires the attribute before comparing its folded
empty value, the guard #1086 added for `=`. A non-empty `|=` value never
matches an empty string, and `^=`, `$=`, `*=` and `~=` already translate
an empty value to `false()`, so the other cases keep their output. On
`<div><p id=none></p><p id=empty d=""></p><p id=dash d="-x"></p></div>`,
the XPath for `[d|="" i]` on lxml 6.1.3 now selects `empty` and `dash`,
the same elements as `select()`.

[cssselect
1.5.0](https://github.com/scrapy/cssselect/blob/v1.5.0/cssselect/xpath.py#L794-L804)
wraps every `|=` match in the same presence guard, and it rejects the
`i` flag with a syntax error.

closes #1109
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

css_to_xpath with an empty value and i flag matches elements without the attribute

1 participant