Come indovinare in modo affidabile la codifica tra MacRoman, CP1252, Latin1, UTF-8 e ASCII


99

Al lavoro sembra che nessuna settimana passi mai senza qualche connipazione, calamità o catastrofe legata alla codifica. Il problema di solito deriva dai programmatori che pensano di poter elaborare in modo affidabile un file di "testo" senza specificarne la codifica. Ma non puoi.

Quindi è stato deciso d'ora in poi vietare ai file di avere nomi che finiscono con *.txto *.text. L'idea è che quelle estensioni inducano in errore il programmatore occasionale in un ottuso compiacimento riguardo alle codifiche, e questo porta a una gestione impropria. Sarebbe quasi meglio non avere alcuna estensione, perché almeno allora sai che non sai cosa hai.

Tuttavia, non andremo così lontano. Invece dovrai usare un nome di file che termina con la codifica. Così, per i file di testo, per esempio, questi sarebbero qualcosa come README.ascii, README.latin1, README.utf8, etc.

Per i file che richiedono una particolare estensione, se si può specificare la codifica all'interno del file stesso, come in Perl o Python, allora lo farai. Per i file come l'origine Java in cui non esiste una tale funzionalità interna al file, inserirai la codifica prima dell'estensione, come SomeClass-utf8.java.

Per l'output, UTF-8 deve essere fortemente preferito.

Ma per l'input, dobbiamo capire come gestire le migliaia di file nella nostra base di codice denominata *.txt. Vogliamo rinominarli tutti per adattarli al nostro nuovo standard. Ma non possiamo assolutamente osservarli tutti. Quindi abbiamo bisogno di una libreria o di un programma che funzioni davvero.

Questi sono variamente in ASCII, ISO-8859-1, UTF-8, Microsoft CP1252 o Apple MacRoman. Sebbene sappiamo di poter dire se qualcosa è ASCII, e abbiamo un buon cambiamento nel sapere se qualcosa è probabilmente UTF-8, siamo perplessi riguardo alle codifiche a 8 bit. Poiché siamo in esecuzione in un ambiente Unix misto (Solaris, Linux, Darwin) con la maggior parte dei desktop Mac, abbiamo alcuni file MacRoman fastidiosi. E soprattutto questi sono un problema.

Da qualche tempo sto cercando un modo per determinare a livello di codice quale di

  1. ASCII
  2. ISO-8859-1
  3. CP1252
  4. MacRoman
  5. UTF-8

è presente un file e non ho trovato un programma o una libreria in grado di distinguere in modo affidabile tra queste tre diverse codifiche a 8 bit. Probabilmente abbiamo più di mille file MacRoman da soli, quindi qualsiasi rilevatore di set di caratteri che utilizziamo deve essere in grado di rilevarli. Niente di ciò che ho visto può gestire il trucco. Avevo grandi speranze per la libreria del rilevatore di set di caratteri ICU , ma non può gestire MacRoman. Ho anche esaminato i moduli per fare lo stesso genere di cose sia in Perl che in Python, ma ancora e ancora è sempre la stessa storia: nessun supporto per il rilevamento di MacRoman.

Quello che sto quindi cercando è una libreria o un programma esistente che determini in modo affidabile in quale di queste cinque codifiche si trova un file, e preferibilmente di più. In particolare deve distinguere tra le tre codifiche a 3 bit che ho citato, specialmente MacRoman . I file contengono più del 99% di testo in lingua inglese; ce ne sono alcuni in altre lingue, ma non molti.

Se è codice di libreria, la nostra preferenza per la lingua è che sia in Perl, C, Java o Python e in quest'ordine. Se è solo un programma, allora non ci interessa davvero in quale linguaggio si trovi fintanto che viene fornito con il codice sorgente completo, gira su Unix ed è completamente libero.

Qualcun altro ha avuto questo problema di un miliardo di file di testo legacy codificati in modo casuale? In tal caso, come hai tentato di risolverlo e quanto hai avuto successo? Questo è l'aspetto più importante della mia domanda, ma mi interessa anche sapere se pensi che incoraggiare i programmatori a nominare (o rinominare) i loro file con la codifica effettiva in cui si trovano questi file ci aiuterà a evitare il problema in futuro. Qualcuno ha mai provato a far rispettare questo su una base istituzionale, e se sì, era che il successo o no, e perché?

