Comment deviner de manière fiable l'encodage entre MacRoman, CP1252, Latin1, UTF-8 et ASCII

99

Au travail, il semble qu'aucune semaine ne passe sans une conniption, une calamité ou une catastrophe liées à l'encodage. Le problème vient généralement des programmeurs qui pensent pouvoir traiter de manière fiable un fichier «texte» sans spécifier l'encodage. Mais tu ne peux pas.

Il a donc été décidé d'interdire désormais aux fichiers d'avoir des noms qui se terminent par *.txtou *.text. L'idée est que ces extensions induisent en erreur le programmeur occasionnel dans une complaisance sourde concernant les encodages, ce qui conduit à une mauvaise manipulation. Il serait presque préférable de ne pas avoir d'extension du tout, car au moins, vous savez que vous ne savez pas ce que vous avez.

Cependant, nous ne sommes pas prêts à aller aussi loin. À la place, vous devrez utiliser un nom de fichier qui se termine par le codage. Donc , pour les fichiers texte, par exemple, ce serait quelque chose comme README.ascii, README.latin1, README.utf8, etc.

Pour les fichiers qui nécessitent une extension particulière, si l'on peut spécifier le codage à l'intérieur du fichier lui-même, comme en Perl ou Python, alors vous devez le faire. Pour les fichiers comme Java source où aucune fonctionnalité de ce type n'existe à l'intérieur du fichier, vous placerez l'encodage avant l'extension, par exemple SomeClass-utf8.java.

Pour la sortie, UTF-8 doit être fortement préféré.

Mais pour entrer, nous devons comprendre comment gérer les milliers de fichiers nommés dans notre base de code *.txt. Nous voulons tous les renommer pour qu'ils correspondent à notre nouvelle norme. Mais nous ne pouvons pas tous les observer. Nous avons donc besoin d'une bibliothèque ou d'un programme qui fonctionne réellement.

Ceux-ci sont différents en ASCII, ISO-8859-1, UTF-8, Microsoft CP1252 ou Apple MacRoman. Bien que nous sachions que nous pouvons dire si quelque chose est ASCII, et que nous sommes un bon changement de savoir si quelque chose est probablement UTF-8, nous sommes perplexes au sujet des encodages 8 bits. Parce que nous fonctionnons dans un environnement Unix mixte (Solaris, Linux, Darwin) avec la plupart des bureaux étant des Mac, nous avons pas mal de fichiers MacRoman ennuyeux. Et ceux-ci sont particulièrement problématiques.

Depuis un certain temps, je cherche un moyen de déterminer par programme lequel des

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

il y a un fichier et je n'ai pas trouvé de programme ou de bibliothèque capable de distinguer de manière fiable les trois encodages 8 bits différents. Nous avons probablement plus d'un millier de fichiers MacRoman à eux seuls, donc quel que soit le détecteur de jeu de caractères que nous utilisons, il doit être capable de les détecter. Rien de ce que j'ai regardé ne peut gérer le truc. J'avais de grands espoirs pour la bibliothèque de détecteurs de charset ICU , mais elle ne peut pas gérer MacRoman. J'ai aussi regardé des modules pour faire le même genre de chose en Perl et Python, mais encore et encore c'est toujours la même histoire: pas de support pour la détection de MacRoman.

Ce que je recherche donc, c'est une bibliothèque ou un programme existant qui détermine de manière fiable dans lequel de ces cinq encodages se trouve un fichier - et de préférence plus que cela. En particulier, il doit faire la distinction entre les trois encodages 3 bits que j'ai cités, en particulier MacRoman . Les fichiers sont à plus de 99% de texte en anglais; il y en a quelques-uns dans d'autres langues, mais pas beaucoup.

S'il s'agit de code de bibliothèque, notre préférence de langage est qu'il soit en Perl, C, Java ou Python, et dans cet ordre. S'il ne s'agit que d'un programme, alors nous ne nous soucions pas vraiment de la langue dans laquelle il se trouve tant qu'il est livré dans son intégralité, qu'il fonctionne sous Unix et qu'il est totalement libre.

