2013-02-07 26 views
14

Cercando di analizzare XML, con elementtree, che contiene un'entità non definita (cioè  ) solleva:Parse XML con (X) HTML entità

ParseError: undefined entity  

In Python 2.x entità XML dict può essere aggiornato da la creazione di parser (documentation):

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

ma come fare lo stesso con Python 3.x?


Aggiornamento: C'era incomprensioni da parte mia, perché ho trascurato che stavo chiamando parser.parser.UseForeignDTD(1) prima di tentare di aggiornare XML entità dict, che stava causando l'errore con il parser. Fortunatamente, @ m.brindley era paziente e ha sottolineato che l'entità XML dict esiste ancora in Python 3.x e può essere aggiornata allo stesso modo di Python 2.x

risposta

17

Il problema qui è che le sole entità mnemoniche valide in XML sono quot, amp, apos, lt e gt. Ciò significa che quasi tutte le entità denominate (X) HTML devono essere definite nella DTD utilizzando lo entity declaration markup definito nello XML 1.1 spec. Se il documento deve essere autonomo, questo dovrebbe essere fatto con una DTD inline in questo modo:

<?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> 

Il XMLParser in xml.etree.ElementTree utilizza un xml.parsers.expat per fare il parsing vero e proprio. Negli argomenti init per XMLParser, c'è uno spazio per "predefined HTML entities" ma tale argomento non è ancora implementato. Un dict vuoto denominato entity viene creato nel metodo init e questo è ciò che viene utilizzato per cercare entità non definite.

Non credo che expat (per estensione, ET XMLParser) sia in grado di gestire lo spostamento degli spazi dei nomi in qualcosa come XHMTL per aggirare questo problema. Forse perché non recupererà le definizioni di spazi dei nomi esterni (ho provato a creare xmlns="http://www.w3.org/1999/xhtml" lo spazio dei nomi predefinito per l'elemento dati ma non ha funzionato bene), ma non posso confermarlo. Per impostazione predefinita, expat genera un errore nei confronti di entità non XML, ma è possibile aggirare il problema definendo un DOCTYPE esterno - questo fa sì che il parser di expat passi le voci di entità non definite al metodo _default() .

Procedimento _default() fa un aspetto piano del entity dict nel caso XMLParser e se trova una chiave corrispondente, sostituirà l'entità con il valore associato. Questo mantiene la sintassi Python-2.x menzionata nella domanda.

Solutions:

  • Se i dati non ha un DOCTYPE esterna e ha (X) HTML entità mnemoniche, siete fuori di fortuna. Non è valido XML e expat è giusto lanciare un errore. È necessario aggiungere un DOCTYPE esterno.
  • Se i dati hanno un DOCTYPE esterno, è sufficiente utilizzare la sintassi precedente per associare nomi mnemonici ai caratteri. Nota: è necessario utilizzare chr() in py3k - unichr() non è un nome valido più
    • In alternativa, è possibile aggiornare XMLParser.entity con html.entities.html5 di mappare tutte le entità HTML5 mnemoniche validi ai loro personaggi.
  • Se i dati sono XHTML, si potrebbe sottoclasse HTMLParser per gestire entità mnemoniche, ma questo non restituirà un ElementTree, se lo desideri.

Ecco il frammento di che ho usato - analizza XML con un DOCTYPE esterno attraverso HTMLParser (per dimostrare come aggiungere la manipolazione entità da sottoclassi), ET.XMLParser con mapping di entità e expat (che sarà solo silenzio ignorare le entità non definite a causa di il DOCTYPE esterno). Esiste un'entità XML valida (&gt;) e un'entità non definita (&copy;) che io mappo a chr(0x24B4) con 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

Grazie per aver esplorato il problema, ma in qualche modo dubito che Python 3.x SPL non consenta l'aggiornamento della tabella delle entità XML. Almeno non riuscivo a trovare un annuncio del genere. Siamo spiacenti, ma l'utilizzo di espressioni regolari per preparare dati XHTML remoti non è accettabile come idea. – theta

+0

Avevo ancora un po 'di tempo per giocare con questo e ho scoperto perché expat non passava al metodo '_default()' in 'XMLParser'. Vedi la mia modifica: sei in grado di mappare le entità che hanno fornito un DOCTYPE esterno. –

+0

Volevo che questa domanda fosse più generale, ma cerchiamo di filtrare il problema più "localmente": ho dati XHTML con 'xhtml1-transitional.dtd' e so in anticipo che solo l'entità XML indefinita è'   '. Io uso lxml per impostazione predefinita e, se non disponibile, esegui il fallback su SPL, ma restituisco ET. – theta

2

stavo avendo un problema simile e ho ottenuto intorno ad esso utilizzando lxml. Il suo etree.XMLParser ha un argomento di parole chiave recover che lo costringe a cercare di ignorare l'XML spezzato.