E sì, capisco perfettamente perché non si possa garantire una risposta definitiva data la natura del problema. Questo è particolarmente il caso di file di piccole dimensioni, in cui non si dispone di dati sufficienti per continuare. Fortunatamente, i nostri file sono raramente piccoli. A parte il READMEfile casuale , la maggior parte ha dimensioni comprese tra 50k e 250k e molti sono più grandi. Qualunque cosa più di qualche K di dimensione è garantita in inglese.

Il dominio del problema è il text mining biomedico, quindi a volte ci occupiamo di corpora estesi ed estremamente grandi, come tutti i repository di accesso aperto di PubMedCentral. Un file piuttosto enorme è il BioThesaurus 6.0, a 5,7 gigabyte. Questo file è particolarmente fastidioso perché è quasi tutto UTF-8. Tuttavia, alcuni numbskull sono andati e hanno bloccato alcune righe in una codifica a 8 bit - Microsoft CP1252, credo. Ci vuole un bel po 'di tempo prima che tu inciampi su quello. :(


Risposte:


86

Innanzitutto, i casi facili:

ASCII

Se i tuoi dati non contengono byte superiori a 0x7F, allora è ASCII. (O una codifica ISO646 a 7 bit, ma quelle sono molto obsolete.)

UTF-8

Se i tuoi dati vengono convalidati come UTF-8, puoi tranquillamente presumere che sia UTF-8. A causa delle rigide regole di convalida di UTF-8, i falsi positivi sono estremamente rari.

ISO-8859-1 rispetto a windows-1252

L'unica differenza tra queste due codifiche è che ISO-8859-1 ha i caratteri di controllo C1 dove windows-1252 ha i caratteri stampabili € ‚ƒ„… † ‡ ˆ ‰ Š ‹ŒŽ ''“ ”• –—˜ ™ š› œžŸ. Ho visto molti file che usano virgolette o trattini ricci, ma nessuno che usa caratteri di controllo C1. Quindi non preoccuparti nemmeno di loro, o ISO-8859-1, rileva invece Windows-1252.

Questo ora ti lascia con una sola domanda.

Come si distingue MacRoman da cp1252?

Questo è molto più complicato.

Caratteri indefiniti

I byte 0x81, 0x8D, 0x8F, 0x90, 0x9D non vengono utilizzati in windows-1252. Se si verificano, supponi che i dati siano MacRoman.

Caratteri identici

I byte 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) sono gli stessi in entrambe le codifiche. Se questi sono gli unici byte non ASCII, non importa se scegli MacRoman o cp1252.

Approccio statistico

Conta le frequenze dei caratteri (NON byte!) Nei dati che sai essere UTF-8. Determina i personaggi più frequenti. Quindi utilizzare questi dati per determinare se i caratteri cp1252 o MacRoman sono più comuni.

Ad esempio, in una ricerca che ho appena eseguito su 100 articoli di Wikipedia in inglese casuali, i caratteri non ASCII più comuni sono ·•–é°®’èö—. Sulla base di questo fatto,

  • I byte 0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9 o 0xF6 suggeriscono windows-1252.
  • I byte 0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5 o 0xE1 suggeriscono MacRoman.

Conta i byte suggeriti da cp1252 e dai byte suggeriti da MacRoman e segui il valore maggiore.


6
Ho accettato la tua risposta perché nessuno si è presentato meglio e hai fatto un buon lavoro scrivendo proprio i problemi con cui stavo armeggiando. In effetti ho programmi per fiutare quei byte, sebbene tu abbia circa il doppio del numero che mi ero inventato.
Cristo

10
Finalmente sono riuscito a implementarlo. Si scopre che Wikipedia non è un buon dato di allenamento. Da 1k articoli casuali di en.wikipedia, senza contare la sezione LINGUE, ho ottenuto 50k punti di codice unASCII, ma la distribuzione non è credibile: il punto centrale e il punto elenco sono troppo alti, & c & c & c. Quindi ho usato il corpus tutto UTF8 PubMed Open Access, mining + 14M di punti di codice unASCII. Li uso per costruire un modello di frequenza relativa di tutte le codifiche a 8 bit, più elaborato del tuo ma basato su quell'idea. Ciò si rivela altamente predittivo della codifica per testi biomedici, il dominio di destinazione. Dovrei pubblicare questo. Grazie!
tchrist

5
Non ho ancora alcun file MacRoman, ma l'uso di CR come delimitatori di riga non fornirebbe un test utile. Funzionerebbe per le versioni precedenti di Mac OS, anche se non conosco OS9.
Milliways

10

Mozilla nsUniversalDetector (collegamenti Perl: Encode :: Detect / Encode :: Detect :: Detector ) è provato milioni di volte.


Ulteriore documentazione si trova qui: mozilla.org/projects/intl/detectorsrc.html , da lì, suggerisce che se scavi nei documenti puoi trovare i set di caratteri supportati
Joel Berger

@ Joel: ho scavato nella fonte. Era una domanda retorica. x-mac-cyrillicè supportato, x-mac-hebrewviene discusso a lungo nei commenti, x-mac-anything-elsenon viene menzionato.
John Machin

@ John Machin: strano che il cirillico e l'ebraico facciano un cenno, ma nient'altro. Stavo solo inserendo un'altra fonte di documentazione, non avevo letto oltre, grazie per averlo fatto!
Joel Berger

7

Il mio tentativo di una tale euristica (supponendo che tu abbia escluso ASCII e UTF-8):

  • Se da 0x7f a 0x9f non appaiono affatto, probabilmente è ISO-8859-1, perché questi sono codici di controllo usati molto raramente.
  • Se da 0x91 a 0x94 compaiono in lotti, probabilmente è Windows-1252, perché quelle sono le "virgolette intelligenti", di gran lunga i caratteri più probabili in quell'intervallo da utilizzare nel testo inglese. Per essere più sicuri, potresti cercare delle coppie.
  • Altrimenti, è MacRoman, specialmente se vedi molto da 0xd2 a 0xd5 (è lì che si trovano le virgolette tipografiche in MacRoman).

Nota a margine:

Per i file come la sorgente Java in cui non esiste una tale funzionalità interna al file, inserirai la codifica prima dell'estensione, come SomeClass-utf8.java

Non farlo!!

Il compilatore Java si aspetta che i nomi dei file corrispondano ai nomi delle classi, quindi rinominare i file renderà il codice sorgente non compilabile. La cosa corretta sarebbe indovinare la codifica, quindi utilizzare lo native2asciistrumento per convertire tutti i caratteri non ASCII in sequenze di escape Unicode .


7
Stoopid kompilor! No, non possiamo dire alle persone che possono usare solo ASCII; non sono più gli anni '60. Non sarebbe un problema se ci fosse un'annotazione @encoding in modo che il fatto che il sorgente sia in una particolare codifica non fosse costretto a essere memorizzato esternamente al codice sorgente, un difetto davvero idiota di Java di cui né Perl né Python soffrono . Dovrebbe essere nella sorgente. Questo non è però il nostro problema principale; sono le migliaia di *.textfile.
Cristo

3
@tchrist: in realtà non sarebbe poi così difficile scrivere il proprio processore di annotazioni per supportare tale annotazione. Ancora una svista imbarazzante non averlo nell'API standard.
Michael Borgwardt

Anche se Java supportasse @encoding, ciò non garantirebbe che la dichiarazione di codifica sia corretta .
dan04

4
@ dan04: Puoi dire lo stesso della dichiarazione di codifica in XML, HTML o altrove. Ma proprio come con quegli esempi, se fosse definito nell'API standard, la maggior parte degli strumenti che funzionano con il codice sorgente (in particolare gli editor e gli IDE) lo supporterebbero, il che impedirebbe in modo abbastanza affidabile alle persone di creare accidentalmente file la cui codifica dei contenuti non corrisponde il declartion.
Michael Borgwardt

4
"Il compilatore Java si aspetta che i nomi dei file corrispondano ai nomi delle classi." Questa regola si applica solo se il file definisce una classe pubblica di primo livello.
Matthew Flaschen

6

"Perl, C, Java o Python e in quest'ordine": atteggiamento interessante :-)

"abbiamo un buon cambiamento nel sapere se qualcosa è probabilmente UTF-8": in realtà la possibilità che un file contenente testo significativo codificato in qualche altro set di caratteri che utilizza byte con set di bit elevato venga decodificato con successo poiché UTF-8 è incredibilmente piccolo.

Strategie UTF-8 (nella lingua meno preferita):

# 100% Unicode-standard-compliant UTF-8
def utf8_strict(text):
    try:
        text.decode('utf8')
        return True
    except UnicodeDecodeError:
        return False

# looking for almost all UTF-8 with some junk
def utf8_replace(text):
    utext = text.decode('utf8', 'replace')
    dodgy_count = utext.count(u'\uFFFD') 
    return dodgy_count, utext
    # further action depends on how large dodgy_count / float(len(utext)) is

# checking for UTF-8 structure but non-compliant
# e.g. encoded surrogates, not minimal length, more than 4 bytes:
# Can be done with a regex, if you need it

Una volta deciso che non è né ASCII né UTF-8:

I rilevatori di set di caratteri di origine Mozilla di cui sono a conoscenza non supportano MacRoman e in ogni caso non fanno un buon lavoro su set di caratteri a 8 bit soprattutto con l'inglese perché AFAICT dipendono dal controllo se la decodifica ha senso nel dato lingua, ignorando i caratteri di punteggiatura e sulla base di un'ampia selezione di documenti in quella lingua.

Come altri hanno notato, in realtà sono disponibili solo i caratteri di punteggiatura con set di bit elevato per distinguere tra cp1252 e macroman. Suggerirei di addestrare un modello di tipo Mozilla sui propri documenti, non Shakespeare o Hansard o la Bibbia KJV, e prendere in considerazione tutti i 256 byte. Presumo che i tuoi file non contengano markup (HTML, XML, ecc.) - ciò distorcerebbe le probabilità in modo scioccante.

Hai menzionato file che sono principalmente UTF-8 ma non riescono a decodificare. Dovresti anche essere molto sospettoso di:

(1) file che sono presumibilmente codificati in ISO-8859-1 ma contengono "caratteri di controllo" nell'intervallo da 0x80 a 0x9F inclusi ... questo è così diffuso che la bozza dello standard HTML5 dice di decodificare TUTTI i flussi HTML dichiarati come ISO-8859 -1 utilizzando cp1252.

(2) file che decodificano OK come UTF-8 ma l'Unicode risultante contiene "caratteri di controllo" nell'intervallo da U + 0080 a U + 009F inclusi ... questo può derivare dalla transcodifica cp1252 / cp850 (visto che è successo!) / Ecc file da "ISO-8859-1" a UTF-8.

Background: ho un progetto bagnato-domenica-pomeriggio per creare un rilevatore di set di caratteri basato su Python che sia orientato ai file (invece che orientato al web) e legacy ** nfunzioni bene con i set di caratteri a 8 bit inclusi quelli come cp850 e cp437. Non è ancora neanche lontanamente la prima serata. Mi interessano i file di formazione; i tuoi file ISO-8859-1 / cp1252 / MacRoman sono altrettanto "liberi" come ti aspetti che sia la soluzione di codice di qualcuno?


1
il motivo per l'ordinamento della lingua è l'ambiente. La maggior parte delle nostre applicazioni principali tende ad essere in java e le utility minori e alcune applicazioni sono in perl. Abbiamo un po 'di codice qua e là che è in Python. Sono principalmente un programmatore C e perl, almeno per prima scelta, quindi stavo cercando una soluzione java da inserire nella nostra libreria di app, o una libreria perl per lo stesso. Se C, potrei creare uno strato di colla XS per collegarlo all'interfaccia perl, ma non l'ho mai fatto prima in Python.
Cristo

3

Come hai scoperto, non esiste un modo perfetto per risolvere questo problema, perché senza la conoscenza implicita di quale codifica utilizza un file, tutte le codifiche a 8 bit sono esattamente le stesse: una raccolta di byte. Tutti i byte sono validi per tutte le codifiche a 8 bit.

Il meglio che puoi sperare è una sorta di algoritmo che analizza i byte e, in base alle probabilità che un certo byte venga utilizzato in una determinata lingua con una certa codifica, indovinerà quale codifica utilizza i file. Ma questo deve sapere quale lingua utilizza il file e diventa completamente inutile quando si hanno file con codifiche miste.

Al rialzo, se sai che il testo in un file è scritto in inglese, è improbabile che tu noti alcuna differenza a prescindere dalla codifica che decidi di utilizzare per quel file, poiché le differenze tra tutte le codifiche menzionate sono tutte localizzate in le parti delle codifiche che specificano caratteri normalmente non utilizzati nella lingua inglese. Potresti avere alcuni problemi in cui il testo utilizza una formattazione speciale, o versioni speciali di punteggiatura (CP1252 ha diverse versioni dei caratteri delle virgolette per esempio), ma per l'essenza del testo probabilmente non ci saranno problemi.


1

Se riesci a rilevare ogni codifica TRANNE per macroman, sarebbe logico presumere che quelle che non possono essere decifrate siano in macroman. In altre parole, crea semplicemente un elenco di file che non possono essere elaborati e gestiscili come se fossero macroman.

Un altro modo per ordinare questi file sarebbe creare un programma basato su server che consenta agli utenti di decidere quale codifica non è confusa. Certo, sarebbe all'interno dell'azienda, ma con 100 dipendenti che ne fanno alcuni ogni giorno, avrai migliaia di file fatti in pochissimo tempo.

Infine, non sarebbe meglio convertire tutti i file esistenti in un unico formato e richiedere che i nuovi file siano in quel formato.


5
Divertente! Quando ho letto per la prima volta questo commento dopo essere stato interrotto per 30 minuti, ho letto "macroman" come "macro man" e non ho effettuato la connessione con MacRoman finché non ho eseguito una ricerca di quella stringa per vedere se l'OP lo aveva menzionato
Adrian Pronk

+1 questa risposta è piuttosto interessante. non sono sicuro che sia un'idea buona o cattiva. qualcuno può pensare a una codifica esistente che potrebbe anche passare inosservata? è probabile che ce ne sia uno in futuro?
nome utente

1

Qualcun altro ha avuto questo problema di un miliardo di file di testo legacy codificati in modo casuale? In tal caso, come hai tentato di risolverlo e quanto hai avuto successo?

Attualmente sto scrivendo un programma che traduce i file in XML. Deve rilevare automaticamente il tipo di ogni file, che è un superset del problema di determinare la codifica di un file di testo. Per determinare la codifica sto usando un approccio bayesiano. Cioè, il mio codice di classificazione calcola una probabilità (verosimiglianza) che un file di testo abbia una codifica particolare per tutte le codifiche che comprende. Il programma seleziona quindi il decoder più probabile. L'approccio bayesiano funziona in questo modo per ogni codifica.

  1. Imposta la probabilità iniziale ( precedente ) che il file si trovi nella codifica, in base alle frequenze di ciascuna codifica.
  2. Esamina ogni byte a turno nel file. Cerca il valore di byte per determinare la correlazione tra quel valore di byte presente e un file che si trova effettivamente in quella codifica. Usa questa correlazione per calcolare una nuova probabilità (a posteriori ) che il file sia nella codifica. Se hai più byte da esaminare, usa la probabilità a posteriori di quel byte come probabilità a priori quando esamini il byte successivo.
  3. Quando arrivi alla fine del file (in realtà guardo solo i primi 1024 byte), la probabilità che hai è la probabilità che il file sia nella codifica.

Emerge che Bayes' teorema diventa molto facile da fare se invece di probabilità di calcolo, di calcolare contenuto informativo , che è il logaritmo della probabilità : info = log(p / (1.0 - p)).

Dovrai calcolare la probabilità a priori iniziale e le correlazioni esaminando un corpus di file che hai classificato manualmente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.