在Perl中将混合ISO-8859-1和UTF-8的多行字符串强制转换为UTF-8

3
考虑以下问题:
一个多行字符串 $junk 包含一些使用 UTF-8 编码的行和一些使用 ISO-8859-1 编码的行。我不知道哪些行使用哪种编码,因此需要使用启发式方法。
我想将 $junk 转换为纯粹的 UTF-8,并正确重新编码 ISO-8859-1 行。此外,在处理过程中如果出现错误,我希望提供“尽力而为”的结果,而不是抛出错误。
我的当前尝试如下:
$junk = force_utf8($junk);

sub force_utf8 {
  my $input = shift;
  my $output = '';
  foreach my $line (split(/\n/, $input)) {
    if (utf8::valid($line)) {
      utf8::decode($line);
    }
    $output .= "$line\n";
  }
  return $output;
}

显然,由于我们缺乏每行原始编码的信息,转换永远不会完美。但这是我们能得到的“尽力而为的结果”吗?

您将如何改进 force_utf8(...) 子函数的启发式/功能?


你一直要求别人对你的代码进行评论,但你并没有听取他们说的话。如果你的整体策略是错误的,那么对代码的评论就毫无意义了。 - mpeters
mpeters:我知道这种方法的局限性,因此需要启发式算法-这些都是已知的。我的问题更多地是“我知道这不会是最优的,但是在这些假设下,这是我们能做到的最好吗?”到目前为止,这个问题仍然没有答案。 - knorv
以下是对代码的一条评论:不要使用&来调用子程序,只需使用force_utf8($junk)即可。 - Sinan Ünür
5个回答

2
你可能可以使用一些领域知识来修复它。例如,ISO-8859-1中不太可能出现é这样的字符组合;更有可能是UTF-8的é。
如果您的输入受限于一组受限制的字符,您还可以使用一种启发式方法,例如假设Ã永远不会出现在您的输入流中。
没有这种领域知识,通常情况下,您的问题是棘手的。

代码将处理多种语言的输入,因此列举特定的翻译不是一个可行的选择。 - knorv

2
我除了建议你可以先尝试使用Encode::Guess之外,没有其他有用的建议。

1

仅凭一个字符很难判断它是ISO-8859-1还是UTF-8编码。问题在于两者都是8位编码,因此仅仅查看最高有效位是不够的。因此,对于每一行,我会假定它是UTF-8编码并进行转码。当发现无效的UTF-8编码时,重新假定该行实际上是ISO-8859-1编码并进行再次转码。这种启发式方法的问题在于,您可能会转码既是ISO-8859-1编码又是格式良好的UTF-8编码的行;但是,如果没有关于$junk的外部信息,就无法确定哪个是适当的。


2
UTF-8不是8位编码。它用8位(“低”或“7位”ASCII)表示常用的西方字符,但如果需要,它将使用多字节字符。 - DaveE
UTF-8是一种8位编码,也与7位ASCII完全兼容。它是否使用所有8位来表示给定字符与重点无关。 - fbrereto
不,它不是8位编码。虽然某些UTF-8字符串可能只由使用8位的字符组成,但在字符串中的任何给定UTF-8字符的大小可以高达四个字节(32位)。请参见en.wikipedia.org/wiki/UTF-8或tools.ietf.org/html/rfc3629。 - DaveE
我理解你的观点,但我仍然认为这个启发式方法在大多数情况下都是有效的。 - fbrereto
严格来讲,它适用于所有意图和目的,例如,“无论你想要什么或者无论你的目的是什么,这个启发式方法应该都能奏效”。 - DaveE

1

看一下this文章。UTF-8被优化为用8位表示西方语言字符,但不限于每个字符8位。多字节字符使用常见的位模式来指示它们是否是多字节字符以及字符使用的字节数。如果您可以安全地假设字符串中只有这两种编码,则其余部分应该很简单。


0
简而言之,我选择使用“file -bi”和“iconv -f ISO-8859-1 -t UTF-8”来解决我的问题。
最近,我遇到了一个类似的问题,尝试规范化文件名的编码。我有一些ISO-8859-1、UTF-8和ASCII混合的文件。当我处理这些文件时,我意识到由于目录名称具有与文件编码不同的编码,添加了一些复杂性。
起初,我尝试使用Perl,但它无法正确区分UTF-8和ISO-8859-1,导致UTF-8乱码。
在我的情况下,这是一次针对合理文件数量的一次性转换,因此我选择了我知道并且没有错误的缓慢方法(主要是因为每行只有1-2个非相邻字符使用特殊的ISO-8859-1代码)。
选项#1将ISO-8859-1转换为UTF-8。
cat mixed_text.txt |
while read i do
type=${"$(echo "$i" | file -bi -)"#*=}
if [[ $type == 'iso-8859-1' ]]; then
    echo "$i" | iconv -f ISO-8859-1 -t UTF-8
else
    echo "$i"
fi
done > utf8_text.txt

选项 #2 将ISO-8859-1转换为ASCII

cat mixed_text.txt |
while read i do
type=${"$(echo "$i" | file -bi -)"#*=}
if [[ $type == 'iso-8859-1' ]]; then
    echo "$i" | iconv -f ISO-8859-1 -t ASCII//TRANSLIT
else
    echo "$i"
fi
done > utf8_text.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接