使用未定义的实体解析XHTML5

时间:2014-07-03 21:06:20

标签: python xml html5 xhtml lxml

请考虑一下:

import xml.etree.ElementTree as ET

xhtml = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
        <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
        <head><title>XHTML sample</title></head>
            <body>
                <p>&nbsp;Sample text</p>
            </body>
        </html>
'''
parser = ET.XMLParser()
parser.entity['nbsp'] = '&#x00A0;'
tree = ET.fromstring(xhtml, parser=parser)
print(ET.tostring(tree, method='xml'))

,它呈现xhtml字符串的漂亮文本表示。

但是,对于HTML5 doctype的相同XHTML文档:

xhtml = '''<!DOCTYPE html>
        <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
        <head><title>XHTML sample</title></head>
            <body>
                <p>&nbsp;Sample text</p>
            </body>
        </html>
'''

我得到例外:

xml.etree.ElementTree.ParseError: undefined entity: line 5, column 19

所以解析器无法处理它,尽管我向实体dict添加了nbsp

如果我使用lxml

,也会发生同样的情况
from lxml import etree
parser = etree.XMLParser(resolve_entities=False)
tree = etree.fromstring(xhtml, parser=parser)
print etree.tostring(tree, method='xml')

提出:

lxml.etree.XMLSyntaxError: Entity 'nbsp' not defined, line 5, column 26

虽然我已将解析器设置为忽略实体。

为什么会这样,以及如何使用HTML5 doctype声明解析XHTML文件?


lxml的部分解决方案是使用recoverer:

parser = etree.XMLParser(resolve_entities=False, recover=True)

但是我还在等待更好的。

1 个答案:

答案 0 :(得分:3)

问题在于,幕后使用的Expat parser通常不会报告未知实体 - 它会抛出错误,因此您试图触发的xml.etree.ElementTree中的后备代码赢了“甚至跑。您可以使用UseForeignDTD method来更改此行为,它将使Expat忽略doctype声明并将所有实体声明传递给xml.etree.ElementTree。以下代码正常工作:

import xml.etree.ElementTree as ET

xhtml = '''<!DOCTYPE html>
        <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
        <head><title>XHTML sample</title></head>
            <body>
                <p>&nbsp;Sample text</p>
            </body>
        </html>
'''
parser = ET.XMLParser()
parser._parser.UseForeignDTD(True)
parser.entity['nbsp'] = u'\u00A0'
tree = ET.fromstring(xhtml, parser=parser)
print(ET.tostring(tree, method='xml'))

这种方法的副作用:正如我所说,doctype声明完全被忽略了。这意味着您必须声明所有实体,甚至是doctype所涵盖的实体。

请注意,您放入ElementTree.XMLParser.entity字典的值必须是常规字符串,实体将替换为的文本 - 您不能再引用其他实体。因此u'\u00A0'应为&nbsp;