Fixant un fichier composé de UTF-8 et de Windows-1252

j'ai une application qui produit un fichier UTF-8, mais certains éléments sont mal encodés. Certains caractères sont codés iso-8859-1 alias iso-latin-1 ou cp1252 alias Windows-1252. Est-il un moyen de récupérer le texte d'origine?

11
demandé sur ikegami 2015-02-23 22:33:02

3 réponses

Oui!

évidemment, il est préférable de corriger le programme qui crée le fichier, mais ce n'est pas toujours possible. Ce qui suit sont les deux solutions.

Une ligne peut contenir un mélange de codages

Encodage::FixLatin fournit une fonction nommée fix_latin qui décode le texte qui se compose d'un mélange de UTF-8, iso-8859-1, cp1252 et US-ASCII.

$ perl -e'
   use Encoding::FixLatin qw( fix_latin );
   $bytes = "\xD0 \x92 \xD0\x92\n";
   $text = fix_latin($bytes);
   printf("U+%v04X\n", $text);
'
U+00D0.0020.2019.0020.0412.000A

les heuristiques sont employées, mais elles sont assez fiables. Seulement les suivants les cas ne fonctionne pas:



  • [ÀÁÂÃÄÅÁÍÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß]

    encodé selon iso-8859-1 ou cp1252, suivi de l'un des

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY>®°±23µ¶·1º"¼½¾¿]

    encodé selon iso-8859-1 ou cp1252.



  • [àáâãäåçñèéêëìíîï]

    encodé selon iso-8859-1 ou cp1252, suivi de deux

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY>®°±23µ¶·1º"¼½¾¿]

    encodé selon iso-8859-1 ou cp1252.



  • [ðñòóôõö÷]

    encodé selon iso-8859-1 ou cp1252, suivi de deux de

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY>®°±23µ¶·1º"¼½¾¿]

    encodé selon iso-8859-1 ou cp1252.

Le même résultat peut être produit à l'aide du module de base Encoder, bien que j'imagine que c'est un peu plus lent que L'encodage::FixLatin avec encodage::FixLatin::XS installé.

$ perl -e'
   use Encode qw( decode_utf8 encode_utf8 decode );
   $bytes = "\xD0 \x92 \xD0\x92\n";
   $text = decode_utf8($bytes, sub { encode_utf8(decode("cp1252", chr($_[0]))) });
   printf("U+%v04X\n", $text);
'
U+00D0.0020.2019.0020.0412.000A

Chaque ligne n'utilise qu'un seul encodage

fix_latin fonctionne au niveau des personnages. S'il est connu que chaque ligne est entièrement codée à L'aide de L'une des lignes UTF-8, iso-8859-1, cp1252 ou US-ASCII, vous pouvez rendre le processus encore plus fiable en vérifiant si la ligne est valide UTF-8.

$ perl -e'
   use Encode qw( decode );
   for $bytes ("\xD0 \x92 \xD0\x92\n", "\xD0\x92\n") {
      if (!eval {
         $text = decode("UTF-8", $bytes, Encode::FB_CROAK|Encode::LEAVE_SRC);
         1  # No exception
      }) {
         $text = decode("cp1252", $bytes);
      }

      printf("U+%v04X\n", $text);
   }
'
U+00D0.0020.2019.0020.00D0.2019.000A
U+0412.000A

les heuristiques sont employées, mais elles sont très fiables. Ils n'échoueront que si ce qui suit est vrai pour une ligne donnée:

  • la ligne est codée selon iso-8859-1 ou cp1252,

  • Au moins un des

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY> ® ° ±23µ#·1º " ¼½¼ ¿ÀÁÂÃÄÅÈÉÊËÌÌÍÎÏÐÑÒÓÔÕÖ×Øùúüýþßàáâãäåäèéêëìíîïðñòóôõö÷]

    est présent dans le ligne,

  • toutes les occurrences de

    [ÀÁÂÃÄÅÁÍÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß]

    sont toujours suivis par exactement un des

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY> ® ° ±23µ # ·1º " ¼½ þ],

  • toutes les occurrences de

    [àáâãäåçñèéêëìíîï]

    sont toujours suivis par exactement deux

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY> ® ° ±23µ # ·1º " ¼½ þ],

  • toutes les occurrences de

    [ðñòóôõö÷]

    sont toujours suivis par exactement trois

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY> ® ° ±23µ # ·1º " ¼½ þ],

  • Aucun

    [øùúûüýþÿ]

    sont présents dans la ligne, et

  • aucune

    [€'ƒ"...†‡ˆ‰Š"ŒŽ"""•--™š"œžŸ<NBSP>¡¢£¤¥¦§©ª"<SHY>®°±23µ¶·1º"¼½¾¿]

    sont présents dans la ligne, sauf lorsque mentionné précédemment.


Notes:

  • encodage::FixLatin installe l'outil en ligne de commande fix_latin pour convertir des fichiers, et il serait trivial d'en écrire un en utilisant la seconde approche.
  • fix_latin (à la fois la fonction et le fichier) peut être accéléré par l'installation de encodage:: FixLatin:: XS.
  • la même approche peut être utilisée pour les mélanges D'UTF-8 avec d'autres encodages à un octet. La fiabilité doit être similaire, mais elle peut varier.
11
répondu ikegami 2015-02-24 22:12:06
Unicode:: UTF8. Avec Unicode:: UTF8 c'est trivial en utilisant l'option de repli dans Unicode:: UTF8:: decode_utf8 ().

use Unicode::UTF8 qw[decode_utf8];
use Encode        qw[decode];

print "UTF-8 mixed with Latin-1 (ISO-8859-1):\n";
for my $octets ("\xD0 \x92 \xD0\x92\n", "\xD0\x92\n") {
    no warnings 'utf8';
    printf "U+%v04X\n", decode_utf8($octets, sub { $_[0] });
}

print "\nUTF-8 mixed with CP-1252 (Windows-1252):\n";
for my $octets ("\xD0 \x92 \xD0\x92\n", "\xD0\x92\n") {
    no warnings 'utf8';
    printf "U+%v04X\n", decode_utf8($octets, sub { decode('CP-1252', $_[0]) });
}

Sortie:

UTF-8 mixed with Latin-1 (ISO-8859-1):
U+00D0.0020.0092.0020.0412.000A
U+0412.000A

UTF-8 mixed with CP-1252 (Windows-1252):
U+00D0.0020.2019.0020.0412.000A
U+0412.000A

Unicode:: UTF8 est écrit en C/XS et n'invoque le callback / fallback qu'en cas de rencontre avec une séquence UTF-8 mal formée.

5
répondu chansen 2015-02-23 20:20:38

récemment je suis tombé sur des fichiers avec un mélange sévère de UTF-8, CP1252, et UTF-8 encodé, puis interprété comme CP1252, puis celui encodé comme UTF-8 à nouveau, qui interprété comme CP1252 à nouveau, et ainsi de suite.

j'ai écrit le code ci-dessous, qui a bien fonctionné pour moi. Il recherche des séquences typiques de bytes UTF-8, même si certains de ces bytes ne sont pas UTF-8, mais la représentation Unicode de L'équivalent CP1252 byte.

my %cp1252Encoding = (
# replacing the unicode code with the original CP1252 code
# see e.g. http://www.i18nqa.com/debug/table-iso8859-1-vs-windows-1252.html
"\x{20ac}" => "\x80",
"\x{201a}" => "\x82",
"\x{0192}" => "\x83",
"\x{201e}" => "\x84",
"\x{2026}" => "\x85",
"\x{2020}" => "\x86",
"\x{2021}" => "\x87",
"\x{02c6}" => "\x88",
"\x{2030}" => "\x89",
"\x{0160}" => "\x8a",
"\x{2039}" => "\x8b",
"\x{0152}" => "\x8c",
"\x{017d}" => "\x8e",

"\x{2018}" => "\x91",
"\x{2019}" => "\x92",
"\x{201c}" => "\x93",
"\x{201d}" => "\x94",
"\x{2022}" => "\x95",
"\x{2013}" => "\x96",
"\x{2014}" => "\x97",
"\x{02dc}" => "\x98",
"\x{2122}" => "\x99",
"\x{0161}" => "\x9a",
"\x{203a}" => "\x9b",
"\x{0153}" => "\x9c",
"\x{017e}" => "\x9e",
"\x{0178}" => "\x9f",
);
my $re = join "|", keys %cp1252Encoding;
$re = qr/$re/;
my %cp1252Decoding = reverse % cp1252Encoding;
my $cp1252Characters = join "|", keys %cp1252Decoding;

sub decodeUtf8
{
    my ($str) = @_;

    $str =~ s/$re/ $cp1252Encoding{$&} /eg;
    utf8::decode($str);
    return $str;
}

sub fixString
{
    my ($str) = @_;

    my $r = qr/[\x80-\xBF]|$re/;

    my $current;
    do {
        $current = $str;

        # If this matches, the string is likely double-encoded UTF-8. Try to decode
        $str =~ s/[\xF0-\xF7]$r$r$r|[\xE0-\xEF]$r$r|[\xC0-\xDF]$r/ decodeUtf8($&) /eg;

    } while ($str ne $current);

    # decodes any possible left-over cp1252 codes to Unicode
    $str =~ s/$cp1252Characters/ $cp1252Decoding{$&} /eg;
    return $str;
}

cela a des limites similaires à la réponse d'ikegami, sauf que les mêmes limitations sont également applicables aux chaînes encodées UTF-8.

-1
répondu fishinear 2017-12-06 10:58:15