Skip to content

Match nothing for empty-value substring attribute selectors - #298

Merged
facelessuser merged 1 commit into
facelessuser:mainfrom
chuenchen309:soupsieve-hunt-work
Jul 21, 2026
Merged

Match nothing for empty-value substring attribute selectors#298
facelessuser merged 1 commit into
facelessuser:mainfrom
chuenchen309:soupsieve-hunt-work

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

Summary

The substring attribute selectors [attr^=""], [attr$=""], and [attr*=""] currently match every element that merely has the attribute, when they should match nothing if the value is empty.

Per CSS Selectors Level 4 §6.2 (Substring matching attribute selectors):

[att^=val] — Represents an element with the att attribute whose value begins with the prefix "val". If "val" is the empty string then the selector does not represent anything.
[att$=val] — … If "val" is the empty string then the selector does not represent anything.
[att*=val] — … If "val" is the empty string then the selector does not represent anything.

Browsers agree: document.querySelectorAll('[href^=""]') returns an empty NodeList.

Root cause

In parse_attribute_selector, an empty value compiles to ^.* / .*?$ / .*?.*, which matches any value. The sibling ~= operator already special-cases the empty value to the never-match sentinel [^\s\S] — this PR extends the identical handling to the three substring operators (self-consistency).

Reproduction

import soupsieve as sv
from bs4 import BeautifulSoup
soup = BeautifulSoup('<a href="foo">1</a><a href="">2</a>', 'html.parser')
selector before (actual) after / spec-correct
[href^=""] ['1', '2'] []
[href$=""] ['1', '2'] []
[href*=""] ['1', '2'] []
[href~=""] [] (already correct) []

Non-empty values ([href^=f]['1']) and the case-insensitive i flag are unaffected.

Verification

  • 3 new tests in tests/test_level3/test_attribute.py (styled on the existing ~= empty-value test) fail on the unfixed tree, pass after the fix.
  • Full suite: 397 passed, 1 skipped, no regressions.
  • Changelog entry added under 2.9.

Related history: closed issue #30 introduced the ~= empty-value handling — confirming this exact spec point is treated as a real bug; the substring operators were simply missed.


This PR was authored by an AI coding agent (Claude Code) running on this account: the AI found the bug, ran the repro, wrote the tests, and wrote this description. The human account holder reviews every change and is accountable for it. The verification above is real and re-runnable from the diff. If this isn't the kind of contribution you want, say so and I'll close it.

`[attr^=""]`, `[attr$=""]`, and `[attr*=""]` currently match every
element that merely has the attribute, because the empty value compiles to
`^.*` / `.*?$` / `.*?.*`. Per CSS Selectors Level 4 substring matching,
an empty substring value must not represent anything. The sibling `~=`
operator already special-cases the empty value to a never-match sentinel;
this extends the same handling to the three substring operators.

Signed-off-by: chuenchen309 <48723787+chuenchen309@users.noreply.github.com>
@gir-bot gir-bot added S: needs-review Needs to be reviewed and/or approved. C: css-level-3 CSS level 3 selectors. C: css-parsing Related to CSS parsing. C: docs Related to documentation. C: source Related to source code. C: tests Related to testing. labels Jul 19, 2026
@facelessuser

Copy link
Copy Markdown
Owner

Please add to the changelog that this was done by Claude and the user for clarity.

@facelessuser

Copy link
Copy Markdown
Owner

Scratch that last statement. Such a request doesn't seem to be used widely, so we'll just move forward with the changelog as is.

@facelessuser

Copy link
Copy Markdown
Owner

@gir-bot lgtm

@gir-bot gir-bot added S: approved The pull request is ready to be merged. and removed S: needs-review Needs to be reviewed and/or approved. labels Jul 21, 2026
@facelessuser
facelessuser merged commit 8c5ceee into facelessuser:main Jul 21, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C: css-level-3 CSS level 3 selectors. C: css-parsing Related to CSS parsing. C: docs Related to documentation. C: source Related to source code. C: tests Related to testing. S: approved The pull request is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants