From 264aee3d37340a2f357d2b6face04bd3fdb48ac4 Mon Sep 17 00:00:00 2001 From: Alan Date: Thu, 1 Oct 2026 10:19:39 +0800 Subject: [PATCH] Fix lxml walker for empty fragments --- AUTHORS.rst | 1 + CHANGES.rst | 2 ++ html5lib/tests/test_treewalkers.py | 23 +++++++++++++++++++++++ html5lib/treewalkers/etree_lxml.py | 7 ++++--- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 90401390..824f057d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -63,4 +63,5 @@ Patches and suggestions - Ville Skyttä - Hugo van Kemenade - Mark Vasilkov +- shkyyy18 diff --git a/CHANGES.rst b/CHANGES.rst index 47dcda3a..9c54f9ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,8 @@ Features: Bug fixes: +* Allow the lxml treewalker to serialize empty fragments without raising + ``IndexError``. (#343) * The sanitizer now permits ```` tags. It used to allow ``
`` already. (#423) diff --git a/html5lib/tests/test_treewalkers.py b/html5lib/tests/test_treewalkers.py index 780ca964..2c36d2cd 100644 --- a/html5lib/tests/test_treewalkers.py +++ b/html5lib/tests/test_treewalkers.py @@ -15,6 +15,7 @@ from html5lib import html5parser, treewalkers from html5lib.filters.lint import Filter as Lint +from html5lib.serializer import HTMLSerializer import re attrlist = re.compile(r"^(\s+)\w+=.*(\n\1\w+=.*)+", re.M) @@ -123,6 +124,28 @@ def test_fragment_single_char(tree, char): assert list(output) == expected +@pytest.mark.parametrize("tree,intext", + itertools.product(sorted(treeTypes.items()), + ["", ""])) +def test_fragment_empty(tree, intext): + treeName, treeClass = tree + if treeClass is None: + pytest.skip("Treebuilder not loaded") + + parser = html5parser.HTMLParser(tree=treeClass["builder"]) + document = parser.parseFragment(intext) + document = treeClass.get("adapter", lambda x: x)(document) + + assert list(Lint(treeClass["walker"](document))) == [] + assert "".join(HTMLSerializer().serialize(treeClass["walker"](document))) == "" + + +@pytest.mark.skipif(treeTypes["lxml"] is None, reason="lxml not importable") +def test_lxml_empty_list(): + walker = treewalkers.getTreeWalker('lxml') + assert list(Lint(walker([]))) == [] + + @pytest.mark.skipif(treeTypes["lxml"] is None, reason="lxml not importable") def test_lxml_xml(): expected = [ diff --git a/html5lib/treewalkers/etree_lxml.py b/html5lib/treewalkers/etree_lxml.py index a614ac5b..69a7858c 100644 --- a/html5lib/treewalkers/etree_lxml.py +++ b/html5lib/treewalkers/etree_lxml.py @@ -55,7 +55,7 @@ def getnext(self): return None def __len__(self): - return 1 + return len(self.children) class Doctype(object): @@ -180,11 +180,12 @@ def getNodeDetails(self, node): def getFirstChild(self, node): assert not isinstance(node, tuple), "Text nodes have no children" - assert len(node) or node.text, "Node has no children" if node.text: return (node, "text") - else: + elif len(node): return node[0] + else: + return None def getNextSibling(self, node): if isinstance(node, tuple): # Text node