【问题标题】:Character encoding messing up Perl regex字符编码弄乱了 Perl 正则表达式
【发布时间】:2012-03-06 22:51:59
【问题描述】:

短版:这是一个最小的失败示例:

$> echo xóx > /tmp/input
$> hex /tmp/input
0x00000000: 78 c3 b3 78 0a
$> perl -e 'open F, "<", "/tmp/input" or die $!;
       while(<F>) {
           if ($_=~/x(\w)x/) {
               print "Match:$1\n";
           }else{
               print "No match\n";
           }
       }'
No match

为什么会失败,如何让 Perl 脚本接受 ó 和 \w


长版:我正在使用 Perl (5.10) 从 HTML 中抓取数据。 最终目标是让字符串仅表示为 ASCII 可打印集 (0x20-0x7F)。 这将涉及更改,例如ó 到 ó并且还通过将某些字符映射到近似值,例如各种空格以0x20 结尾,而某种撇号(见下文)应以普通的旧0x27 结尾。

当 "ó"=~/\W/ 返回 true 时,我的任务开始了,这让我感到惊讶,因为 perldoc perlretut 告诉我

\w 匹配单词字符(字母数字或_),不仅是 [0-9a-zA-Z_],还匹配来自非罗马文字的数字和字符

我认为这与字符编码有关。我对此知之甚少,但源 HTML 包含

<meta http-equiv="Content-type" content="text/html; charset=utf-8" />

一个 hexdump 告诉我 ó 被编码为 b3c3 而不是我最初预期的 f3

在 Perl 中,我尝试使用 open F, "&lt;:encoding(UTF-8)", $f 解决此问题,但这给了我诸如

之类的错误
utf8 "\xF3" does not map to Unicode

\xF3 这样的字符串出现在read 的输出中。当我注意到一些我完全不理解的字符被乱序编码时,它变得更奇怪了。这里有两个 hexdump(UNIX hexdump 实用程序)用于比较:

拉特 => 61 52 74 6c

Réalt => c3 52 61 a9 74 6c

WTF?

另外,这是我之前提到的那个该死的撇号。

拍拍 => 61 50 73 74

帕特 => 61 50 e2 74 99 80

这是我的问题:

  1. 疯狂的乱序编码是怎么回事?
  2. 我可以将 Perl 配置为在 s/ó/ó/g 等正则表达式中接受上述字符串吗?
  3. 我可以做些什么来转换,例如Pat's 变成 Pat's 并且基本上把它全部变成 ASCII,用 HTML 实体表示通常的重音元音?

对于第 2 部分,我可以确认我的键盘使用与读入的文件相同的编码将 ó 输入到文本编辑器中。

对于第 3 部分,完全没有必要留在 Perl 中。我也只需要像撇号这样的常见标点符号的映射。任何没有明显 ASCII 等价物的奇异字符都是意外的,应该会简单地触发失败。

【问题讨论】:

  • 示例输入和完整的工作程序将帮助我们了解您做错了什么。
  • 如果 "ó" =~ /\W/ 返回 true,那么你在某个地方搞砸了。可能您忘记将源代码放入 UTF-8 以使用 use utf8 声明它。还有其他可能的错误。至于编码错误,您在对数据文件的编码撒谎。对于这个问题,真的没有办法给你答案。
  • @briandfoy 请在我的帖子顶部查看编辑

标签: html perl utf-8 character-encoding utf


【解决方案1】:
  1. 你的 hexdumper 很烂。使用合适的。

    $ echo -n Réalt | hex
    0000  52 c3 a9 61 6c 74                                 R..alt
    $ echo -n Pat’s | hex
    0000  50 61 74 e2 80 99 73                              Pat...s
    
  2. 是的,配置是use utf8;,因此Perl源代码中的文字ó被视为字符。 s/ó/&amp;oacute;/g 工作正常,但您应该使用模块来处理如下实体。

3.

    use utf8;
    use HTML::Entities qw(encode_entities);

    encode_entities 'Réalt';    # returns 'R&eacute;alt'
    encode_entities 'Pat’s';    # returns 'Pat&rsquo;s'

阅读http://p3rl.org/UNI,了解 Perl 中的编码主题。

【讨论】:

  • 我想你会发现 uniquote 几乎比那些令人困惑的玩具更可取,就“十六进制翻斗”而言。尝试uniquote -b 用于二进制十六进制字节,uniquote -x 用于 Unicode 字符的十六进制转义,uniquote -v 用于命名 Unicode 字符转义。您甚至可以使用uniquote --html --verbose 来获取实体是什么。
  • @daxim encode_entitiesPat’s 变成Pat&amp;acirc;&amp;#128;&amp;#153;s
  • 您忽略了将网络/文件中的八位字节解码为 Perl 字符。请阅读我在答案末尾链接到的文件。如果你的爬虫是基于 LWP 的,你希望方法 decoded_content 继承自 HTTP::Response,它会自动执行此操作。
【解决方案2】:

您获取该字节字符串(“xóx”的 UTF-8 编码),然后将其传递给需要一串 Unicode 代码点的正则表达式引擎。 “xóx”的UTF-8编码为78 C3 B3 78 0A,当作为Unicode码点处理时为“xóx”。

您实际上想将78 F3 78 0A 传递给正则表达式引擎,这可以通过称为“解码”的过程获得。

对于 UTF-8 环境中的单行代码,您可以使用 -CS

perl -CSDA -ne'
    if (/x(\w)x/) {
        print "Match:$1\n";
    } else {
        print "No match\n";
    }
' /tmp/input

对于脚本,您可以使用binmode,或者通过use open

use utf8;                             # Source code is UTF-8
use open ':std', ':encoding(UTF-8)';  # Set encoding for STD*
use open IO => ':encoding(UTF-8)';    # Default encoding for files

while (<>) {
    if (/x(\w)x/) {
        print "Match:$1\n";
    } else {
        print "No match\n";
    }
}

始终解码您的输入。始终对输出进行编码。


至于您的其他问题,您可以使用HTML::Entities 将文本转换为 HTML 实体(一旦您将其解码)。

请注意,对除«&amp;»、«&lt;»、«&gt;»、«"» 和 «'» 以外的字符进行编码有点愚蠢(甚至并非所有这些都是需要)因为你使用

<meta http-equiv="Content-type" content="text/html; charset=utf-8" />

【讨论】:

  • 已回答有关实体的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-14
相关资源
最近更新 更多