修复Python lxml中的tostring()

11 投票
1 回答
8845 浏览
提问于 2025-04-16 09:28

lxml的 tostring() 函数在只打印文档的一部分时似乎有点问题。看看这个例子:

from lxml.html import fragment_fromstring, tostring
frag = fragment_fromstring('<p>This stuff is <em>really</em> great!')
em = frag.cssselect('em').pop(0)
print tostring(em)

我本来期待看到 <em>really</em>,但结果却打印出了 <em>really</em> great!,这显然是错误的。这里的 ' great !' 不是选中的 em 的一部分。这个问题不仅仅是错误,对于处理结构化的XML文档来说,这种多余的文本是很常见的,真是个麻烦。

据我了解,lxml会把当前元素后面的任何自由文本存储在元素的 .tail 属性中。查看 tostring() 的代码,我发现它调用了 ElementTree.py 中的 _write() 函数,而这个函数总是会打印出尾部文本。这在处理整个树时是正确的,但在渲染子树的最后一个元素时却不应该这样,然而它并没有做出区分。

为了正确地渲染选中的XML而不带尾部文本,我尝试从头编写一个 toxml() 函数来替代它。这个方法基本上是可行的,但在处理注释、处理指令、命名空间、编码等方面有很多特殊情况需要考虑。所以我改变了思路,现在只是利用 tostring(),对它的输出进行后处理,去掉多余的 .tail 文本:

def toxml(e):
    """ Replacement for lxml's tostring() method that doesn't add spurious
    tail text. """

    from lxml.etree import tostring
    xml = tostring(e)
    if e.tail:
        xml = xml[:-len(e.tail)]
    return xml

一系列基本的测试显示这个方法效果不错。

有什么批评或建议吗?

1 个回答

14

那用 xml = lxml.etree.tostring(e, with_tail=False) 这个怎么样?

from lxml.html import fragment_fromstring
from lxml.etree import tostring
frag = fragment_fromstring('<p>This stuff is <em>really</em> great!')
em = frag.cssselect('em').pop(0)
print tostring(em, with_tail=False)

看起来 with_tail 是在2.0版本中新增的;你是不是用的是旧版本?

撰写回答