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 " ">
<!ENTITY copy "©">
]>
<data>
<country name="Liechtenstein">
<rank>1 ></rank>
<year>2008©</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 (>
) e un'entità non definita (©
) 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></rank>
<year>2008©</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)
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
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. –
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