为什么 lxml 在解析时关闭这个 "ol" 标签?

Why is lxml closing this "ol" tag when parsing?

这是一些 HTML:

<ol><ul><li>item</li></ul></ol>

和一些 python 3 代码 lxml 解析并重新打印:

import sys
from lxml import etree, html

document_root = html.fromstring(sys.stdin.read())
print(etree.tostring(document_root, encoding='unicode'))

这是输出:

<div><ol/><ul><li>item</li></ul>
</div>

在输出中,lxmlul 开始之前关闭了 ol,这改变了列表结构。

为什么要这样做?

我可以让 lxml 以保留列表结构的方式解析 HTML 吗?

编辑:请注意,如果我将 ul 替换为 ol (<ol><ol><li>item</li></ol></ol>),或者如果我替换 olul (<ul><ul><li>item</li></ul></ul>)。输出与输入没有变化。

我无法控制 HTML,它可能来自任何地方。

我正在使用从 PyPi 安装的 lxml 4.6.3,以及 python 3.9。

或者,是否有另一种解析 HTML 的方法,我可以从中提取列表文本并保留 Python 中的列表结构?

如你所知,我正在使用 lxml 来删除属性,所以下面是更接近我的用例的代码。不过,我想先给出最小的可复现的测试用例。

更接近我的用例的代码:

import sys

import lxml.html.clean as clean
from lxml import etree, html

document_root = html.fromstring(sys.stdin.read())

cleaner = clean.Cleaner(safe_attrs_only=True, safe_attrs=frozenset())
cleansed = cleaner.clean_html(document_root)

# Do something with the lists in cleansed, defined by ol, ul, and li ..

print(etree.tostring(cleansed, encoding='unicode')

我认为 HTML 4 和 HTML5 都不允许 ul 元素作为 ol 元素的子元素。只有 li 个元素可以是直接子元素。

这可能就是为什么 HTML 解析器构建的树结构不代表输入标记中的嵌套。 “传统的”HTML 4 解析器,就像可能在 lxml's/libxml 的 HTML 解析器算法中实现的那样,是否对结构进行了相同的更改,我不记得了,我我不确定在哪里测试它。

虽然两个 HTML5 验证器将您的 ul 标记为 ol 的不允许的子代,但当前的浏览器似乎保留了该嵌套。