Quelqu'un d'autre a-t-il eu ce problème d'un zillion de fichiers texte hérités encodés au hasard? Si oui, comment avez-vous tenté de le résoudre et dans quelle mesure avez-vous réussi? C'est l'aspect le plus important de ma question, mais je suis également intéressé à savoir si vous pensez qu'encourager les programmeurs à nommer (ou renommer) leurs fichiers avec l'encodage réel de ces fichiers nous aidera à éviter le problème à l'avenir. Quelqu'un a-t-il déjà essayé d'appliquer cela sur une base institutionnelle, et si oui, cela a-t-il réussi ou non, et pourquoi?

Et oui, je comprends parfaitement pourquoi on ne peut garantir une réponse définitive étant donné la nature du problème. C'est particulièrement le cas avec les petits fichiers, où vous ne disposez pas de suffisamment de données pour continuer. Heureusement, nos fichiers sont rarement petits. Mis à part le READMEfichier aléatoire , la plupart ont une taille comprise entre 50k et 250k, et beaucoup sont plus grands. Tout ce qui dépasse quelques K est garanti en anglais.

Le domaine du problème est l'exploration de texte biomédicale, nous avons donc parfois affaire à des corpus étendus et extrêmement volumineux, comme tout le référentiel Open Access de PubMedCentral. Un fichier assez volumineux est le BioThesaurus 6.0, à 5,7 gigaoctets. Ce fichier est particulièrement ennuyeux car il est presque entièrement en UTF-8. Cependant, certains numbskull y sont allés et y ont collé quelques lignes qui sont dans un encodage 8 bits - Microsoft CP1252, je crois. Cela prend un certain temps avant de vous lancer sur celui-là. :(

tchrist
la source
Voir stackoverflow.com/questions/4255305/… pour une solution
mpenkov

Réponses:

86

Tout d'abord, les cas faciles:

ASCII

Si vos données ne contiennent pas d'octets au-dessus de 0x7F, c'est ASCII. (Ou un encodage ISO646 7 bits, mais ceux-ci sont très obsolètes.)

UTF-8

Si vos données sont valides en UTF-8, vous pouvez supposer en toute sécurité qu'il s'agit de UTF-8. En raison des règles de validation strictes de l'UTF-8, les faux positifs sont extrêmement rares.

ISO-8859-1 contre Windows-1252

La seule différence entre ces deux encodages est que ISO-8859-1 a les caractères de contrôle C1 où windows-1252 a les caractères imprimables € ‚ƒ„… † ‡ ˆ ‰ Š ‹ŒŽ ''“ ”• –—˜ ™ š› œžŸ. J'ai vu beaucoup de fichiers qui utilisent des guillemets ou des tirets, mais aucun n'utilise des caractères de contrôle C1. Alors ne vous embêtez même pas avec eux, ou ISO-8859-1, détectez simplement windows-1252 à la place.

Cela ne vous laisse plus qu'une seule question.

Comment distinguez-vous MacRoman de cp1252?

C'est beaucoup plus délicat.

Caractères non définis

Les octets 0x81, 0x8D, 0x8F, 0x90, 0x9D ne sont pas utilisés dans Windows-1252. S'ils se produisent, supposez que les données sont MacRoman.

Caractères identiques

Les octets 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) se trouvent être les mêmes dans les deux encodages. Si ce sont les seuls octets non ASCII, peu importe que vous choisissiez MacRoman ou cp1252.

Approche statistique

