【问题标题】:Autodetect the character encoding when reading a file [duplicate]读取文件时自动检测字符编码[重复]
【发布时间】:2018-12-14 23:28:14
【问题描述】:

我有时不得不从外部源读取文本文件,这些文件可以使用各种字符编码;通常是 UTF-8、Latin-1 或 Windows CP-1252。

有没有办法方便地读取这些文件,像 Vim 等编辑器那样自动检测编码?

我希望有一些简单的东西:

open(my $f, '<:encoding(autodetect)', 'foo.txt') or die 'Oops: $!';

请注意,Encode::Guess 不能解决问题:它仅在可以明确检测到编码时才有效,否则它会发出嘶哑的声音。大多数 UTF-8 数据名义上是有效的 latin-1 数据,因此它在 UTF-8 文件上失败。

例子:

#!/usr/bin/env perl

use 5.020;
use warnings;

use Encode;
use Encode::Guess qw(utf-8 cp1252);

binmode STDOUT => 'utf8';

my $utf8 = "H\x{C3}\x{A9}llo, W\x{C3}\x{B8}rld!"; # "Héllo, Wørld!" in UTF-8
my $latin = "H\x{E9}llo, W\x{F8}rld!";            # "Héllo, Wørld!" in CP-1252

# Version 1
my $enc1 = Encode::Guess->guess($latin);
if (ref($enc1)) {
    say $enc1->name, ': ', $enc1->decode($latin);
}
else {
    say "Oops: $enc1";
}
my $enc2 = Encode::Guess->guess($utf8);
if (ref($enc2)) {
    say $enc2->name, ': ', $enc2->decode($utf8);
}
else {
    say "Oops: $enc2";
}

# Version 2
say decode("Guess", $latin);
say decode("Guess", $utf8);

输出:

cp1252: Héllo, Wørld!
Oops: utf-8-strict or utf8 or cp1252
Héllo, Wørld!
cp1252 or utf-8-strict or utf8 at ./guesstest line 32.

Borodin 答案中“更新”下的版本仅适用于 UTF-8 数据,但适用于 Latin-1 数据。 如果您需要同时处理 UTF-8 和 Latin-1 文件,则不能使用 Encode::Guess

这与this one 不是同一个问题:我正在寻找一种在打开文件时自动检测的方法。

【问题讨论】:

  • 没有办法可靠地检测纯文本的字符集。
  • 不是 100% 可靠,不。但肯定可以对主要字符集进行“足够好”的检测。即使有 1% 的失败率,它仍然比仅仅假设 UTF-8 或 Latin-1 并且一直跌倒或显示奇怪的字符要好。 (例如,vim 做得很好:我不记得是否曾经误检测过文件。)
  • 请注意,Windows 1252 (CP-1252) 与 ISO-8859-1 相同。前者在高位寄存器"\x80""\9F" 的前16 个字符中具有可打印字符,后者具有相同的未分配范围,以便为C1 控制字符集留出空间。
  • Windows-1252 (cp1252) 是 iso-8859-1 的超集。 (例如,HTML5 使用 windows-1252 作为默认值来处理错误指定的 Windows 内容。)
  • @mscha:我评论只是因为您在原始帖子中写了latin-1/windows-1252。 Latin-1 和 ISO-8859-1 是相同编码的两个名称,但 CP-1252 不同。在猜测编码时,最好假设,如果样本数据使用字符代码"\x80""\9F",那么它使用的是可打印字符的 CP-1252。在 Latin-1 中,Latin-1 Supplement 将控制字符分配给该范围,但我从未见过它们被使用过,而且我认为现有的 32 个 ASCII 控制字符足以满足任何目的。

标签: perl io character-encoding


【解决方案1】:

这是我目前的解决方法。至少对 UTF-8 和 Latin-1(或 Windows-1252)文件有效。

use 5.024;
use experimental 'signatures';
use Encode qw(decode);

sub slurp($file)
{
    # Read the raw bytes
    local $/;
    open (my $fh, '<:raw', $file) or return undef();
    my $raw = <$fh>;
    close($fh);

    my $content;

    # Try to interpret the content as UTF-8
    eval { my $text = decode('utf-8', $raw, Encode::FB_CROAK); $content = $text };

    # If this failed, interpret as windows-1252 (a superset of iso-8859-1 and ascii)
    if (!$content) {
        eval { my $text = decode('windows-1252', $raw, Encode::FB_CROAK); $content = $text };
    }

    # If this failed, give up and use the raw bytes
    if (!$content) {
        $content = $raw;
    }

    return $content;
}

【讨论】:

  • 或许可以谈谈这是如何工作的?
  • 在代码中添加了一些 cmets。
  • 评论只混淆代码。平行文本文档中的描述要有用得多。
  • 它对我有用。就像,它以字节(原始)读取文件并将其转换为给定的字符集。
【解决方案2】:

这取决于您可能处理的可能编码。 看看 Encode::Guess 模块

一般来说,很容易判断您是否没有有 ASCII 文件,因为代码点是 7 位,所以超过 127 的任何东西都意味着它不是 ASCII .还可以可靠地判断您的文件是否不是 UTF-8,因为多字节字符的最高有效位具有特定的序列。其他任何事情都不那么可靠,但可能

我不知道您可能使用的是什么编码,但这是一般的想法。 Encode::Guess 是核心 Encode 模块的一部分,因此不需要安装

use Encode::Guess;

my $enc = guess_encoding($data, qw/ ascii cp1252 iso-8859-1 utf-8 /);
say ref $enc? $enc->name : $enc, "\n";

或者您可以在不检查模块选择的内容的情况下执行最佳猜测解码

  use Encode::Guess qw/ ascii cp1252 iso-8859-1 utf-8 /;

  my $chars = decode("Guess", $data);

请记住,您提供的可能编码越少,猜测就越有可能准确。你应该仔细阅读模块文档


更新

这是 OP 的单行演示,Encode::Guess “不能解决问题” 编写为正确的程序

请注意,正如文档所述,guess_encoding 有时可能会返回类似utf-8 or iso-8859-1 的字符串,在这种情况下,程序员必须处理歧义。 *在 OP 的示例中并非如此:数据被标识为 UTF-8 编码,guess_encodingdecode('guess', ...) 都返回正确的结果

您可以使用此代码使用您选择的任何字节字符串测试Encode::Guess:只需修改$raw 的内容

use strict;
use warnings 'all';
use feature 'say';
use open qw/ :std encoding(UTF-8) /;

use Encode;
use Encode::Guess;
use Data::Dump;

my $raw = qq/H\x{C3}\x{A9}llo, W\x{C3}\x{B8}rld!/;

my $enc = guess_encoding($raw);

if ( my $class = ref $enc ) {
    printf qq{Guessed encoding \$enc is an %s object "%s"\n}, $class, $enc->name
}
else {
    printf qq{Guessed encoding \$enc is a scalar "%s"\n}, $enc;
}

my $chars = decode('guess', $raw);

printf "Decoded characters: %s\n", $chars;
dd $chars;

输出

Guessed encoding $enc is an Encode::utf8 object "utf8"
Decoded characters: Héllo, Wørld!
"H\xE9llo, W\xF8rld!"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 2013-07-06
    • 2016-02-12
    • 1970-01-01
    • 2010-09-29
    • 2016-10-06
    相关资源
    最近更新 更多