用(X)HTML实体解析XML

2024-06-02 05:24:59 发布

您现在位置:Python中文网/ 问答频道 /正文

尝试使用ElementTree解析包含未定义实体(即 )的XML时引发:

ParseError: undefined entity  

在Python 2.x中,可以通过创建解析器(documentation)来更新dict实体:

parser = ET.XMLParser()
parser.entity["nbsp"] = unichr(160)

但是如何在Python 3.x中实现同样的功能呢?


更新:我这边有误解,因为我忽略了在尝试更新XML实体dict之前调用parser.parser.UseForeignDTD(1),这会导致解析器出错。幸运的是,@m.brindley很有耐心,他指出XML实体dict仍然存在于Python 3.x中,并且可以像在Python 2.x中一样进行更新


Tags: 实体parser解析器documentationxmldictetentity
2条回答

我有一个类似的问题,通过使用lxml来解决它。它的etree.XMLParser有一个recover关键字参数,该参数迫使它尝试忽略损坏的XML。

这里的问题是,XML中唯一有效的助记符实体是quotampaposltgt。这意味着几乎所有(X)HTML命名实体都必须使用XML 1.1 spec中定义的entity declaration markup在DTD中定义。如果文档是独立的,则应使用内联DTD完成,如下所示:

<?xml version="1.1" ?>
<!DOCTYPE naughtyxml [
    <!ENTITY nbsp "&#0160;">
    <!ENTITY copy "&#0169;">
]>
<data>
    <country name="Liechtenstein">
        <rank>1&nbsp;&gt;</rank>
        <year>2008&copy;</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
</data>

xml.etree.ElementTree中的XMLParser使用xml.parsers.expat进行实际的解析。在XMLParser的init参数中,有一个用于predefined HTML entities的空格,但该参数尚未实现。在init方法中创建一个名为entity的空dict,这是用来查找未定义实体的内容。

我不认为expat(扩展来说,ET XMLParser)能够处理将名称空间切换到XHMTL之类的东西来解决这个问题。可能是因为它无法获取外部名称空间定义(我尝试将xmlns="http://www.w3.org/1999/xhtml"作为数据元素的默认名称空间,但效果不佳),但我无法确认。默认情况下,expat将对非XML实体引发一个错误,但是您可以通过定义外部DOCTYPE来解决这个问题-这将导致expat解析器将未定义的实体项传递回ET.XMLParser_default()方法。

_default()方法查找XMLParser实例中的entitydict,如果找到匹配的键,它将用关联的值替换实体。这维护了问题中提到的Python-2.x语法。

解决方案:

  • 如果数据没有外部DOCTYPE并且有(X)个HTML助记符实体,那么您就走运了。它不是有效的XML,expat抛出错误是正确的。您应该添加外部DOCTYPE。
  • 如果数据具有外部DOCTYPE,则可以使用旧语法将助记符名称映射到字符。注意:在py3k中应该使用chr()-unichr()不再是有效的名称
    • 或者,可以用html.entities.html5更新XMLParser.entity,将所有有效的HTML5助记实体映射到它们的字符。
  • 如果数据是XHTML,则可以子类HTMLParser来处理助记实体,但这不会返回所需的ElementTree

这是我使用的代码片段-它通过HTMLParser(演示如何通过子类添加实体处理)、ET.XMLParser(使用实体映射)和expat(由于外部DOCTYPE的原因,它将静默忽略未定义的实体)来解析带有外部DOCTYPE的XML。有一个有效的XML实体(&gt;)和一个未定义的实体(&copy;),我用ET.XMLParser映射到chr(0x24B4)

from html.parser import HTMLParser
from html.entities import name2codepoint
import xml.etree.ElementTree as ET
import xml.parsers.expat as expat

xml = '''<?xml version="1.0"?>
<!DOCTYPE data PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<data>
    <country name="Liechtenstein">
        <rank>1&gt;</rank>
        <year>2008&copy;</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
</data>'''

# HTMLParser subclass which handles entities
print('=== HTMLParser')
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, name, attrs):
        print('Start element:', name, attrs)
    def handle_endtag(self, name):
        print('End element:', name)
    def handle_data(self, data):
        print('Character data:', repr(data))
    def handle_entityref(self, name):
        self.handle_data(chr(name2codepoint[name]))

htmlparser = MyHTMLParser()
htmlparser.feed(xml)


# ET.XMLParser parse
print('=== XMLParser')
parser = ET.XMLParser()
parser.entity['copy'] = chr(0x24B8)
root = ET.fromstring(xml, parser)
print(ET.tostring(root))
for elem in root:
    print(elem.tag, ' - ', elem.attrib)
    for subelem in elem:
        print(subelem.tag, ' - ', subelem.attrib, ' - ', subelem.text)

# Expat parse
def start_element(name, attrs):
    print('Start element:', name, attrs)
def end_element(name):
    print('End element:', name)
def char_data(data):
    print('Character data:', repr(data))
print('=== Expat')
expatparser = expat.ParserCreate()
expatparser.StartElementHandler = start_element
expatparser.EndElementHandler = end_element
expatparser.CharacterDataHandler = char_data
expatparser.Parse(xml)

相关问题 更多 >