使用 (X)HTML 实体解析 XML

问题描述 投票:0回答:3

尝试使用 ElementTree 解析包含未定义实体(即

 
)的 XML:

ParseError:未定义的实体

在 Python 2.x XML 实体字典中可以通过创建解析器来更新(文档):

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

但是如何使用 Python 3.x 做同样的事情?


更新:我这边存在误解,因为我忽略了在尝试更新 XML 实体字典之前我正在调用

parser.parser.UseForeignDTD(1)
,这会导致解析器出错。幸运的是,@m.brindley 很有耐心,并指出 XML 实体字典在 Python 3.x 中仍然存在,并且可以像 Python 2.x 中一样进行更新

python xml python-3.x elementtree
3个回答
19
投票

这里的问题是 XML 中唯一有效的助记实体是

quot
amp
apos
lt
gt
。这意味着几乎所有 (X)HTML 命名实体都必须使用 XML 1.1 规范中定义的实体声明标记在 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>

XMLParser
中的
xml.etree.ElementTree
使用
xml.parsers.expat
来进行实际的解析。在
XMLParser
的初始化参数中,有一个空格用于“预定义的 HTML 实体”,但该参数尚未实现。在 init 方法中创建了一个名为
entity
的空字典,这用于查找未定义的实体。

我不认为 expat(通过扩展,ET XMLParser)能够处理将名称空间切换到 XHMTL 之类的东西来解决这个问题。可能是因为它不会获取外部名称空间定义(我尝试将

xmlns="http://www.w3.org/1999/xhtml"
设置为数据元素的默认名称空间,但效果不佳),但我无法确认这一点。默认情况下,expat 会针对非 XML 实体引发错误,但您可以通过定义外部 DOCTYPE 来解决此问题 - 这会导致 expat 解析器将未定义的实体条目传递回
ET.XMLParser
_default()
方法。

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

解决方案:

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

这是我使用的代码片段 - 它通过

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

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)

1
投票

我遇到了类似的问题,并通过使用 lxml 解决了这个问题。它的

etree.XMLParser
有一个
recover
关键字参数,强制它尝试忽略损坏的 XML。


0
投票
from xml.etree import ElementTree
from html.entities import name2codepoint
from io import StringIO
import unicodedata

url = "https://docs.python.org/3/library/html.parser.html"
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 \
    (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
}
html_response = requests.get(url=url, headers = headers)

def getParser():
    return xp

with open("sample.html", "w", encoding="utf-8") as html_file:
    html_file.write(html_response.text)

xp = ElementTree.XMLParser()
for k, v in name2codepoint.items(): xp.entity[k] = chr(v)

with open("sample.html", "r", encoding="utf-8") as html_file:
    html = html_file.read()
    b = StringIO(html)
    t = ElementTree.parse(b, xp)
    print(t)
© www.soinside.com 2019 - 2024. All rights reserved.