2012-01-04 11 views
13

sto ottenendo questo errore nel mio programma Python: ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control charactersFiltrando alcuni byte in python

Questa domanda, random text from /dev/random raising an error in lxml: All strings must be XML compatible: Unicode or ASCII, no NULL bytes, spiega il problema.

La soluzione era filtrare determinati byte, ma sono confuso su come procedere per farlo.

Qualsiasi aiuto?

Modifica: scusa se non ho dato abbastanza informazioni sul problema. i dati di stringa provengono da una query API esterna di cui non ho il controllo sul modo in cui i dati sono formattati.

+1

Hai anche dati casuali su input come nella domanda a cui ti stai riferendo? –

risposta

21

Come la risposta alla domanda legata detto, lo standard XML definisce un carattere valido come:

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] 

Traducendo che in Python:

def valid_xml_char_ordinal(c): 
    codepoint = ord(c) 
    # conditions ordered by presumed frequency 
    return (
     0x20 <= codepoint <= 0xD7FF or 
     codepoint in (0x9, 0xA, 0xD) or 
     0xE000 <= codepoint <= 0xFFFD or 
     0x10000 <= codepoint <= 0x10FFFF 
     ) 

È possibile quindi utilizzare tale funzione tuttavia è necessario a, ad es

cleaned_string = ''.join(c for c in input_string if valid_xml_char_ordinal(c)) 
+0

Sembra che potrebbe funzionare. Sfortunatamente i dati provengono da una API esterna e l'errore viene generato molto raramente. Quindi non ho modo di riprodurre le condizioni che hanno causato l'errore. Devo aspettare che le circostanze siano le stesse prima di poter testare questa soluzione. Grazie per la risposta, sono abbastanza nuovo per Python e capire l'implementazione della soluzione (e come risolve il mio problema) mi darà la possibilità di capire meglio la lingua. – y3di

+0

@ y3di: Mentre si attende che l'errore si ripresenti, è possibile modificare il codice per intercettare l'errore e scrivere il documento XML all'origine in un file di registro. In questo modo puoi scoprire esattamente quali sono i personaggi illegali e decidere se eliminarli o sostituire altri (i) personaggio (i) è più appropriato. In ogni caso, la tua domanda (in effetti: come filtrare i caratteri illegali da un documento XML) ha ricevuto risposta e dovresti considerare di accettarla (fai clic sul "segno di spunta" a sinistra della risposta). –

+1

Per scopi di attribuzione, sto semplicemente inserendo qui un gist con un codice per filtrare questi caratteri dalle stringhe come ho dovuto usare di recente. Grazie per la tua risposta. https://gist.github.com/rcalsaverini/6212717 –

3

Penso che questo sia duro/eccessivo e sembra dolorosamente lento, ma il mio programma è ancora veloce e dopo aver tentato di capire cosa stava andando male (anche dopo aver tentato di implementare l'implementazione clean_string di John), ho adattato la sua risposta per eliminare ASCII non stampabile utilizzando la seguente (Python 2.7):

from curses import ascii 
def clean(text): 
    return str(''.join(
      ascii.isprint(c) and c or '?' for c in text 
      )) 

non sono sicuro di quello che ho fatto di sbagliato con l'opzione migliore, ma volevo solo andare avanti ...

+0

è meglio, grazie –

+0

potresti usare' bytes.translate() 'per rimuovere/sostituire i byte che non sono in 'string.printable'. A differenza della soluzione di @John Machin; non funziona con testo Unicode arbitrario. – jfs

10

Un altro approccio è molto più veloce della risposta di cui sopra è di usare espressioni regolari, in questo modo:

re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', text) 

Confrontando la risposta di cui sopra, essa risulta essere più di 10 volte più veloce nel mio test:

import timeit 

func_test = """ 
def valid_xml_char_ordinal(c): 
    codepoint = ord(c) 
    # conditions ordered by presumed frequency 
    return (
     0x20 <= codepoint <= 0xD7FF or 
     codepoint in (0x9, 0xA, 0xD) or 
     0xE000 <= codepoint <= 0xFFFD or 
     0x10000 <= codepoint <= 0x10FFFF 
    ); 
''.join(c for c in r.content if valid_xml_char_ordinal(c)) 
""" 

func_setup = """ 
import requests; 
r = requests.get("https://stackoverflow.com/questions/8733233/") 
""" 

regex_test = """re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', r.content)""" 
regex_setup = """ 
import requests, re; 
r = requests.get("https://stackoverflow.com/questions/8733233/") 
""" 

func_test = timeit.Timer(func_test, setup=func_setup) 
regex_test = timeit.Timer(regex_test, setup=regex_setup) 

print func_test.timeit(100) 
print regex_test.timeit(100) 

uscita:

> 2.63773989677 
> 0.221401929855 

Quindi, dare un senso a questo, ciò che Sta facendo sta scaricando questa pagina web una volta (la pagina che stai leggendo attualmente), quindi eseguendo la tecnica funzionale e la tecnica regex sul suo contenuto 100X ciascuno.

L'utilizzo del metodo funzionale richiede circa 2.6 secondi.
L'utilizzo del metodo regex richiede circa 0,2 secondi.


Aggiornamento: Come identificate nei commenti, la regex in questa risposta precedentemente cancellato alcuni personaggi, che avrebbero dovuto essere ammessi in XML. Questi personaggi includono qualsiasi cosa nello Supplementary Multilingual Plane, che include script antichi come cuneiforme, geroglifici e (stranamente) emoji.

La regex corretta è ora sopra. Un test rapido per questo in futuro sta usando re.DEBUG, che stampa:

In [52]: re.compile(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', re.DEBUG) 
max_repeat 1 4294967295 
    in 
    negate None 
    range (32, 55295) 
    literal 9 
    literal 10 
    literal 13 
    range (57344, 65533) 
    range (65536, 1114111) 
Out[52]: re.compile(ur'[^ -\ud7ff\t\n\r\ue000-\ufffd\U00010000-\U0010ffff]+', re.DEBUG) 

mie scuse per l'errore. Posso solo offrire che ho trovato questa risposta altrove e metterla qui. Era l'errore di qualcun altro, ma l'ho propagato. Le mie sincere scuse a chiunque ne sia affetto.

Aggiornamento 2, 2017-12-12: Ho imparato da alcuni utenti OSX che questo codice non funzionerà sulle cosiddette build strette di Python, che a volte apparentemente OSX ha. Puoi verificarlo eseguendo import sys; sys.maxunicode. Se stampa 65535, il codice qui non funzionerà finché non installerai una "versione larga". See more about this here.

+0

sei sicuro che l'espressione regolare funzioni come previsto? Credo che le ultime due lettere "F" non vengano analizzate come parte della fuga "\ u". Prova questo in una console python: 'print (u '\ u10ffff')' E poi questo: 'print (u '\ u10ffzz')' –

+0

Hai ragione, e questo sicuramente avrebbe potuto causare cancellazioni di dati rimuovendo unicode con codepoints tra 65536 e 1114111. Ho aggiornato la risposta. – mlissner

Problemi correlati