Comptez les fréquences de caractères (PAS d'octets!) Dans les données que vous savez être UTF-8. Déterminez les caractères les plus fréquents. Utilisez ensuite ces données pour déterminer si les caractères cp1252 ou MacRoman sont plus courants.

Par exemple, dans une recherche que je viens d'effectuer sur 100 articles Wikipedia anglais aléatoires, les caractères non ASCII les plus courants sont ·•–é°®’èö—. Sur la base de ce fait,

  • Les octets 0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9 ou 0xF6 suggèrent windows-1252.
  • Les octets 0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5 ou 0xE1 suggèrent MacRoman.

Comptez les octets suggérant cp1252 et les octets suggérant MacRoman, et choisissez celui qui est le plus grand.

dan04
la source
6
J'ai accepté votre réponse car aucune meilleure ne s'est présentée, et vous avez fait du bon travail en écrivant les problèmes mêmes que j'avais bricolés. J'ai en effet des programmes pour flairer ces octets, bien que vous en ayez environ deux fois le nombre que j'avais moi-même trouvé.
tchrist
10
Finalement réussi à mettre en œuvre cela. Il s'avère que Wikipédia n'est pas de bonnes données d'entraînement. De 1k articles aléatoires en.wikipedia, sans compter la section LANGUES, j'ai obtenu 50k points de code unASCII, mais la distribution n'est pas crédible: le point central et la puce sont trop élevés, & c & c & c. J'ai donc utilisé le corpus PubMed Open Access entièrement UTF8, minant + 14 millions de points de code unASCII. Je les utilise pour construire un modèle de fréquence relative de tous les encodages 8 bits, plus sophistiqué que le vôtre mais basé sur cette idée. Cela s'avère hautement prédictif de l'encodage des textes biomédicaux, le domaine cible. Je devrais publier ceci. Merci!
tchrist
5
Je n'ai toujours pas de fichiers MacRoman, mais l'utilisation de CR comme délimiteurs de ligne ne fournirait-elle pas un test utile. Cela fonctionnerait pour les anciennes versions de Mac OS, même si je ne connais pas OS9.
Milliways
10

Mozilla nsUniversalDetector (liaisons Perl: Encode :: Detect / Encode :: Detect :: Detector ) est prouvé des millions de fois.

Daxim
la source
Plus de documentation se trouve ici: mozilla.org/projects/intl/detectorsrc.html , à partir de là, cela suggère que si vous creusez dans la documentation, vous pouvez trouver les jeux de caractères pris en charge
Joel Berger
@Joel: J'ai creusé la source. C'était une question rhétorique. x-mac-cyrillicest pris en charge, x-mac-hebrewest discuté en détail dans les commentaires, x-mac-anything-elsene reçoit pas de mention.
John Machin
@John Machin: étrange que le cyrillique et l'hébreu obtiennent un signe de tête, mais rien d'autre. Je ne faisais que lancer une autre source de documentation, je n'avais pas lu plus loin, merci de l'avoir fait!
Joel Berger
7

Ma tentative d'une telle heuristique (en supposant que vous ayez exclu ASCII et UTF-8):

  • Si 0x7f à 0x9f n'apparaissent pas du tout, c'est probablement ISO-8859-1, car ce sont des codes de contrôle très rarement utilisés.
  • Si 0x91 à 0x94 apparaissent souvent, c'est probablement Windows-1252, car ce sont les "guillemets intelligents", de loin les caractères les plus susceptibles d'être utilisés dans le texte anglais. Pour être plus sûr, vous pouvez rechercher des paires.
  • Sinon, c'est MacRoman, surtout si vous voyez beaucoup de 0xd2 à 0xd5 (c'est là que se trouvent les guillemets typographiques dans MacRoman).

Note latérale:

Pour les fichiers comme Java source où aucune fonctionnalité de ce type n'existe à l'intérieur du fichier, vous placerez l'encodage avant l'extension, comme SomeClass-utf8.java

Ne faites pas cela!!

Le compilateur Java s'attend à ce que les noms de fichiers correspondent aux noms de classe, donc renommer les fichiers rendra le code source non compilable. La bonne chose serait de deviner l'encodage, puis d'utiliser l' native2asciioutil pour convertir tous les caractères non ASCII en séquences d'échappement Unicode .

Michael Borgwardt
la source
7
Stoopid kompilor! Non, nous ne pouvons pas dire aux gens qu'ils ne peuvent utiliser que l'ASCII; ce ne sont plus les années 60. Ce ne serait pas un problème s'il y avait une annotation @encoding afin que le fait que la source soit dans un encodage particulier ne soit pas obligé d'être stocké en externe au code source, une lacune vraiment idiote de Java dont ni Perl ni Python ne souffrent . Cela devrait être dans la source. Ce n'est cependant pas notre principal problème; ce sont les milliers de *.textfichiers.
tchrist
3
@tchrist: En fait, il ne serait pas si difficile d'écrire votre propre processeur d'annotations pour prendre en charge une telle annotation. Encore un oubli embarrassant de ne pas l'avoir dans l'API standard.
Michael Borgwardt
Même si Java prenait en charge @encoding, cela ne garantirait pas que la déclaration de codage soit correcte .
dan04
4
@ dan04: Vous pouvez dire la même chose de la déclaration d'encodage en XML, HTML ou n'importe où ailleurs. Mais tout comme pour ces exemples, s'il était défini dans l'API standard, la plupart des outils qui fonctionnent avec le code source (en particulier les éditeurs et les IDE) le prendraient en charge, ce qui empêcherait de manière assez fiable les gens de créer accidentellement des fichiers dont le codage du contenu ne correspond pas la déclinaison.
Michael Borgwardt
4
"Le compilateur Java s'attend à ce que les noms de fichiers correspondent aux noms de classes." Cette règle s'applique uniquement si le fichier définit une classe publique de niveau supérieur.
Matthew Flaschen
6

