簡體   English   中英

使用(X)HTML實體解析XML

[英]Parse XML with (X)HTML entities

嘗試使用ElementTree解析包含未定義實體(即  )的XML:

ParseError: undefined entity  

在Python 2.x中,可以通過創建解析器( 文檔 )來更新XML實體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中那樣進行更新。

這里的問題是XML中唯一有效的助記符實體是quotampaposltgt 這意味着幾乎所有(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>

xml.etree.ElementTreeXMLParser使用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()方法在XMLParser實例中查找entity dict,如果找到匹配的鍵,它將用相關的值替換實體。 這維護了問題中提到的Python-2.x語法。

解決方案:

  • 如果數據沒有外部DOCTYPE並且有(X)HTML助記符實體,那么你就不走運了。 它是無效的XML和expat是正確的拋出錯誤。 您應該添加外部DOCTYPE。
  • 如果數據具有外部DOCTYPE,則可以使用舊語法將助記符名稱映射到字符。 注意:你應該在py3k中使用chr() - unichr()不再是有效的名字
    • 或者,您可以使用html.entities.html5更新XMLParser.entity ,以將所有有效的HTML5助記符實體映射到其字符。
  • 如果數據是XHTML,您可以將HTMLParser子類HTMLParser處理助記符實體,但這不會根據需要返回ElementTree

這是我使用的代碼片段 - 它通過HTMLParser解析XML與外部DOCTYPE(通過子類化演示如何添加實體處理), ET.XMLParser通過實體映射和expat ET.XMLParser (由於外部DOCTYPE,它將默默地忽略未定義的實體) 。 有一個有效的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)

我遇到了類似的問題,並使用lxml解決了這個問題。 它的etree.XMLParser有一個recover關鍵字參數,迫使它試圖忽略損壞的XML。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM