web-dev-qa-db-fra.com

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

Au travail, il semble qu'aucune semaine ne se passe sans une connotation, 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 se terminant par *.txt ou *.text. L'idée est que ces extensions induisent 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 alors vous savez que vous ne savez pas ce que vous avez.

Cependant, nous n'irons pas aussi loin. À la place, vous devrez utiliser un nom de fichier se terminant par l'encodage. Ainsi, 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 l'encodage à l'intérieur du fichier lui-même, comme en Perl ou Python, alors vous devez le faire. Pour les fichiers comme Java où aucune telle fonctionnalité n'existe en interne dans le fichier, vous mettrez l'encodage avant l'extension, comme SomeClass-utf8.Java.

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

Mais pour la saisie, nous devons comprendre comment gérer les milliers de fichiers dans notre base de code nommée *.txt. Nous voulons tous les renommer pour les adapter à 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.

Celles-ci sont variées 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 nous sommes un bon changement de savoir si quelque chose est probablement UTF-8, nous sommes perplexes sur les encodages 8 bits. Parce que nous fonctionnons dans un environnement mixte Unix (Solaris, Linux, Darwin) avec la plupart des ordinateurs de bureau étant des Mac, nous en avons pas mal fichiers MacRoman ennuyeux. Et ce sont surtout des problèmes.

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

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

un fichier est dans, et je n'ai pas trouvé un programme ou une bibliothèque qui peut distinguer de manière fiable entre ces trois encodages 8 bits différents. Nous avons probablement plus d'un millier de fichiers MacRoman seuls, donc quel que soit le détecteur de jeu de caractères que nous utilisons doit pouvoir les détecter. Rien de ce que j'ai regardé ne peut gérer l'astuce. J'avais de grands espoirs pour la bibliothèque de détecteurs de jeux de caractères IC , mais elle ne peut pas gérer MacRoman. J'ai également regardé les modules pour faire le même genre de choses à la fois en Perl et en Python, mais encore et encore c'est toujours la même histoire: pas de support pour détecter 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 contiennent plus de 99% de texte en anglais; il y en a quelques autres dans d'autres langues, mais pas beaucoup.

S'il s'agit du code de bibliothèque, notre préférence de langue est qu'il soit en Perl, C, Java ou Python, et dans cet ordre. S'il ne s'agit que d'un programme, nous ne nous soucions pas vraiment de la langue dans laquelle il se trouve tant qu'il est fourni en source complète, s'exécute sur Unix et n'est pas encombré.

Quelqu'un d'autre a-t-il eu ce problème d'un zillion de fichiers texte hérités encodés de manière aléatoire? Si oui, comment avez-vous tenté de le résoudre et quel a été votre succès? C'est l'aspect le plus important de ma question, mais je voudrais également 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é de faire appliquer cela sur une base institutionnelle, et si oui, ça 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 n'avez pas assez de données pour continuer. Heureusement, nos fichiers sont rarement petits. Hormis le fichier aléatoire README, la plupart sont dans la plage de taille de 50k à 250k, et beaucoup sont plus grands. Tout ce qui dépasse quelques K est garanti en anglais.

Le domaine problématique étant l’exploration de texte biomédicale, nous traitons donc parfois des corpus étendus et extrêmement volumineux, comme tous les dépôts en libre accès de PubMedCentral. Un fichier assez volumineux est le BioThesaurus 6.0, à 5,7 gigaoctets. Ce fichier est particulièrement ennuyeux car il est presque tout UTF-8. Cependant, certains numbskull sont allés et ont collé quelques lignes qui sont dans un codage 8 bits - Microsoft CP1252, je crois. Cela prend un certain temps avant de voyager avec celui-ci. :(

99
tchrist

Tout d'abord, les cas faciles:

ASCII

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

UTF-8

Si vos données sont validées en UTF-8, alors vous pouvez le supposer en toute sécurité is 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 l'ISO-8859-1 a les caractères de contrôle C1 où windows-1252 a les caractères imprimables € ‚ƒ„… † ‡ ˆ ‰ Š ‹ŒŽ ''“ ”• –—˜ ™ š› œžŸ. J'ai vu de nombreux fichiers qui utilisent des guillemets ou des tirets, mais aucun qui utilise des caractères de contrôle C1. Alors ne vous embêtez pas avec eux, ou ISO-8859-1, détectez simplement windows-1252 à la place.

Cela vous laisse maintenant avec une seule question.

Comment distinguez-vous MacRoman de cp1252?

C'est beaucoup plus délicat.

Caractères indé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.

Personnages identiques

Les octets 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) se trouvent être les mêmes dans les deux codages. S'il s'agit des seuls octets non ASCII, peu importe que vous choisissiez MacRoman ou cp1252.

Approche statistique