"Perl, C, Java ou Python, et dans cet ordre": attitude intéressante :-)

«Nous avons un bon changement de savoir si quelque chose est probablement UTF-8»: En fait, la chance qu'un fichier contenant du texte significatif encodé dans un autre jeu de caractères qui utilise des octets à haut bit soit décodé avec succès car UTF-8 est extrêmement petit.

Stratégies UTF-8 (dans la langue la moins préférée):

# 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

Une fois que vous avez décidé que ce n'est ni ASCII ni UTF-8:

Les détecteurs de jeux de caractères d'origine Mozilla que je connais ne prennent pas en charge MacRoman et ne font en aucun cas un bon travail sur les jeux de caractères 8 bits, en particulier avec l'anglais, car ils dépendent de vérifier si le décodage a du sens dans le langue, ignorant les caractères de ponctuation et basée sur une large sélection de documents dans cette langue.

Comme d'autres l'ont fait remarquer, vous ne disposez en réalité que des caractères de ponctuation à jeu de bits élevé pour faire la distinction entre cp1252 et macroman. Je suggérerais de former un modèle de type Mozilla sur vos propres documents, pas Shakespeare ou Hansard ou la Bible KJV, et en tenant compte des 256 octets. Je suppose que vos fichiers ne contiennent aucun balisage (HTML, XML, etc.) - cela déformerait les probabilités, quelque chose de choquant.

Vous avez mentionné des fichiers qui sont pour la plupart UTF-8 mais qui ne parviennent pas à décoder. Vous devez également être très méfiant envers:

(1) fichiers qui sont prétendument encodés en ISO-8859-1 mais contiennent des "caractères de contrôle" dans la plage 0x80 à 0x9F inclus ... ceci est si répandu que le projet de norme HTML5 dit de décoder TOUS les flux HTML déclarés comme ISO-8859 -1 en utilisant cp1252.

(2) fichiers qui décodent OK comme UTF-8 mais l'Unicode résultant contient des "caractères de contrôle" dans la plage U + 0080 à U + 009F inclus ... cela peut résulter du transcodage cp1252 / cp850 (vu cela se produire!) / Etc fichiers de "ISO-8859-1" à UTF-8.

Contexte: J'ai un projet humide-dimanche après-midi pour créer un détecteur de jeu de caractères basé sur Python qui est orienté fichier (au lieu d'être orienté Web) et fonctionne bien avec les jeux de caractères 8 bits, y compris legacy ** nceux comme cp850 et cp437. C'est encore loin d'être aux heures de grande écoute. Je suis intéressé par les fichiers de formation; Vos fichiers ISO-8859-1 / cp1252 / MacRoman sont-ils aussi "libres" que vous vous attendez à ce que la solution de code de quiconque soit?

John Machin
la source
1
la raison de la commande des langues est l'environnement. La plupart de nos applications principales ont tendance à être en java et les utilitaires mineurs et certaines applications sont en perl. Nous avons un peu de code ici et là qui est en python. Je suis principalement un programmeur C et perl, du moins par premier choix, donc je cherchais soit une solution java à brancher dans notre bibliothèque d'applications, soit une bibliothèque perl pour la même chose. Si C, je pourrais créer une couche de colle XS pour la connecter à l'interface perl, mais je ne l'ai jamais fait en python auparavant.
tchrist
3

Comme vous l'avez découvert, il n'y a pas de moyen parfait de résoudre ce problème, car sans la connaissance implicite du codage utilisé par un fichier, tous les codages 8 bits sont exactement les mêmes: une collection d'octets. Tous les octets sont valides pour tous les encodages 8 bits.

