解析带有未定义实体的XHTML5
请考虑以下内容:
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> Sample text</p>
</body>
</html>
'''
parser = ET.XMLParser()
parser.entity['nbsp'] = ' '
tree = ET.fromstring(xhtml, parser=parser)
print(ET.tostring(tree, method='xml'))
这段代码可以很好地将 xhtml
字符串转换成文本表示。
但是,对于同样的 XHTML 文档,如果使用 HTML5 的文档类型声明:
xhtml = '''<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head><title>XHTML sample</title></head>
<body>
<p> Sample text</p>
</body>
</html>
'''
我遇到了一个异常:
xml.etree.ElementTree.ParseError: undefined entity: line 5, column 19
这意味着解析器无法处理这个文档,尽管我已经把 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 文档类型声明的 XHTML 文件解析成功呢?
对于 lxml 的一个部分解决方案是使用恢复器:
parser = etree.XMLParser(resolve_entities=False, recover=True)
不过我仍在等待更好的解决方案。
1 个回答
这里的问题是,背后使用的Expat解析器通常不会报告未知的实体,而是会直接抛出错误。因此,你想要触发的xml.etree.ElementTree
中的备用代码根本不会运行。你可以使用UseForeignDTD
方法来改变这种行为,这样Expat就会忽略文档类型声明,并将所有实体声明传递给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> 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'))
这种方法的副作用是:正如我所说,文档类型声明会被完全忽略。这意味着你必须声明所有的实体,即使那些本来应该由文档类型覆盖的实体。
需要注意的是,你放入ElementTree.XMLParser.entity
字典中的值必须是普通字符串,也就是实体将被替换成的文本——你不能再在这里引用其他实体。因此,对于
,它应该是u'\u00A0'
。