Skip to content

Commit 0157bb6

Browse files
committed
fix: drop processing instructions in HTML parser
partition_html() crashed with 'lxml.etree._ProcessingInstruction object has no attribute is_phrasing' when the document contained a processing instruction such as '<?xml ...?>'. The HTML parser's element-class lookup only maps real elements, so a processing instruction reaches the tree as a plain lxml _ProcessingInstruction node without the parser's custom interface, then crashes the phrasing/flow iteration. Pass remove_pis=True to the HTMLParser so processing instructions are stripped during parsing, mirroring the existing remove_comments=True. They carry no rendered text, so dropping them matches browser behavior. Fixes #4358
1 parent 7c8f675 commit 0157bb6

4 files changed

Lines changed: 20 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.22.32
2+
3+
### Fixes
4+
5+
- **Fix crash on processing instructions in HTML**: `partition_html()` no longer raises `AttributeError: 'lxml.etree._ProcessingInstruction' object has no attribute 'is_phrasing'` when the document contains a processing instruction such as `<?xml ...?>`. Processing instructions are now dropped during parsing, consistent with how comments are already handled.
6+
17
## 0.22.31
28

39
### Enhancements

test_unstructured/partition/html/test_partition.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ def test_partition_html_processes_chinese_chracters():
203203
assert elements[0].text == "每日新闻"
204204

205205

206+
def test_partition_html_ignores_processing_instructions():
207+
"""A processing-instruction like `<?xml ...?>` must not crash the parser (see #4358)."""
208+
html_text = (
209+
'<html><body><div>\n<?xml version="1.0" encoding="UTF-8"?>\n'
210+
"<p>hello world</p></div></body></html>"
211+
)
212+
assert partition_html(text=html_text) == [Text("hello world")]
213+
214+
206215
def test_emoji_appears_with_emoji_utf8_code():
207216
assert partition_html(text='<html charset="utf-8"><p>Hello &#128512;</p></html>') == [
208217
Text("Hello 😀")

unstructured/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.22.31" # pragma: no cover
1+
__version__ = "0.22.32" # pragma: no cover

unstructured/partition/html/parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,10 @@ def derive_element_type_from_text(text: str) -> type[Text] | None:
945945
# ------------------------------------------------------------------------------------------------
946946

947947

948-
html_parser = etree.HTMLParser(remove_comments=True)
948+
# -- `remove_pis` drops processing-instructions (e.g. `<?xml ...?>`); like comments they
949+
# -- carry no rendered text and otherwise reach the parser as `_ProcessingInstruction` nodes
950+
# -- that lack the custom-element interface (`.is_phrasing`), crashing iteration.
951+
html_parser = etree.HTMLParser(remove_comments=True, remove_pis=True)
949952
# -- elements that don't have a registered class get DefaultElement --
950953
fallback = etree.ElementDefaultClassLookup(element=DefaultElement)
951954
# -- elements that do have a registered class are assigned that class via lookup --

0 commit comments

Comments
 (0)