Repository navigation
fix: do not leak lxml's synthetic fragment wrapper into the text output - #135
Merged
AlbertWeichselbraun merged 1 commit intoOct 23, 2026
Merged
Conversation
_get_html_tree used lxml.html.fromstring, which wraps fragments that consist of multiple root elements or top-level text in a synthetic <div> (or <span>). The relaxed CSS profile styles these tags, so the wrapper added indentation (div) or surrounding spaces (span) to the output, e.g. '<b>un</b>believable' was converted to 'un believable'. Use lxml.html.document_fromstring instead, which always returns the <html> root and never creates a synthetic wrapper. Output for single elements, full documents and the strict profile is unchanged. tests/test_broken_table_handling.py: the input starts with <body>, which fromstring turned into a <div>; the expected text is updated accordingly.
AlbertWeichselbraun
added a commit
that referenced
this pull request
Oct 23, 2026
This prevents test failures under Windows as reported by @OsamaAnsar in #135.
Contributor
|
Thank you for spotting and fixing this. Your pull request has been merged and included in the Inscriptis 2.7.6 release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
inscriptis._get_html_treeparses the input withlxml.html.fromstring. For fragments that consist of several root elements, or of top-level text next to elements,fromstringreturns a synthetic<div>(if a block-level tag is present) or<span>as the root. The relaxed CSS profile styles those tags (div:padding_inline=2,span: prefix/suffix" "), so markup that is not in the input shows up in the text. In particular, words get split:<b>un</b>believableis converted toun believable.Observed on
master(a863dd7, 2.7.5, lxml 6.1.3 and 5.4.0, default/relaxed profile), compared with the same markup inside an explicit<html><body>...</body></html>:get_text)<body><p>a</p>x' a\n\n x''a\n\nx''a\n\nx'<p>a</p><p>b</p>' a\n\n b\n''a\n\nb\n''a\n\nb\n'<div>a</div><div>b</div>' a\n b'' a\n b'' a\n b'<b>a</b>b'a b''ab''ab'<b>bold</b>text'bold text''boldtext''boldtext'<b>un</b>believable'un believable''unbelievable''unbelievable'<td>a</td><td>b</td>' ab''ab''ab'get_annotated_text('<b>un</b>believable', ParserConfig(annotation_rules={'b': ['bold']}))returned text'un believable'before and returns'unbelievable'with label(0, 2, 'bold')now.Change
_get_html_treeuseslxml.html.document_fromstringinstead offromstring(also in the<pre>fallback).document_fromstringalways returns the<html>root, so no synthetic wrapper is created; this is the same treefromstringalready returns for full documents.Behaviour changes
tests/test_broken_table_handling.py::test_forgotten_td_close_tagparses<body>hallo<table>...</table>echo</body>.fromstringturned that<body>into a<div>, which gave"hallo\n1 2\n3 4\n\necho". With the real<body>the expected text is now"hallo1 2\n3 4\n\necho". This is the same output that<html><body>hallo<table>...already produces on master, so I treated the old"hallo\n"as an artifact of the wrapper rather than intended table behaviour (the test is about the missing</td>handling). Please say if you would rather keep the line break and handle this differently.Not changed
<p>a</p>,<div>a</div>,<span>a</span>,<li>a</li><li>b</li>), full documents, empty/whitespace-only/comment-only input: output identical before and after.<span>elements keep the relaxed profile's intended padding (e.g.fi<span>r</span>stis stillfi r st, see Presentation of internal spans seems a bit odd #75).Hello <b>world</b>!,plain text) already ended in a\nbefore this change and still do; libxml2 puts such text into an implied<p>, which is separate from the lxml wrapper.Tests
New
tests/test_fragment_wrapper.py(29 cases): exact expected text for the fragments above, equality with the same markup inside<html><body>for the relaxed and strict profiles, annotation offsets, and unchanged outputs.With only the
src/inscriptis/__init__.pychange reverted, 15 tests fail (14 in the new file, plus the updatedtest_forgotten_td_close_tag); with the change restored, all 109 tests pass.Verified
pytest(full suite): 109 passed on Python 3.12 with lxml 6.1.3 and with lxml 5.4.0 (the lower bound inpyproject.toml).ruff checkandruff format --check: clean.tests/html/*.htmlfiles passed in raw, without a<body>wrapper (88 comparisons, text and annotations, both profiles): 16 relaxed-profile results differ (the leading indentation of the synthetic wrapper is gone); strict-profile output is identical.Not tested
tox: there is notox.iniin the repository, so I ran what CI runs (ruff check) plusruff format --checkandpytest. I did not runuv build,ty, orsafety.test_html_snippets,test_html_snippets_annotations) fail on master unlessPYTHONUTF8=1is set, because theyopen()UTF-8 files without an encoding. Unrelated to this change; I ran withPYTHONUTF8=1.Inscriptis(fromstring(...))(as in the docs and examples) is not affected by this change and still has the old behaviour.