2013-02-07 18 views
14

は、(すなわち )提起:解析XML

のPython 2.xのXMLエンティティの辞書で

ParseError: undefined entity  

を更新することができパーサ(documentation)作成:

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

をが、Pythonの3.xのと同じことを行う方法?


アップデート:私は、パーサーと、エラーの原因となったXMLエンティティの辞書を更新しようとする前にparser.parser.UseForeignDTD(1)を呼んでいたことを見落としているので、私の側からあり誤解されました。幸運なことに、@ m.brindleyは、XMLエンティティdictがまだPython 3.xに存在し、Python 2.xと同じ方法で更新できることを指摘しました。

答えて

17

ここでの問題は、XMLの唯一の有効なニーモニックエンティティquot,amp,apos,ltおよびgtである。つまり、XML 1.1 specで定義されたentity declaration markupを使用して、ほとんどすべて(X)のHTML名前付きエンティティを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引数には、 'predefined HTML entities'のためのスペースがありますが、その引数はまだ実装されていません。 entityという名前の空の辞書がinitメソッドで作成され、これが未定義のエンティティを検索するために使用されます。

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()はもう別
    • 有効な名前ではありません、あなたはそれらの文字にすべての有効なHTML5のニーモニックエンティティをマッピングするためにhtml.entities.html5XMLParser.entityを更新することができます。
  • データがXHTMLの場合は、ニーモニックエンティティを処理するためにHTMLParserをサブクラス化することができますが、必要に応じて、これはElementTreeを返しません。ここで

私が使用したスニペットである - それはHTMLParser(サブクラス化によるエンティティの処理を追加する方法を示すために)、エンティティのマッピングとET.XMLParserexpatを介して外部DOCTYPEでXMLをパース(ただ黙っによる未定義の実体を無視しますました外部DOCTYPE)。有効なXMLエンティティ(&gt;)と未定義のエンティティ(&copy;)があり、これはで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) 
+1

問題を調査していただきありがとうございますが、私はPython 3.x SPLがXMLエンティティテーブルの更新を許可していないと疑っています。少なくとも私はそのような発表を見つけることができませんでした。申し訳ありませんが、正規表現を使用してリモートのXHTMLデータを作成することは考えられません。 – theta

+0

私はこれにもう少し時間をかけて、expatが 'XMLParser'の' _default() 'メソッドに渡っていない理由を試しました。私の編集を参照してください - 外部DOCTYPEが定義されている場合、マップエンティティが可能です。 –

+0

私はこの質問をより一般的なものにしたいと思っていましたが、問題をもっと「局所的に」フィルタリングしてみましょう:XHTMLデータに 'xhtml1-transitional.dtd'があり、定義されていないXMLエンティティだけが'   'です。私はデフォルトでlxmlを使用し、使用できない場合はSPLにフォールバックしますが、ETを返します。 – theta

2

私は同様の問題を有するlxmlを使用してその周りました。そのetree.XMLParserには、壊れたXMLを無視しようとするキーワード引数がrecoverになっています。