Skip to content

tests: pin how c1 numeric character references extract - #367

Merged
wRAR merged 3 commits into
scrapy:masterfrom
HuzaifaChaudary:tests/c1-character-references
Sep 9, 2026
Merged

wRAR merged 3 commits into
scrapy:masterfrom
HuzaifaChaudary:tests/c1-character-references

Conversation

@HuzaifaChaudary

@HuzaifaChaudary HuzaifaChaudary commented Aug 30, 2026 •

Copy link
Copy Markdown

Closes #76.

what this is

#76 reported that numeric character references in the reserved C1 range came out as the raw code point rather than what a browser shows, so … gave \x85 instead of …. That is fixed in libxml2 now, and I have posted the check on the issue.

Nothing in the suite holds it there, though, so this adds the coverage rather than leaving an eight year old behaviour resting on a dependency nobody is pinning for it.

what is covered

The whole range, not a sample. All 32 code points from 0x80 to 0x9f, through both the css and the xpath path, compared against html.unescape, which implements the same HTML5 replacement table:

for code_point in range(0x80, 0xA0):
    reference = f"&#{code_point};"
    expected = html.unescape(reference)
    sel = self.sscls(text=f"<p>{reference}</p>")
    assert sel.css("p::text").get() == expected, reference
    assert sel.xpath("//p/text()").get() == expected, reference

Plus the hex and named forms, since &#x85; and &hellip; reach the same place by a different route.

the xml one is deliberate

def test_xml_keeps_c1_character_references_literal(self) -> None:
    sel = self.sscls(text="<p>&#133;</p>", type="xml")
    assert sel.xpath("//p/text()").get() == "\x85"

XML has no CP1252 remapping, so &#133; really is U+0085 there and the literal code point is correct. That is the behaviour the original report showed, using etree.fromstring, which is the XML parser. It is pinned so nobody later "fixes" the XML path to match HTML.

does it actually catch anything

A regression to the old behaviour differs from the assertion on 27 of the 32 code points. The other five, 0x81, 0x8d, 0x8f, 0x90 and 0x9d, have no entry in the replacement table, so the literal code point is the right answer for them either way.

checks

check result
pytest tests/ 302 passed, 2 skipped
ruff format --check already formatted
ruff check 7 findings repo wide, the same 7 on master. one is in the file this touches
mypy parsel tests 7 errors in 4 files, the same 7 on master. two are in the file this touches
pylint docs parsel tests 10.00/10
pytest docs parsel tests (the CI target) 446 passed, 2 skipped

I stated those two rows loosely the first time, so to be exact. I compared against origin/master in a throwaway worktree using the same binaries rather than assuming. The finding sets are identical; the only difference is line numbers, PLR0917 moving from 1242 to 1270 and the two mypy lines from 11/23 to 12/24, which are exactly my added line counts. Nothing new is attributable to this change.

Both are also local environment artifacts rather than CI signal. tox.ini pins types-lxml, types-jmespath and types-psutil for the typing env and none are in my venv, so all 7 mypy errors disappear under CI. Local ruff is 0.16.5 while .pre-commit-config.yaml pins v0.15.22, so version skew explains findings CI would not raise.

issue 76 reported that entities like the ellipsis came out as the raw code point instead of what a browser shows. that is fixed now in libxml2 but nothing in the suite holds it there. these cover the whole range against the table python implements so a regression is caught on 27 of the 32 code points. the xml one is pinned too because the literal code point is correct there and should not be changed to match html.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.96%. Comparing base (6b8fc75) to head (4347922).
⚠️ Report is 9 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #367      +/-   ##
==========================================
+ Coverage   97.95%   97.96%   +0.01%     
==========================================
  Files           5        5              
  Lines         440      443       +3     
  Branches       77       78       +1     
==========================================
+ Hits          431      434       +3     
  Misses          6        6              
  Partials        3        3              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

the windows wheels and the min-deps job still ship an older one , where the reference comes
back as the raw character . that is the behaviour scrapy#76 reported , so it is pinned too rather
than assumed away .
@HuzaifaChaudary

Copy link
Copy Markdown
Author

windows and the min-deps job were red and they were right . the cp1252 remapping is libxml2's , not parsel's , and it only arrived in 2.14 where the html tokeniser became html5 conforming . i checked it both ways here : on libxml2 2.9.13 the whole 0x80 to 0x9f range comes back as the raw character , on 2.14.6 it comes back mapped . the same lxml 6.1.3 passes on macos and fails on windows , so it is the bundled libxml2 that differs and not the lxml version .

so #76 is only really fixed where libxml2 is new enough , which i had not realised when i opened this . the test now pins both sides rather than assuming the new one , and it goes red either way if that ever changes . the named reference does not go through the table and is the same everywhere . full suite is 302 passed on 2.9.13 and 302 on 2.14.6 .

@AdrianAtZyte

Copy link
Copy Markdown
Contributor

What do you think about using skifif instead of having wrong expectations for old libxml2?

@HuzaifaChaudary

Copy link
Copy Markdown
Author

yes , that is better . done in 4347922 .

the two tests that need the mapping are marked skipif on libxml2 older than 2.14 now , and the skip reason prints the version it found . the named reference one and the xml one stay ungated , they do not go through the c1 table so they read the same on every build .

ran it both ways . lxml 6.1.3 with libxml2 2.14.6 , 304 passed 2 skipped . lxml 5.1.0 with libxml2 2.12.3 , the two skip and everything else still passes .

@wRAR
wRAR merged commit 67fd751 into scrapy:master Sep 9, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Work around incorrect extraction of "reserved" HTML entities

4 participants