修复由UTF-8和Windows-1252组成的文件

问题描述:

我有一个应用程序生成一个UTF-8文件,但一些内容编码不正确。一些字符被编码为iso-8859-1 aka iso-latin-1或cp1252 aka Windows-1252。是否有恢复原始文本的方法?

I have an application that produces a UTF-8 file, but some of the contents are incorrectly encoded. Some of the characters are encoded as iso-8859-1 aka iso-latin-1 or cp1252 aka Windows-1252. Is there a way of recovering the original text?

是!

显然,最好修复创建文件的程序,但这不总是可能的。以下是两种解决方案。

Obviously, it's better to fix the program creating the file, but that's not always possible. What follows are two solutions.

Encoding :: FixLatin 提供了一个名为 fix_latin 的函数,

Encoding::FixLatin provides a function named fix_latin which decodes text that consists of a mix of UTF-8, iso-8859-1, cp1252 and 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

启发式,但它们相当可靠。只有以下情况会失败:

Heuristics are employed, but they are fairly reliable. Only the following cases will fail:



  • [ÀÁ ÃÄÅÆÇÈÉÊËÌÍÎ xCF;ÐÑÒÓÔÕÖ×ØÙÚÛ ÜÝÞß]
    使用iso-8859-1或cp1252进行编码,然后使用
    [&#x20AC;&#x201A;&#x0192; &#x201E;&#x2026;&#x2020;&#x2021;&#x02C6;&#x2030;&#x0152;&#x017D;&#x2018;&#x2019;&# x201C;&#x201D;&#x2022;&#x2013;&#x2014;&#x02DC;&#x2122;&#x0161;&#x203A;&#x0153;&#x017E;&#x0178; &#xA1;&#xA2;&#xA3;&#xA4;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9; #xAA;&#xAB;&#xAC; < SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2; xB3;&#xB4;&#xB5;&#xB6;&#xB7;&#xB8;&#xB9;&#xBA;&#xBB;&#xBC;&#xBD;&#xBE;&#xBF; ]
    使用iso-8859-1或cp1252编码。

  • One of
    [ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß]
    encoded using iso-8859-1 or cp1252, followed by one of
    [€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿]
    encoded using iso-8859-1 or cp1252.


[&#xE0;&#xE1; &#xE3;&#xE4;&#xE5;&#xE6;&#xE7;&#xE8;&#xE9;&#xEA;&#xEB;&#xEC;&#xED;&#xEE; xEFA];
使用iso-8859-1或cp1252编码,后跟
[&#x20AC;&#x201A;&#x0192;&#x201E;&#x2026;&#x2020; x2021;&#x02C6;&#x2030;&#x0160;&#x2039;&#x0152;&#x017D;&#x2018;&#x2019;&#x201C;&#x201D;&#x2022;&#x2013; &#x2014;&#x02DC;&#x2122;&#x0161;&#x203A;&#x0153;&#x017E;&#x0178; < NBSP> #xA1;&#xA2;&#xA3;&#xA4;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC; < SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5;&#xB6; &#xB7;&#xB8;&#xB9;&#xBA;&#xBB;&#xBC;&#xBD;&#xBE;&#xBF;]
使用iso-8859-1或cp1252进行编码。

One of
[àáâãäåæçèéêëìíîï]
encoded using iso-8859-1 or cp1252, followed by two of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿]
encoded using iso-8859-1 or cp1252.


[&#xF0;&#xF1;&#xF2;&#xF3;&#xF4;&#xF5; xF6;&#xF7;]
使用iso-8859-1或cp1252进行编码,然后使用
[&#x20AC;&#x201A;&#x0192;&#x201E;&#x2026; x2020;&#x2021;&#x02C6;&#x2030;&#x0160;&#x2039;&#x0152;&#x017D;&#x2018;&#x2019;&#x201C;&#x201D;&#x2022; &#x2013;&#x2014;&#x02DC;&#x2122;&#x0161;&#x203A;&#x0153;&#x017E;&#x0178; < NBSP> &#xA1;&#xA2;&#xA3;&#xA4;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC ; < SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5; &#xB6;&#xB7;&#xB8;&#xB9;&#xBA;&#xBB;&#xBC;&#xBD;&#xBE;&#xBF;]
使用iso-8859- 1或cp1252。

One of
[ðñòóôõö÷]
encoded using iso-8859-1 or cp1252, followed by two of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿]
encoded using iso-8859-1 or cp1252.

使用核心模块 Encode ,虽然我想这是一个公平比Encoding :: FixLatin安装Encoding :: FixLatin :: XS。

The same result can be produced using core module Encode, though I imagine this is a fair bit slower than Encoding::FixLatin with Encoding::FixLatin::XS installed.

$ 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



每行使用一个编码



fix_latin 在字符级别工作。如果已知每行都使用UTF-8,iso-8859-1,cp1252或US-ASCII之一完全编码,则可以通过检查行是否为有效的UTF-8来使过程更可靠。

Each line only uses one encoding

fix_latin works on a character level. If it's known that each line is entirely encoded using one of UTF-8, iso-8859-1, cp1252 or US-ASCII, you could make the process even more reliable by check if the line is valid 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

使用启发式方法,但它们非常可靠。只有所有符合以下条件,才会失败:

Heuristics are employed, but they are very reliable. They will only fail if all of the following are true for a given line:


  • 使用iso-8859-1或cp1252编码,

  • The line is encoded using iso-8859-1 or cp1252,


[&#x20AC;&#x201A;&#x0192; #x201E;&#x2026;&#x2020;&#x2021;&#x02C6;&#x2030;&#x0152;&#x017D;&#x2018;&#x2019;&#x201C ;&#x201D;&#x2022;&#x2013;&#x2014;&#x02DC;&#x2122;&#x0161;&#x203A;&#x0153;&#x017E;&#x0178; < NBSP> &#xA1;&#xA2;&#xA3;&#xA4;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9; xAA;&#xAB;&#xAC; < SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2;&#xB3 ;&#xB4;&#xB5;&#xB6;&#xB7;&#xB8;&#xB9;&#xBA;&#xBB;&#xBC;&#xBD;&#xBE;&#xBF; #xC0;&#xC1;&#xC2;&#xC3;&#xC4;&#xC5;&#xC6;&#xC7;&#xC8;&#xC9;&#xCA;&#xCB;&#xCC ;&#xCD;&#xCE;&#xCF;&#xD0;&#xD1;&#xD2;&#xD3;&#xD4;&#xD5;&#xD6;&#xD7;&#xD8; #xD9;&#xDA;&#xDB;&#xDC;&#xDD;&#xDE;&#xDF;&#xE0;&#xE1;&#xE2;&#xE3;&#xE4;&#xE5 ;&#xE6;&#xE7;&#xE8;&#xE9;&#xEA;&#xEB;&#xEC;&#xED;&#xEE;&#xEF;&#xF0;&#xF1; #xF2;&#xF3;&#xF4;&#xF5;&#xF6;&#xF7;]
在行中,

At least one of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷]
is present in the line,


[&#xC0;&#xC1;&#xC2;&#xC3;&#xC4;&#xC5;&#xC6;&#xC7;&#xC8;&#xC9;&# xCA;&#xCB;&#xCC;&#xCD;&#xCE;&#xCF;&#xD0;&#xD1;&#xD2;&#xD3;&#xD4;&#xD5;&#xD6; &#xD7;&#xD8;&#xD9;&#xDA;&#xDB;&#xDC;&#xDD;&#xDE;&#xDF;]
后面总是紧跟一个
[&#x20AC;&#x201A;&#x0192;&#x201E;&#x2026;&#x2020;&#x2021;&#x02C6;&#x2030;&#x0160;&#x2039;&#x0152; #x017D;&#x2018;&#x2019;&#x201C;&#x201D;&#x2022;&#x2013;&#x2014;&#x02DC;&#x2122;&#x0161;&#x203A;&#x0153 ;&#x017E;&#x0178; < NBSP> &#xA1;&#xA2;&#xA3;&#xA4;&#xA5;&#xA6; &#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC; < SHY> &#xAE;&#xAF; #xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5;&#xB6;&#xB7;&#xB8;&#xB9;&#xBA;&#xBB;&#xBC ;&#xBD;&#xBE;&#xBF;],

All instances of
[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß]
are always followed by exactly one of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿],


[&#xE0;&#xE1; xE2;&#xE3;&#xE4;&#xE5;&#xE6;&#xE7;&#xE8;&#xE9;&#xEA;&#xEB;&#xEC;&#xED;&#xEE; &#xEF2;&#x2020;&#x2026;&#x2020;&#x2021;&#x02C6 ;&#x2030;&#x0160;&#x2039;&#x0152;&#x017D;&#x2018;&#x2019;&#x201C;&#x201D;&#x2022;&#x2013;&#x2014; #x02DC;&#x2122;&#x0161;&#x203A;&#x0153;&#x017E;&#x0178; < NBSP> &#xA1; xA2;&#xA3;&#xA4;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC; SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5;&#xB6;&#xB7; #xB8;&#xB9;&#xBA;&#xBB;&#xBC;&#xBD;#xBE;&#xBF;],

All instances of
[àáâãäåæçèéêëìíîï]
are always followed by exactly two of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿],


[&#xF0;&#xF1;&#xF2;&#xF3;&#xF4;&#xF5;&#xF6;&#xF7;]
的所有实例总是后面紧跟三
[&#x20AC;&#x201A;&#x0192;&#x201E;&#x2026;&#x2020;&#x2021;&#x02C6;&#x2030;&#x0160;&#x2039; #x0152;&#x017D;&#x2018;&#x2019;&#x201C;&#x201D;&#x2022;&#x2013;&#x2014;&#x02DC;&#x2122;&#x0161;&#x203 ;&#x0153;&#x017E;&#x0178; < NBSP> &#xA1;&#xA2;&#xA3;&#xA4;&#xA5; &#xA6;&#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC; < SHY> &#xAE; #xAF;&#xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5;&#xB6;&#xB7;&#xB8;&#xB9;&#xBA;&#xBB ;&#xBC;&#xBD;&#xBE;&#xBF;],

All instances of
[ðñòóôõö÷]
are always followed by exactly three of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿],


[&#xF8;&#xF9 ;&#xFA;&#xFB;&#xFC;&#xFD;&#xFE;&#xFF;]
在此行中,

None of
[øùúûüýþÿ]
are present in the line, and


[&#x20AC;&#x201A;&#x0192;&#x201E;&#x2026;&#x2020;&#x2021;&#x02C6;&#x2030;&#x0160; &#x2039;&#x2022;&#x2013;&#x2014;&#x02DC;&#x2122;&#x2014; x0161;&#x203A;&#x0153;&#x017E;&#x0178; < NBSP> &#xA1;&#xA2;&#xA3;&#xA4 ;&#xA5;&#xA6;&#xA7;&#xA8;&#xA9;&#xAA;&#xAB;&#xAC; < SHY> &#xAE;&#xAF;&#xB0;&#xB1;&#xB2;&#xB3;&#xB4;&#xB5;&#xB6;&#xB7;&#xB8;&#xB9;

None of
[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ<NBSP>¡¢£¤¥¦§¨©ª«¬<SHY>®¯°±²³´µ¶·¸¹º»¼½¾¿]
are present in the line except where previously mentioned.

注意:


  • :FixLatin安装命令行工具 fix_latin 可以转换文件,使用第二种方法写一个工具很简单。

  • fix_latin (函数和文件)都可以通过安装 Encoding :: FixLatin :: XS

  • 同样的方法可用于UTF-8与其他单字节编码的混合。可靠性应该类似,但它可以有所不同。

  • Encoding::FixLatin installs command line tool fix_latin to convert files, and it would be trivial to write one using the second approach.
  • fix_latin (both the function and the file) can be sped up by installing Encoding::FixLatin::XS.
  • The same approach can be used for mixes of UTF-8 with other single-byte encodings. The reliability should be similar, but it can vary.