修复包含 UTF-8 和 Windows-1252 的文件
Fixing a file consisting of both UTF-8 and Windows-1252
我有一个生成 UTF-8 文件的应用程序,但其中一些内容编码不正确。一些字符被编码为 iso-8859-1 aka iso-latin-1 或 cp1252 aka Windows-1252。有没有办法恢复原文?
是的!
显然,最好修复创建文件的程序,但这并不总是可行的。以下是两种解决方案。
一行可以包含多种编码
Encoding::FixLatin 提供了一个名为 fix_latin
的函数,它解码由 UTF-8、iso-8859-1、cp1252 和 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
采用了启发式方法,但它们相当可靠。只有以下情况会失败:
[ÀÁÂÃÄÅÆÇÈÉÊÌÍÒÓÔÕÖ×ØÙÚÛÜÝÞß]
之一使用 iso-8859-1 或 cp1252 编码,后跟
[€‚ƒ „…†‡‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ ¶·¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
[àáâãäåæçèéêëìíîï]
之一使用 iso-8859-1 或 cp1252 编码,后跟两个
[€‚ƒ„… †‡‡‰Š‹ŒŽ''“”•– - - - - - - - - - - ¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
[ðñòóôõö÷]
之一使用 iso-8859-1 或 cp1252 编码,后跟两个
[€‚ƒ„ …†‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶ ·¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
可以使用核心模块 Encode 产生相同的结果,尽管我认为这比安装了 Encoding::FixLatin::XS 的 Encoding::FixLatin 慢一点。
$ 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 来使该过程更加可靠。
$ 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
采用了启发式方法,但它们非常可靠。仅当给定行满足以下 所有 条件时,它们才会失败:
该行使用iso-8859-1或cp1252编码,
至少
[€‚ƒ„…†‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡ ¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¦ÀÁÂÃÄÅÆÇÈÉÊÈÌÍÏÏÑÒÓÔÕÖ×ØÙÛÛÜÝÞßàáâãäæçèéêêìííîïðñ=ò][ôis][ois][ois][6] ]
[ÀÁÂÃÄÅÆÇÈÉÊÈÌÍÈÏÐÒÓÔÕÖ×ØÙÚÛÜÝÞß]
的所有实例总是紧跟
[€‚ƒ„…†‡‡‰‰ Š‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾ ¿],
[àáâãäåæçèéêêëìíîï]
的所有实例始终紧跟两个
[€‚ƒ„…†‡‡‰‰Š‹ ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿] ,
[ðñòóôõö÷]
的所有实例总是紧跟三个
[€‚ƒ„…†‡^‰‰ ‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿ ],
None of
[øùúûüýþÿ]
存在于行中,并且
None of
[€‚ƒ„…†‡‡‰Š‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿]
出现在行中,除了前面提到的地方。
备注:
- Encoding::FixLatin安装命令行工具
fix_latin
转换文件,用第二种方式写一个就很简单了
fix_latin
(函数和文件)可以通过安装 Encoding::FixLatin::XS. 来加速
- 相同的方法可用于 UTF-8 与其他单字节编码的混合。可靠性应该相似,但可能会有所不同。
这也是我写Unicode::UTF8. With Unicode::UTF8 this is trivial using the fallback option in 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]) });
}
输出:
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 是用 C/XS 编写的,只有在遇到格式错误的 UTF-8 序列时才会调用 callback/fallback。
最近我发现文件严重混合了 UTF-8、CP1252 和 UTF-8 编码,然后解释为 CP1252,然后再次编码为 UTF-8,再次解释为 CP1252,等等.
我写了下面的代码,对我来说效果很好。它寻找典型的 UTF-8 字节序列,即使某些字节不是 UTF-8,而是等效 CP1252 字节的 Unicode 表示。
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;
}
这与 ikegami 的回答有类似的限制,除了相同的限制也适用于 UTF-8 编码的字符串。
我有一个生成 UTF-8 文件的应用程序,但其中一些内容编码不正确。一些字符被编码为 iso-8859-1 aka iso-latin-1 或 cp1252 aka Windows-1252。有没有办法恢复原文?
是的!
显然,最好修复创建文件的程序,但这并不总是可行的。以下是两种解决方案。
一行可以包含多种编码
Encoding::FixLatin 提供了一个名为 fix_latin
的函数,它解码由 UTF-8、iso-8859-1、cp1252 和 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
采用了启发式方法,但它们相当可靠。只有以下情况会失败:
[ÀÁÂÃÄÅÆÇÈÉÊÌÍÒÓÔÕÖ×ØÙÚÛÜÝÞß]
之一使用 iso-8859-1 或 cp1252 编码,后跟
[€‚ƒ „…†‡‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ ¶·¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
[àáâãäåæçèéêëìíîï]
之一使用 iso-8859-1 或 cp1252 编码,后跟两个
[€‚ƒ„… †‡‡‰Š‹ŒŽ''“”•– - - - - - - - - - - ¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
[ðñòóôõö÷]
之一使用 iso-8859-1 或 cp1252 编码,后跟两个
[€‚ƒ„ …†‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶ ·¸¹º»¼½¾¿]
使用 iso-8859-1 或 cp1252 编码。
可以使用核心模块 Encode 产生相同的结果,尽管我认为这比安装了 Encoding::FixLatin::XS 的 Encoding::FixLatin 慢一点。
$ 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 来使该过程更加可靠。
$ 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
采用了启发式方法,但它们非常可靠。仅当给定行满足以下 所有 条件时,它们才会失败:
该行使用iso-8859-1或cp1252编码,
至少
[€‚ƒ„…†‡‡‰Š‹ŒŽ''“”•–~™š›œžŸ<NBSP>
¡ ¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¦ÀÁÂÃÄÅÆÇÈÉÊÈÌÍÏÏÑÒÓÔÕÖ×ØÙÛÛÜÝÞßàáâãäæçèéêêìííîïðñ=ò][ôis][ois][ois][6] ]
[ÀÁÂÃÄÅÆÇÈÉÊÈÌÍÈÏÐÒÓÔÕÖ×ØÙÚÛÜÝÞß]
的所有实例总是紧跟
[€‚ƒ„…†‡‡‰‰ Š‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾ ¿],
[àáâãäåæçèéêêëìíîï]
的所有实例始终紧跟两个
[€‚ƒ„…†‡‡‰‰Š‹ ŒŽ''“”•–~™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿] ,
[ðñòóôõö÷]
的所有实例总是紧跟三个
[€‚ƒ„…†‡^‰‰ ‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿ ],None of
[øùúûüýþÿ]
存在于行中,并且None of
[€‚ƒ„…†‡‡‰Š‹ŒŽ''“”•– ™š›œžŸ<NBSP>
¡¢£¤¥¦§¨©ª«¬<SHY>
®¯°±²³´µ¶·¸¹º»¼½¾¿]
出现在行中,除了前面提到的地方。
备注:
- Encoding::FixLatin安装命令行工具
fix_latin
转换文件,用第二种方式写一个就很简单了 fix_latin
(函数和文件)可以通过安装 Encoding::FixLatin::XS. 来加速
- 相同的方法可用于 UTF-8 与其他单字节编码的混合。可靠性应该相似,但可能会有所不同。
这也是我写Unicode::UTF8. With Unicode::UTF8 this is trivial using the fallback option in 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]) });
}
输出:
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 是用 C/XS 编写的,只有在遇到格式错误的 UTF-8 序列时才会调用 callback/fallback。
最近我发现文件严重混合了 UTF-8、CP1252 和 UTF-8 编码,然后解释为 CP1252,然后再次编码为 UTF-8,再次解释为 CP1252,等等.
我写了下面的代码,对我来说效果很好。它寻找典型的 UTF-8 字节序列,即使某些字节不是 UTF-8,而是等效 CP1252 字节的 Unicode 表示。
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;
}
这与 ikegami 的回答有类似的限制,除了相同的限制也适用于 UTF-8 编码的字符串。