首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用(X)HTML实体解析XML

用(X)HTML实体解析XML
EN

Stack Overflow用户
提问于 2013-02-07 06:23:58
回答 3查看 9.4K关注 0票数 15

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

ParseError: undefined entity  

在Python2.xXML中,可以通过创建解析器(文档)来更新实体dict:

代码语言:javascript
运行
复制
parser = ET.XMLParser()
parser.entity["nbsp"] = unichr(160)

但是如何在Python3.x上做同样的事情呢?

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-02-09 09:03:47

这里的问题是XML中唯一有效的助记符实体是quotampaposltgt。这意味着几乎所有(X)HTML命名实体都必须使用在实体声明标记中定义的XML1.1规范在DTD中定义。如果文档是独立的,则应该使用内联DTD来完成,如下所示:

代码语言:javascript
运行
复制
<?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 in xml.etree.ElementTree使用xml.parsers.expat来执行实际的解析。在XMLParser的init参数中,存在“预定义HTML实体”的空格,但该参数尚未实现。在init方法中创建了一个名为entity的空dict,它用于查找未定义的实体。

我不认为expat (扩展而言,ET XMLParser)能够处理将名称空间切换到类似XHMTL的操作来解决这个问题。可能是因为它不会获取外部命名空间定义(我尝试将xmlns="http://www.w3.org/1999/xhtml"作为数据元素的默认名称空间,但它没有很好地发挥作用),但我无法证实这一点。默认情况下,expat将引发针对非XML实体的错误,但您可以通过定义外部DOCTYPE来解决此问题--这将导致expat解析器将未定义的实体条目传回给ET.XMLParser_default()方法。

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

解决方案:

  • 如果数据没有外部DOCTYPE,并且有(X)HTML助记实体,那么您就倒霉了。它是无效的XML,expat是正确的抛出错误。您应该添加一个外部DOCTYPE。
  • 如果数据具有外部DOCTYPE,则只需使用旧语法将助记符名称映射为字符即可。注意:您应该在chr()中使用py3k - unichr()不再是一个有效的名称。
    • 或者,您可以用XMLParser.entity更新html.entities.html5,将所有有效的HTML5助记符实体映射到它们的字符。

  • 如果数据是XHTML,您可以使用子类HTMLParser来处理助记符实体,但这不会按需要返回ElementTree

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

代码语言:javascript
运行
复制
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)
票数 18
EN

Stack Overflow用户

发布于 2015-11-10 23:25:18

我也遇到了类似的问题,通过使用lxml解决了这个问题。它的etree.XMLParser有一个recover关键字参数,这迫使它试图忽略损坏的XML。

票数 1
EN

Stack Overflow用户

发布于 2021-08-19 05:52:27

代码语言:javascript
运行
复制
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)
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14744945

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档