Le mieux que vous puissiez espérer, c'est une sorte d'algorithme qui analyse les octets, et basé sur les probabilités d'un certain octet utilisé dans une certaine langue avec un certain encodage, devinera quel encodage les fichiers utilisent. Mais cela doit savoir quelle langue le fichier utilise et devient complètement inutile lorsque vous avez des fichiers avec des encodages mixtes.

Par contre, si vous savez que le texte d'un fichier est écrit en anglais, il est peu probable que vous remarquiez une différence quel que soit le codage que vous décidez d'utiliser pour ce fichier, car les différences entre tous les codages mentionnés sont toutes localisées dans les parties des encodages qui spécifient des caractères normalement non utilisés en anglais. Vous pourriez avoir des problèmes lorsque le texte utilise un formatage spécial, ou des versions spéciales de ponctuation (CP1252 a plusieurs versions des caractères de guillemet par exemple), mais pour l'essentiel du texte, il n'y aura probablement aucun problème.

Epcylon
la source
1

Si vous pouvez détecter tous les encodages SAUF pour macroman, il serait logique de supposer que ceux qui ne peuvent pas être déchiffrés sont en macroman. En d'autres termes, faites simplement une liste des fichiers qui n'ont pas pu être traités et gérez-les comme s'ils étaient macroman.

Une autre façon de trier ces fichiers serait de créer un programme basé sur un serveur permettant aux utilisateurs de décider quel encodage n'est pas brouillé. Bien sûr, ce serait au sein de l'entreprise, mais avec 100 employés qui en font quelques-uns chaque jour, vous aurez des milliers de fichiers traités en un rien de temps.

Enfin, ne serait-il pas préférable de simplement convertir tous les fichiers existants dans un seul format et d'exiger que les nouveaux fichiers soient dans ce format.

Eric Pauley
la source
5
Drôle! Quand j'ai lu ce commentaire pour la première fois après avoir été interrompu pendant 30 minutes, j'ai lu "macroman" comme "macro man" et je n'ai pas établi la connexion avec MacRoman avant de lancer une recherche sur cette chaîne pour voir si l'OP l'avait mentionnée
Adrian Pronk
+1 cette réponse est assez intéressante. je ne sais pas si c'est une bonne ou une mauvaise idée. quelqu'un peut-il penser à un encodage existant qui pourrait également ne pas être détecté? y en aura-t-il un dans le futur?
nom d'utilisateur le
1

Quelqu'un d'autre a-t-il eu ce problème d'un zillion de fichiers texte hérités encodés au hasard? Si oui, comment avez-vous tenté de le résoudre et dans quelle mesure avez-vous réussi?

J'écris actuellement un programme qui traduit des fichiers en XML. Il doit détecter automatiquement le type de chaque fichier, ce qui est un sur-ensemble du problème de la détermination du codage d'un fichier texte. Pour déterminer le codage, j'utilise une approche bayésienne. Autrement dit, mon code de classification calcule une probabilité (probabilité) qu'un fichier texte ait un codage particulier pour tous les codages qu'il comprend. Le programme sélectionne alors le décodeur le plus probable. L'approche bayésienne fonctionne comme ceci pour chaque encodage.

  1. Définissez la probabilité initiale ( antérieure ) que le fichier soit dans le codage, en fonction des fréquences de chaque codage.
  2. Examinez chaque octet tour à tour dans le fichier. Recherchez la valeur d'octet pour déterminer la corrélation entre cette valeur d'octet présente et un fichier se trouvant réellement dans ce codage. Utilisez cette corrélation pour calculer une nouvelle probabilité ( postérieure ) que le fichier soit dans le codage. Si vous avez plus d'octets à examiner, utilisez la probabilité postérieure de cet octet comme probabilité antérieure lorsque vous examinez l'octet suivant.
  3. Lorsque vous arrivez à la fin du fichier (je ne regarde en fait que les 1024 premiers octets), la rentabilité que vous avez est la probabilité que le fichier soit dans le codage.

Il apparaît que Bayes théorème devient très facile à faire si au lieu de calcul des probabilités, vous calculer le contenu de l' information , qui est le logarithme des chances : info = log(p / (1.0 - p)).

Vous devrez calculer la probabilité initail priori, et les corrélations, en examinant un corpus de fichiers que vous avez classifié manuellement.

Raedwald
la source