Comptez les fréquences de caractère (PAS d'octet!) 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.

85
dan04
10
daxim

Ma tentative d'une telle heuristique (en supposant que vous avez 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 beaucoup, 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 certain, vous pouvez rechercher des paires.
  • Sinon, c'est MacRoman, surtout si vous voyez beaucoup de 0xd2 à 0xd5 (c'est là que les guillemets typographiques sont dans MacRoman).

Note latérale:

Pour les fichiers comme Java source où aucune telle fonctionnalité n'existe en interne dans le fichier, vous mettrez l'encodage avant l'extension, comme SomeClass-utf8.Java

Ne faites pas ça !!

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

7
Michael Borgwardt

"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 codé dans un autre jeu de caractères qui utilise des octets à bit élevé décode avec succès car UTF-8 est très 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 jeu de caractères Mozilla-Origin que je connais ne prennent pas en charge MacRoman et en tout cas ne font pas du bon travail sur les jeux de caractères 8 bits, en particulier avec l'anglais, car AFAICT, ils dépendent de la vérification de la pertinence du décodage dans les données données. langue, en ignorant les caractères de ponctuation, et basé sur une large sélection de documents dans cette langue.

Comme d'autres l'ont remarqué, vous n'avez vraiment que les 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 de prendre en compte les 256 octets. Je suppose que vos fichiers ne contiennent aucun balisage (HTML, XML, etc.) - ce qui fausserait les probabilités quelque chose de choquant.

Vous avez mentionné des fichiers qui sont pour la plupart UTF-8 mais ne parviennent pas à décoder. Vous devez également vous méfier:

(1) fichiers prétendument encodés en ISO-8859-1 mais contenant des "caractères de contrôle" compris entre 0x80 et 0x9F inclus ... ceci est si répandu que le projet de norme HTML5 dit de décoder TOUS flux HTML déclarés ISO-8859-1 en utilisant cp1252.

(2) fichiers qui décodent OK en 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 que cela se produit!)/Etc fichiers de "ISO-8859-1" à UTF-8.

Contexte: J'ai un projet le dimanche après-midi humide pour créer un détecteur de jeu de caractères basé sur Python qui est orienté fichier (au lieu de Web) et fonctionne bien avec des jeux de caractères 8 bits, y compris legacy ** n ceux comme cp850 et cp437. Ce n'est pas encore l'heure de grande écoute. Je suis intéressé par les dossiers de formation; vos fichiers ISO-8859-1/cp1252/MacRoman sont-ils aussi "non encombrés" que vous attendez de la solution de code de quiconque?

6
John Machin

Comme vous l'avez découvert, il n'y a pas de moyen parfait pour résoudre ce problème, car sans la connaissance implicite de l'encodage utilisé par un fichier, tous les encodages 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 est une sorte d'algorithme qui analyse les octets et, basé sur les probabilités qu'un certain octet soit utilisé dans une certaine langue avec un certain codage, devinera quel codage 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.

À la hausse, si vous savez que le texte d'un fichier est écrit en anglais, vous ne remarquerez probablement aucune 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 qui ne sont normalement pas utilisés en anglais. Vous pouvez avoir des problèmes lorsque le texte utilise une mise en forme spéciale ou des versions spéciales de ponctuation (CP1252 a plusieurs versions des guillemets par exemple), mais pour l'essentiel du texte, il n'y aura probablement aucun problème.

3
Epcylon

Quelqu'un d'autre a-t-il eu ce problème d'un zillion de fichiers texte hérités encodés de manière aléatoire? Si oui, comment avez-vous tenté de le résoudre et quel a été votre succès?

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 détermination de l'encodage d'un fichier texte. Pour déterminer l'encodage, j'utilise une approche bayésienne. Autrement dit, mon code de classification calcule une probabilité (probabilité) qu'un fichier texte possède un codage particulier pour tous les codages qu'il comprend. Le programme sélectionne ensuite le décodeur le plus probable. L'approche bayésienne fonctionne ainsi pour chaque encodage.

  1. Définissez la probabilité initiale (précédente) que le fichier se trouve dans le codage, en fonction des fréquences de chaque codage.
  2. Examinez tour à tour chaque octet 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 cet encodage. Utilisez cette corrélation pour calculer une nouvelle probabilité (postérieure) que le fichier se trouve dans l'encodage. 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 proabilité que vous avez est la probabilité que le fichier soit dans l'encodage.

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

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

1
Raedwald

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 dans macroman. En d'autres termes, faites simplement une liste des fichiers qui n'ont pas pu être traités et traitez-les comme s'ils étaient macroman.

Une autre façon de trier ces fichiers serait de créer un programme basé sur un serveur qui permet aux utilisateurs de décider quel encodage n'est pas altéré. 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.

1
Eric Pauley