Perl 编程/Unicode UTF-8
在应用程序开发的背景下,使用 UTF-8 编码的 Unicode 是在应用程序中支持多种语言的最佳方式。即使在同一个网页上也可以支持多种语言。
Unicode(通常以 UTF-8 形式出现)正在取代 ASCII 和使用 ISO-8859-1 和 Windows-1252 等 8 位“代码页”。
另请参见Perl Unicode Cookbook - 44 个在 Perl 5 中使用 Unicode 的食谱。
Unicode 是一个标准,它指定了世界上大多数书写系统的所有字符。每个字符都分配了一个唯一的码位,例如 U+0030。前 256 个码位与ISO-8859-1 相同,以便轻松地转换现有的西方/拉丁-1 文本。
要查看特定码位的属性
use Unicode::UCD 'charinfo';
use Data::Dumper;
print Dumper(charinfo(0x263a)); # U+263a
如果您查看Unicode 字符引用,您会注意到并非每个码位都分配了一个字符。此外,由于向后兼容传统编码,某些字符具有多个码位。
UTF-8 是 Unicode 的一种特定编码 - 最受欢迎的编码。其他编码包括 UTF-7、UTF-16、UTF-32 等。如果您决定使用 Unicode,您可能需要使用 UTF-8。
编码定义了每个 Unicode 码位如何映射到位和字节。在 UTF-8 编码中,前 128 个 Unicode 码位使用一个字节。这些字节值与US-ASCII 相同,如果只使用 ASCII 字符,则 UTF-8 编码和 ASCII 编码可以互换。接下来的 1,920 个码位在 UTF-8 中使用两字节编码。编码剩余的码位需要三个或四个字节。
请注意,尽管 Unicode 码位 128-255 与 ISO-8859-1 相同,但 UTF-8 对这些码位中的每一个进行不同的编码。UTF-8 使用两个字节来编码这些码位中的每一个,而 ISO-8859-1 只使用一个字节来编码该范围内的每个字符。因此,ISO-8859-1 和 UTF-8 不可互换。(如果只使用 ASCII 字符,那么它们都是可以互换的,因为 ASCII、ISO-8859-1 和 UTF-8 对前 128 个 Unicode 码位都使用相同的编码。)
因此,重申一下,使用 UTF-8,并非所有字符都编码到单个字节中(与 ASCII 和 ISO-8859-1 不同)。思考一下:这将如何影响编辑器(如 vim 或 emacs)、网页和表单、数据库、Perl 本身、Perl IO、您的 Perl 源代码(如果您想包含具有多字节编码的字符)?如果字符串包含具有多字节编码的字符,那么这将如何影响传递字符串?正则表达式仍然有效吗?
字符编码 | # 字符 | 128 个 US-ASCII 字符 | 接下来的 128 个字符 | 剩余字符 |
---|---|---|---|---|
US-ASCII | 128 | 1 字节 | N/A | N/A |
ISO-8859-1 | 256 | 1 字节 | 1 字节 | N/A |
UTF-8 | > 100,000 | 1 字节 | 2 字节 | 2 - 6 字节 |
从上表可以看出,码位 128-255 (0x80-0xff) 是您需要注意的地方。 稍后,您将发现码位 128-159 (0x80-0x9F) 甚至更棘手,因为流行的 Windows-1252 字符集(另一个每个字符一个字节的编码)在该范围内与 ISO-8859-1 不兼容。
\x{c3}\x{ae}
- 一些函数在 Perl 中使用 UTF-8 编码的字符串时速度较慢
- 您必须编写一些额外的 Perl 代码来确保进入 Perl 的数据被正确解码,并且离开 Perl 的数据被正确编码 - 但您在使用除平台的本地 8 位字符集(我们现在将其称为N8CS[1])之外的任何字符集时都必须这样做,这通常是 ISO-8859-1/Latin-1
- 您必须适当地与您的数据库交互 - 它是否使用 UTF-8?
- 您必须确保您的网页指定网页以 UTF-8 编码
- 您可能需要进行 Web 服务器调整(如果它配置为始终提供某些特定的字符集,而不是 UTF-8)
如果可能,最佳实践方法是在任何地方都使用 UTF-8。这包括网页以及由此产生的 Web 表单、数据库、HTML 模板和存储在 Perl 中的字符串。一个例外可能是您的 Perl 源代码本身。如果 N8CS 足够(即,如果您的源代码中不需要任何 UTF-8 字符或字符串),那么您的源代码不需要以 UTF-8 编码。(好的,另一个例外可能是您的 HTML 模板。如果您的模板只需要/包含 N8CS,那么它们也不需要以 UTF-8 编码。)
要在 Perl Web 应用程序中正确使用 UTF-8,以下是必须完成的操作摘要
- 进入 Perl 的所有文本(非二进制)数据/字节(因此表单数据、数据库数据、文件读取、HTML 模板等)必须被正确解码。如果传入的文本/字节以 UTF-8 编码,则必须对其进行 UTF-8 解码。如果它们以 N8CS(通常是 ISO-8859-1)编码,则应对其进行 N8CS 解码。如果它们以其他字符集编码,则必须使用该字符集对其进行解码。
- 所有从 Perl 输出的文本数据(因此到浏览器、数据库、文件等)必须被正确编码(编码成字节流)。STDOUT(到浏览器的输出)必须使用 UTF-8 编码。
- 浏览器需要通过 HTTP 头部和<meta>标签被告知网页是 UTF-8 编码的。
不要使用早于 5.8.1 版本的 Perl。虽然对 UTF-8 的支持从 5.6.0 版本开始,但正则表达式在下一个版本 5.6.1 中仍然无法正常工作。5.8.1 版本增加了一些速度改进。到了 Perl 5.14,Unicode 支持在很大程度上是干净且流畅的。
在我们开始深入讨论如何使用 UTF-8 的细节之前,我们需要先定义一些术语,然后谈谈 Perl 在内部存储文本时的双重特性。
术语
[edit | edit source]一个字符是一个逻辑实体。为了使用、存储、写入、在程序之间交换等,字符必须被编码(使用字符集)。编码将逻辑字符转换为我们在程序中可以使用的内容。根据用于编码的字符集,单个字符可能需要一个或多个字节来表示。
在引用传入或传出 Perl 程序的数据时,我们将使用字节一词。一个字节是一个字节,即 8 位。编码后的字符组成一个字节流。当一个字节流进入 Perl 时,字节应该被解码(使用正确的字符集——它们被编码的字符集),以便 Perl 能够确定编码后的字节流中包含哪些逻辑字符。然后,Perl 可以将这些字符存储为字符串——一个字符序列。
二进制数据也作为字节流传入。它不应该使用字符集解码,因为它可能根本不包含任何字符,或者它除了字符之外还包含其他信息,因此无法使用字符集解码。
Perl 字符串/文本
[edit | edit source]在内部,Perl 使用以下编码之一存储每个字符串
- 本机编码——字节编码。它使用 N8CS[2]。这是一种每字符一个字节的编码,因此最多只能编码 255 个字符。如果 Perl 没有被指示解码(不推荐),这是所有传入文本/字节的默认编码。使用这种编码的字符串被称为字节字符串或二进制字符串。除非你另行指示,否则 Perl 将认为这些字节是 ISO-8859-1,而不是你的平台编码。这是一个常见的错误。
- UTF-8 编码——字符编码。它使用(显然)UTF-8。使用这种编码的字符串被称为字符字符串或文本字符串或Unicode 字符串。
在创建你自己的字符串时,Perl 尽可能使用 N8CS(出于向后兼容性和效率原因)。但是,如果字符无法在 N8CS 中表示,则使用 UTF-8。换句话说,如果字符串中的所有代码点都<= 0xFF,则使用 N8CS,否则使用 UTF-8。
$native_string = "\xf1";
$native_string = "\x{00f1}"; # still N8CS, since <= 0xff
$native_string = chr(0xf1); # still N8CS, since <= 0xff
$utf8_string = "\x{0100}";
你可以使用以下方法将 N8CS 字符串转换为 UTF-8 字符串utf8::upgrade():
$my_string = "\xf1"; # N8CS byte string (one byte is used internally to encode)
utf8::upgrade($my_string); # UTF-8 character string now (two bytes are used internally to encode)
你的程序可以包含 Perl 的两种内部格式的字符串混合。Perl 使用“UTF8 标志”来跟踪字符串内部使用的编码。值得庆幸的是,格式/标志跟随字符串。Perl 尽可能将字符串保留在 N8CS 中。但是,当 N8CS/本机字符串与 UTF-8 字符串一起使用时,本机字符串会使用 N8CS 静默隐式解码,并升级(编码)到 UTF-8。换句话说,本机字节字符串使用本机字符集解码,然后在内部编码成 UTF-8。生成的字符字符串将设置 UTF8 标志。
UTF-8 流
[edit | edit source]任何 Perl IO 都需要正确处理字符串/文本的解码和编码。由于世界各地使用着多种字符编码,Perl 无法正确猜测用于编码某个特定传入文本/字节的字符编码,也无法知道你想要使用哪种字符编码进行传出文本/字节。传入的 UTF-8 字节流与传入的 Windows-1252 字节流并不相同。例如,Unicode 字符 U+201c(左双引号)在 Windows-1252 中使用一个字节编码(0x93),但 UTF-8 使用三个字节对其进行编码(0xE2 0x80 0x9C)。如果你希望 Perl 正确地解释你的传入文本/字节,你必须告诉 Perl 使用哪种字符集对它们进行编码,以便它们能够被正确解码。
UTF-8 文本/字节在 Perl 程序中进出时的典型流程如下
- 接收外部 UTF-8 编码的文本/字节流并正确解码——即,告诉 Perl 字节使用哪种字符集进行编码(在本例中,编码是 UTF-8)。Perl 可以检查解码过程中的格式错误数据(错误编码),具体取决于你选择的解码方法。Perl 将字符串在内部存储为 N8CS 或 UTF-8,具体取决于你选择的解码方法,以及在字节流中发现的字符。(通常,字符串将在内部存储为 UTF-8。)
- 像往常一样处理字符串。
- 将字符串编码成 UTF-8 编码的字节流并输出。
1. 解码文本输入
[edit | edit source]外部输入包括提交的 HTML 表单数据、数据库数据(例如,来自 SQL SELECT 语句)、HTML 模板、文本文件、套接字、其他程序等。如果这些内容中可能包含 UTF-8 编码的数据/文本,你必须对其进行解码。Perl 中的 UTF-8 解码涉及两个步骤
- 根据 UTF-8 格式规则解码文本。这可能会生成解码错误,具体取决于你选择的解码方法。使用decode()始终会导致字符串在内部存储为 UTF-8,并设置 UTF8 标志(尽管 Encode 的文档中这么说)。使用utf8::decode()可能会导致 N8CS 或 UTF-8 内部编码。如果传入文本只包含 ASCII 字符,则使用 N8CS,否则使用 UTF-8。
- 编码文本(这可能是一个无操作)并在内部将其存储为 N8CS 或 UTF-8。如果存储为 UTF-8,则设置 UTF8 标志。
如果你确定传入数据/字节只包含 N8CS(Perl 将其解释为 ISO-8859-1)文本,则无需显式解码它(因为 Perl 的默认内部编码是 N8CS,这是一种每字符一个字节的编码)。但是,“最佳实践”建议所有传入数据/字节都应该被显式解码——你可以显式解码 ISO-8859-1、ASCII 和许多其他字符编码。
如果你不解码,Perl 假设输入文本/字节是 N8CS 编码的,因此每个字节都被视为一个单独的字符——显然,如果你有一个多字节 UTF-8 编码的字节流/文本进入,这不是你想要的。不正确的解码会导致 双重编码,由于隐式解码(如上所述),这可能难以定位。
这里要强调的另一个重要点是:你需要知道每个输入文本使用哪种编码。不要猜测,不要假设。
输入 - 文件,文件句柄
[edit | edit source]Perl 可以使用 PerlIO 层在数据进入 Perl 时自动对其进行解码
open (my $in_fh, "<:encoding(UTF-8)", $filename) || die; # auto UTF-8 decoding on read
如果你已经有一个打开的文件句柄
binmode $in2_fh, ':encoding(UTF-8)';
不要使用:encoding(utf8)因为它不会检查你的传入文本是否为有效的 UTF-8,它只会将它标记为 UTF-8——参见Perlmonks.
如果你的文本文件包含一个字节顺序标记,请参见Perlmonks.
输入 - HTML 模板
[edit | edit source]如果你使用 CGI 框架或模板引擎来拉取 UTF-8 编码的 HTML 模板文件,你可能需要告知它 UTF-8 编码,以便它在读取模板文件时对其进行“UTF-8 解码”。基本上,框架或模板引擎需要做我们在上一节中讨论的事情。
对于Template::Toolkit,如果你在模板文件中使用适当的字节顺序标记 (BOM) 来指示编码,则工具包将自动对其进行适当的解码。如果模板不使用 BOM,则使用 ENCODING 选项
my $template = Template->new({ ENCODING => 'utf8' });
HTML::Template 目前不支持对 UTF-8 编码的 HTML 模板文件进行解码。这是一个已知限制/错误。有一些解决方法
- 一个补丁可用。
- 你可以使用 TMPL_VARs 将 UTF-8 内容插入 N8CS(甚至 ASCII)编码的模板文件中。在将参数/内容插入 HTML 模板使用 TMPL_VARs 之前,对其进行 UTF-8 解码,隐式解码应该将生成的文本(即,模板和填充的变量)在内部升级到 UTF-8。对于许多应用程序来说,这通常已经足够了。
输入 - 网页表单
[edit | edit source]默认情况下,CGI.pm 不会解码您的表单参数。您可以使用-utf8pragma,它将把所有参数都当作 UTF-8 字符串处理(并解码),但这在您有任何二进制文件上传字段时会失败。一个更好的解决方案是覆盖 param 方法
package CGI::as_utf8;
BEGIN {
use strict;
use warnings;
use CGI 3.47; # earlier versions have a UTF-8 double-decoding bug
{
no warnings 'redefine';
my $param_org = \&CGI::param;
my $might_decode = sub {
my $p = shift;
# make sure upload() filehandles are not modified
return $p if !$p || ( ref $p && fileno($p) );
utf8::decode($p); # may fail, but only logs an error
$p
};
*CGI::param = sub {
# setting a param goes through the original interface
goto &$param_org if scalar @_ != 2;
my ($q, $p) = @_; # assume object calls always
return wantarray
? map { $might_decode->($_) } $q->$param_org($p)
: $might_decode->( $q->$param_org($p) );
}
}
}
1
---
use CGI::as_utf8; # put this line in your app, e.g., in your CGI::Application module(s)
以上是 rhesa 的解决方案,稍微修改了一下——utf8::decode()用于代替 Encode'sdecode_utf8(),因为当只涉及 ASCII 字符时,它更有效(因为 UTF8 标志未设置)。请注意,该模块假定网页和表单始终使用 UTF-8 编码,并且始终使用 CGI.pm 的 OO 接口。
注意,浏览器应该使用与显示表单相同的字符编码来编码表单数据。因此,如果您发送 UTF-8 表单,则应为文本字段获得 UTF-8 编码的数据。您不应该使用accept-charset在您的 HTML 标记中。
输入 - STDIN
[edit | edit source]当 Web 表单被 POST 时,表单数据通过 STDIN 传入 Perl。如果您使用的是 CGI.pm,文本表单数据可通过CGI.pm'sparam()方法获取,上一节介绍了如何正确处理 UTF-8 编码的文本表单数据。
如果您没有任何文件上传(即,您的所有数据都是文本),那么您可以使用 CGI::as_utf8 模块,而是将以下代码行添加到脚本的开头,以使在 STDIN 上接收的所有数据(即,所有 POST 的表单数据)自动解码为 UTF-8
binmode STDIN, ":encoding(UTF-8)";
不要使用
binmode STDIN, ":utf8"; # do NOT use this!
因为它不检查传入的文本是否为有效的 UTF-8,它只是将其标记为 UTF-8——参见 Perl 5 Wiki。
上一节中的方法更可取,因为它在存在任何二进制表单数据(文件上传)时将“执行正确的事”。
如果您正在编写其他(非 CGI)程序来接收 STDIN 上的数据,请适当地解码
my $utf8_text = decode('UTF-8', readline STDIN);
my $iso8859_text = decode('ISO-8859-1', readline STDIN);
my $binary_data = read(...); # don't decode
注意decode()始终设置 Perl 的内部 UTF8 标志。
输入 - 数据库
[edit | edit source]在“在所有地方使用 UTF-8”模型中,将您的数据库配置为以 UTF-8 存储值。
从 UTF-8 数据库读取数据时,确保传入的 UTF-8 编码字符串字段数据被 UTF-8 解码,但不要解码传入的二进制字段数据。
输入 - MySQL
[edit | edit source]对于 MySQL,如果您使用的是 mysql_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 解码(和编码)是自动的。
use DBI();
my $dbh = DBI->connect('dbi:mysql:test_db', $username, $password,
{mysql_enable_utf8 => 1}
);
这意味着您不应该调用utf8::decode()(或任何其他 UTF-8 解码函数)在传入的字符串字段数据上——驱动程序会为您完成此操作。如果某个字段的传入数据只包含 ASCII 字节,则该字段的 UTF8 标志不会被设置(因此它似乎使用的是utf8::decode())。驱动程序也足够智能,不会解码二进制数据。
需要DBD::mysql的 4.004 或更高版本。UTF-8 最初在 MySQL v4.1 中可用。从 v5.0 开始,它是系统默认值。
输入 - PostgreSQL
[edit | edit source]对于 PostgreSQL,从 DBD::Pg 3.0.0 版本开始,如果数据库也设置为 UTF-8,则字符串字段数据的 UTF-8 解码(和编码)是自动的。
对于之前的版本,您必须使用 pg_enable_utf8 数据库句柄属性,它将把所有非二进制数据设置为 UTF-8,而不管 client_encoding 值如何。
use DBI();
my $dbh = DBI->connect('dbi:Pg:test_db', $username, $password,
{pg_enable_utf8 => 1}
);
这意味着您不应该调用utf8::decode()(或任何其他 UTF-8 解码函数)在传入的字符串字段数据上——DBD::Pg 驱动程序会为您完成此操作。驱动程序也足够智能,不会解码二进制数据。
默认的 client_encoding 是使用数据库编码,因此如果您的数据库是 UTF-8,则它将默认设置。在其他情况下,您可能需要告诉 PostgreSQL 在从数据库发送数据时使用 UTF-8
SET CLIENT_ENCODING TO 'UTF8';
或
SET NAMES 'UTF8';
例如,使用 Rose::DB
__PACKAGE__->register_db(
domain => 'development',
...
connect_options => {
pg_server_prepare => 0,
pg_enable_utf8 => 1,
},
post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8';",
);
2. 处理字符串
[edit | edit source]一旦所有传入的字符串都被内部解码为 UTF-8,您就可以像往常一样处理您的文本。正则表达式将起作用(如果使用 Perl v5.8 或更高版本)。
如果您在源代码中创建了包含非 ASCII 字符(高于0x7f)的字符串,请确保您将它们升级到内部 UTF-8 编码
my $text = "\xE0"; # 0xE0 = à in ISO-8859-1
utf8::upgrade($text);
my $unicode_char = "\x{00f1}"; # U+00F1 = ñ
utf8::upgrade($unicode_char);
Perl 5“Unicode 错误”
[edit | edit source](2011-05-03 更新:v5.14 现已可用,最终消除了 Unicode 错误。)
如果没有指定语言环境,如果您有字符在 0x80-0xFF(128-255)范围内的本地/N8CS 字符串,那么\d, \s, \w, \D, \S, \W(因此有正则表达式),以及lc(), uc()等等可能无法按预期工作,因为字符集的非 ASCII 部分(0x80-0xFF)对于这些操作将被忽略。(这是尝试在所有地方使用 UTF-8 的另一个原因。)如果没有语言环境,Perl 无法正确解释此范围内的字符,因为不同的编码在该范围内使用不同的字符,因此它会忽略它们——这被称为 *ASCII 语义*。
有三种方法可以避免这种“Unicode 错误”。最好的方法是升级到 Perl 5.14 并添加一个use 5.014;在文件顶部。其他两种方法涉及让本地编码的字符串切换到 UTF-8 编码——因为当内部编码为 UTF-8 时,将使用 *Unicode 语义*,它始终按预期工作。
1. 遵循“最佳实践”,始终正确解码所有外部输入文本/字节。在解码过程中,发现包含非 ASCII 字符的任何文本/字节将被转换为 UTF-8 内部编码。例如
use Encode;
# suppose $windows1252_octets contains text from an external input, and it contains the character
# "\xE0" (0xE0 = à). String $windows1252_octets will exhibit the Unicode bug -- it won't match /\w/
my $utf8_string = decode('cp1252',$windows1252_octets); # no Unicode bug, $utf8_string matches /\w/
2. 使用utf8::upgrade($native_string)强制 $native_string 切换到 UTF-8 内部编码。(即使字符串只包含 ASCII 字符,它仍然会被“升级”到 UTF-8。)
my $text = "\xE0"; # will exhibit Unicode bug, won't match /\w/
utf8::upgrade($text); # no Unicode bug, matches /\w/
请注意,使用内部 UTF-8 编码,\w表示更大得多的字符集,因此正则表达式操作将变慢(与本地编码相比)。待办事项:实际性能下降是多少?*Unicode 语义* 的 \w 字符集是什么?
另请参见 Unicode::Semantics。
2010-04-19 更新:v5.12 现已可用,并且“大小写转换组件”已修复:“Perl 5.12 现在捆绑了 Unicode 5.2。“feature”pragma 现在支持新的“unicode_strings”功能
use feature "unicode_strings";
这将为字符串上的所有大小写转换操作启用 Unicode 语义,无论它们当前如何内部编码。”阅读 更多。
3. 编码和输出
[edit | edit source]Web 程序的输出包括 STDOUT(发送到您的浏览器以供 CGI 程序使用)、stderr(通常会进入 Web 服务器的错误日志)、数据库写入、日志文件输出等。
如果未对传出的文本进行编码,则文本将使用 Perl 内部格式的字节发送,这可能是本地/N8CS 和 UTF-8 的混合。这可能有效,但不要冒险——“最佳实践”要求明确地对所有输出进行适当编码。
如果您打印一个字符串,其中包含一个字符,其序数值大于 255,Perl 会向您发出警告
$ perl -e 'print "\x{0100}\n"'
Wide character in print at -e line 1.
Ā
要避免此警告,请明确编码输出(如下所述)。
输出 - STDOUT
[edit | edit source]要确保发送回 Web 浏览器(即 STDOUT)的所有输出都使用 UTF8 编码,请将以下内容添加到 Perl 脚本的顶部附近
binmode STDOUT, ":encoding(utf8)";
如果您想要更高效一些(但没有遵循“最佳实践”),您可以选择仅在传出的页面被标记为 UTF-8 时对其进行编码
if(utf8::is_utf8($page)) {
utf8::encode($page);
}
# else, $page is natively encoded, so skip encoding for output
这是一个 片段,可与 CGI::Application 框架一起使用
__PACKAGE__->add_callback('postrun', sub {
my $self = shift;
# Make sure the output is utf8 encoded if it needs it
if($_[0] && ${$_[0]} && utf8::is_utf8(${$_[0]}) ){
utf8::encode( ${$_[0]} );
# ${$_[0]} .= 'utf8::encode() called'; # useful for debugging
}
});
以上代码应放在 CGI::Application 基类中。可选地,该代码可以添加到 cgiapp_postrun() 中。
请注意,如果所有输入 UTF-8 字节都被正确解码,那么所有上述编码技术才能正常工作。
输出 - 数据库
[edit | edit source]如上所述,在“在所有地方使用 UTF-8”模型中,将您的数据库配置为以 UTF-8 存储值。
将数据写入 UTF-8 数据库(INSERT、UPDATE 等)时,确保您的 UTF-8 字符串在写入数据库之前被 UTF-8 编码。不要编码二进制字段数据。
输出 - MySQL
[edit | edit source]如上所述,如果您使用 mysql_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 编码(和解码)将自动进行。这意味着您在使用此属性时不应调用utf8::encode()(或任何其他 UTF-8 编码函数) — 驱动程序会为您执行此操作。驱动程序也很聪明,不会对二进制数据进行编码。
需要 DBD::mysql 的 4.004 或更高版本。UTF-8 最初在 MySQL v4.1 中可用。从 v5.0 开始,它是系统默认值。
如上所述,如果您使用 pg_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 编码(和解码)将自动进行。这意味着您在使用此属性时不应调用utf8::encode()(或任何其他 UTF-8 编码函数) — DBD::Pg 驱动程序会为您执行此操作。驱动程序也很聪明,不会对二进制数据进行编码。
您可能还需要告诉 PostgreSQL 预期传入数据库的 UTF-8(待定:何时?)。
SET CLIENT_ENCODING TO 'UTF8';
或
SET NAMES 'UTF8';
如果您需要写入文件,Perl 可以使用PerlIO层
open my $out_fh, ">:utf8", $filename or die; # auto UTF-8 encoding on write
如果你已经有一个打开的文件句柄
binmode $out2_fh, ':utf8';
要向浏览器提供 UTF-8 编码的页面,“最佳做法”是在 HTTP Content-Type 标头中指定 UTF-8 字符集,并在 HTML 文件中的 content-type <meta> 标记中指定 UTF-8 字符集。CGI.pm 默认发送以下 Content-Type 标头
Content-Type: text/html; charset=ISO-8859-1
添加以下内容以使 UTF-8 而不是 ISO-8859-1 被使用,其中 $q 是您的 CGI 对象
$q->charset('UTF-8');
如果您使用 CGI::Application 框架,请将上述行放在 cgiapp_init() 中。
如果您没有使用 CGI.pm 生成 HTML 标记,请将以下 meta 标记作为 HTML 标记 <header> 部分中的第一个 meta 标记
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
如果您只需要在源代码中的几个字符串中嵌入几个 Unicode 字符,则无需以 UTF-8 格式保存源代码/文件。相反,使用\x{...}或chr()在您的代码中
my $smiley = "\x{263a}";
or
my $smiley = chr(0x263a);
如果您有很多 Unicode 字符,或者您更喜欢以 UTF-8 格式保存源代码,那么您需要告诉 Perl 您的源代码是以 UTF-8 格式编码的。为此,请将以下行添加到您的源代码中
use utf8; # this script is in UTF-8
这是您的程序应该永远拥有上述行的唯一原因 — 请参阅 utf8。
如果您的源代码是以 UTF-8 格式编码的,请确保您的编辑器支持以 UTF-8 格式读取、编辑和写入!
通常,您可能不会注意到 Unicode 问题,直到使用代码点大于 128 的字符。这是因为 ASCII、ISO-8859-1、Windows-1252 和 UTF-8 都对前 128 个 Unicode 代码点使用相同的单字节值进行编码。要对您的应用程序进行良好的 Unicode 测试,请尝试使用0x80 - 0x9F(128-159)范围内的字符,以及大于0xFF (255).
如果您打印包含序数值大于 255 的字符的字符串,Perl 将向您发出警告(因此它是一个“宽”字符,需要多于一个字节的存储空间)
print 中的宽字符位于…第…行
显式地对您的输出进行编码以避免此警告。
如果您收到此错误,您的代码可能正在尝试第二次解码同一个字符串,这将失败。
如果您遵循了上述步骤,但您的页面显示不正常,可能是您的 Web 服务器配置为始终在标头中发送特定字符编码,例如 ISO-8859-1。要确定 Web 服务器是否正在发送 content-type 标头
$ lwp-request -de www.bing.com | grep Content
Apache 可能配置了以下内容
AddDefaultCharset ISO-8859-1
如果可以,请删除该行,或将其更改为
AddDefaultCharset UTF-8
如果服务器提供的页面都使用 UTF-8。另请参阅 当 Apache 和 UTF-8 发生冲突时。
由于您正在学习字符编码,因此您需要了解国际 ISO-8859-1 和 Microsoft 专有 Windows-1252 之间的区别。来自 Windows-1252
[Windows-1252] 在可打印字符方面是 ISO 8859-1 的超集,但它与 IANA 的 ISO-8859-1 不同,因为在 80 到 9F(十六进制)范围内它使用的是可显示字符,而不是控制字符。[…] 通常将 Windows-1252 文本错误地标记为字符集标签 ISO-8859-1。[…] 大多数现代 Web 浏览器和电子邮件客户端将媒体类型字符集 ISO-8859-1 视为 Windows-1252 以适应这种错误标记。这现在是 HTML5 规范中的标准行为,该规范要求以 ISO-8859-1 为广告的文档实际上使用 Windows-1252 编码进行解析。
以下是一个有趣的程序可以尝试
my @undefined_chars_in_windows_1252 = (0x81, 0x8d, 0x8f, 0x90, 0x9d);
my %h = map { $_ => undef } @undefined_chars_in_windows_1252;
foreach my $i (0x80 .. 0x9f) {
next if exists $h{$i};
printf "%02x:%c ", $i,$i;
}
您看到了什么?您看到了 Windows-1252 字符,没有字符,还是方框?如果您使用的是 PuTTY,请更改设置...窗口,翻译,然后尝试选择 ISO-8859-1 或 Windows-1252 并再次运行该程序。
Microsoft Word 使用那些漂亮的左和右奇特/智能引号。如果您将这些字符复制粘贴到使用 Windows-1252 字符集(或可能甚至 ISO-8859-1 字符集)提供的 Web 表单中,这些字符可能会使用模糊的0x80-0x9F(128-159)范围提交到 Web 服务器。(回想一下,Unicode 在此范围内定义了控制字符 — 而不是像智能引号这样的可打印字符。)如果您的 Perl 脚本没有正确解码提交的表单(即,根据 Web 表单使用的相同字符编码),您将得到乱码。
正确地解码和编码,您就不会遇到 Microsoft 智能引号或模糊范围内的任何其他字符的任何问题。更好的是,如果您将所有 Web 页面提供为 UTF-8,提交的表单不应该包含这些模糊值,因为“粘贴”操作应该自动将这些字符转换为有效的 Unicode 字符。然后,您的 Perl 脚本将只接收有效的 UTF-8 编码字符。
奇怪的字符: �
这是 Unicode 的“替换字符”(代码点 U+FFFD),用于指示 Unicode 解析器(如浏览器)何时无法解码 Unicode 编码数据的流。问题可能是链中某处的编码/解码问题。(U+FFFD 在 UTF-8 中编码为 EF BF BD。如果您保存 Web 页面,然后在 bvi 中打开它,您可能会看到 EF BF BD。)IE 将替换字符显示为空方框。Firefox 使用带有问号的黑色菱形。
通常,这些替换字符出现是因为 HTML 数据是 Windows-1252 编码的,但浏览器被指示使用 UTF-8 编码。在您的浏览器中,选择查看->字符编码,看看是否设置为 UTF-8。如果是,尝试选择Windows-1252或西欧 (Windows),看看是否解决了问题。如果解决了,那么您就知道 Web 服务器正在提供错误的字符编码——发送的内容(即数据如何编码)与浏览器被告知使用的字符集(即 HTTP 标头和/或元标记)之间存在不匹配。如果无法解决问题,可能是您的计算机上没有安装 Unicode 字体,或者 Unicode 字体没有该特定字符的字形。
奇怪的字符: ‘ ’ “ †• – —
这些是与以下 Windows-1252 字符的多字节 UTF-8 编码相对应的单个字符
‘ ’ “ ” • – —
它们在模糊的 0x80-0x9F (128-159) 范围内。通常,这些字符出现是因为 HTML 数据是 UTF-8 编码的,但浏览器被指示使用 ISO-8859-1 或 Windows-1252。在您的浏览器中,尝试将编码更改为UTF-8,看看是否解决了问题。如果无法解决问题,或者编码已经设置为 UTF-8,则可能在某个地方存在双重编码问题。
奇怪的字符: ‘ ’ “ †• – —
这些也对应于模糊的 0x80-0x9F (128-159) 范围内的某些字符。如果您看到上述序列,则可能是您忘记在您的 Perl 程序中解码传入的 UTF-8 数据(例如从 UTF-8 编码的 HTML 表单提交的表单数据),然后您将它 UTF-8 编码以进行输出——本机编码的字符串被 UTF-8 编码(不好)。通过调用以下方法来解决问题utf8::decode()在传入的 UTF-8 编码数据上。
- 确保您的编辑器支持以 UTF-8 格式读取、编辑和写入
- 确保您将编辑器设置为使用 Unicode 字体
- 确保您已安装 Unicode 字体
如果您有 此页面 上列出的其中一个 Microsoft 产品,您应该拥有Arial Unicode MS字体。如果未安装,请按照以下步骤安装:添加/删除程序,选择 MS-Office,添加或删除功能,单击“选择高级”,Office 共享功能,国际支持,通用字体。应用更改并重新启动您的 Web 浏览器。
如果您专门要求 UTF-8 文本,但您收到的八位字节流不是有效的 UTF-8 编码,在很多情况下,您可能可以假设传入的文本/八位字节是 ISO-8859-1/Latin-1 或 Windows-1252。使用 Windows-1252 解码,因为它是一个 ISO-8859-1 的超集。
如果您不解码 UTF-8 文本/八位字节,Perl 将假设它们使用 N8CS(通常是 ISO-8859-1/Latin-1)编码。这意味着多字节 UTF-8 字符的单个八位字节被视为单独的字符(不好)。如果这些单独的字符后来被编码为 UTF-8 以进行输出,则会导致“双重编码”。这类似于 HTML 双重编码——例如,&gt; 而不是 >。
大多数现代浏览器和文字处理器执行 字体替换,这意味着如果一个字符不在当前字体中,应用程序将搜索您所有的字体,直到找到包含该字符的字体,然后它将使用该字体中的字形显示该字符。
有时 IE7 和 IE8 似乎无法正确执行字体替换。一种解决方法是在 CSS 中将 Unicode 字体指定为第一个字体font-family属性。IE6 不被视为现代浏览器,它不会执行字体替换。
在 Windows 上,您始终可以使用字符映射应用程序来选择、复制和(切换到您的应用程序,然后)粘贴 Unicode 字符。确保“字符集”下拉框设置为“Unicode”。您也可以使用该应用程序查看字体、字符以及每个字符的 Unicode 代码点值。
my $utf8_char = "\x{263a}"; # for codepoints above 0xFF
$utf8_char =~ /\x{263a}/; # same syntax for regex
my $cloud_char = chr(0x2601); # run-time, ord() does the reverse
如果您的 Perl 源代码文件是 UTF-8 格式的,您可以直接输入 Unicode 字符
use utf8; # tells Perl this file is UTF-8 encoded
my $utf8_char = "☺"; # U+263a, "White Smiling Face"
在 Windows 上
- 要从 Windows-1252 代码页 插入字符:打开数字锁定键,按住 Alt,然后使用数字键盘,输入 0 后面跟着您想要的字符的十进制值。
- 要从当前 DOS 代码页(通常是 CP-437)插入字符:按照上述步骤操作,但不要输入初始的 0。
但是等等,我们想要插入的是 Unicode 字符,而不是 Windows-1252 或 CP-437 字符!好吧,如果应用程序期望 UTF-8,Windows 将为我们将其转换为 Unicode/UTF-8。
在 Web 表单(文本框或文本区域)中,输入 Alt-0147 以从 Windows-1252 字符集生成那些讨厌的智能引号之一。如果网页的字符编码设置为 UTF-8,Windows 应该将 147 字符转换为相应的 UTF-8 编码。(在内部,Windows 可能将 0147 转换为 UTF-16,然后将其转换为应用程序正在使用的字符集。在这种情况下,字符集是 Unicode,而 Windows-1252 字符 147 被转换为其 Unicode 代码点等效值,U+201C。)提交表单时,该字符应作为三个八位字节发送到 Web 服务器:E2 80 9C——这就是 U+201C 使用 UTF-8 编码时的样子。
如果网页的字符编码设置为 Windows-1252,则该字符应作为单个八位字节发送:0x93(即十进制 147)。如果网页的字符编码设置为 ISO-8859-1,则该字符也将作为单个八位字节发送,但其值可以是 0x93 或 0x22(0x22 是 ASCII 和 ISO-8859-1 引号字符)。如果浏览器在指定 ISO-8859-1 时使用超集 Windows-1252 编码,则会发送 0x93。否则,该字符将转换为 ISO-8859-1 中正式定义的唯一引号字符,0x22。
希望您能明白为什么必须知道用于传入表单/文本的编码方式,以便能够在您的 Perl 程序中对其进行正确解码(作为 UTF-8 或 Windows-1252)。
另请参见 如何输入… - Yahoo Answers。
从 Perl 5.8.7 开始,UTF-8 是严格的、正式的 UTF-8。如果您尝试编码或解码无效的 UTF-8,例如,Encode 模块会抱怨。
encode("UTF-8", "\x{FFFF_FFFF}", 1); # croaks
相比之下,utf8 是宽松的、松散的版本,允许几乎任何 4 字节值
encode("utf8", "\x{FFFF_FFFF}", 1); # okay
encode_utf8("\x{FFFF_FFFF}", 1); # okay
Encode 从 2.10 版开始知道区别。
utf8::encode()和utf8::decode()使用正式的 UTF-8。
要解码和编码 UTF-8,您可以使用 Encode 模块或 Perl 核心在 utf8:: 包中定义的函数。Encode 模块更灵活,允许不同的方法来处理格式错误的数据。但是,utf8:: 包可以执行一些不同的技巧。
您应该注意 Encode 模块中的一个错误:每当使用 Encode 模块解码文本时,UTF8 标志总是被打开。文档会让您相信,如果文本只包含 ASCII 字符并且您正在解码 UTF-8,则 UTF8 标志是关闭的。事实并非如此——该标志始终处于打开状态,如下表所示。
如果可以在解码后关闭 UTF8 标志,则可以提高性能(如果文本只包含 ASCII 八位字节,则可以这样做)。使用utf8::decode()为了达到这种效率,因为如果八位字节序列只包含 ASCII 八位字节,它不会打开标志。(这是我通常使用的解码函数。)
下面,请查看 Encode 文档,了解 CHECK 选项,这些选项与模块处理畸形数据的机制相关。
函数 | UTF8 标志 | 描述 / 备注 |
---|---|---|
$flag = utf8::is_utf8($string); | N/A | 测试 $string 是否以 UTF-8 编码。如果否,返回 false;否则返回 true。 |
$flag = utf8::decode($utf8_octets); | 依赖 | 尝试就地将 UTF-8 八位字节序列转换为相应的 N8CS 或 UTF-8 字符串,具体取决于情况。如果$utf8_octets包含非 ASCII 八位字节(即多字节 UTF-8 编码字符),则 UTF8 标志将被打开,结果字符串为 UTF-8。否则,UTF8 标志将保持关闭状态,结果字符串为 N8CS。这是唯一可能导致 N8CS 字节字符串的解码函数。如果返回 false$utf8_string未正确编码为 UTF-8;否则返回 true。 |
$utf8_string = decode('UTF-8', $utf8_octets [, CHECK]) | 打开 | 将 UTF-8 八位字节序列解码为 UTF-8 字符串。遵循严格的官方 UTF-8 解码规则(有关讨论,请参见上一节)。 |
$utf8_string = decode('utf8', $utf8_octets [, CHECK]) | 打开 | 将 UTF-8 八位字节序列解码为 UTF-8 字符串。遵循宽松的解码规则(有关讨论,请参见上一节)。 |
$utf8_string = decode_utf8($utf8_octets [, CHECK]) | 打开 | 将 UTF-8 八位字节序列解码为 UTF-8 字符串。等效于decode("utf8", $utf8_octets),因此使用宽松解码。 |
$octet_count = utf8::upgrade($n8cs_string); | 打开 | 就地将 N8CS 字节字符串转换为相应的 UTF-8 字符串。返回现在用于以 UTF-8 形式内部表示字符串的八位字节数。此函数应用于将 N8CS 字节字符串中包含的字符转换为 UTF-8,从而避免 Perl 5“Unicode 错误”。0x80-0xFFrange to UTF-8, thereby avoiding the Perl 5 "Unicode Bug". |
utf8::encode($string) | 关闭 | 就地将 N8CS 或 UTF-8 $string 转换为 UTF-8 八位字节序列。 |
$utf8_octets = encode('UTF-8', $string [, CHECK]) | 关闭 | 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。遵循严格的官方 UTF-8 编码规则(有关讨论,请参见上一节)。 |
$utf8_octets = encode('utf8', $string) | 关闭 | 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。遵循宽松的 UTF-8 编码规则(有关讨论,请参见上一节)。由于所有可能的字符都有宽松的 utf8 表示形式,因此此函数不会失败。 |
$utf8_octets = encode_utf8($string) | 关闭 | 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。等效于encode("utf8", $string),因此使用宽松编码。由于所有可能的字符都有宽松的 utf8 表示形式,因此此函数不会失败。 |
$flag = utf8::downgrade($utf8_string [, FAIL_OK]); | 关闭 | 就地将 UTF-8 字符串转换为等效的 N8CS 字节字符串。如果失败,则$utf8_string不能用 N8CS 编码表示。如果 FAIL_OK 为真,则在失败时会死亡,否则返回 false。成功时返回 true。 |
Perl 字符编码
[edit | edit source]要确定 Perl 支持哪些字符编码
perl -MEncode -le "print for Encode->encodings(':all')"
重要的是要记住,Perl 内部只使用两种字符编码:本机/字节和 UTF-8/字符。任何使用除 N8CS 之外的编码(平台的本机 8 位字符集,通常为 ISO-8859-1/Latin-1)的字符都必须在进入 Perl 时解码。
网站“x”使用什么?
[edit | edit source]查看页面,然后在浏览器中,查看->字符编码以查看选择了哪种编码。另外,查看 HTML 源代码,看看是否存在元标签
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
您还可以使用以下方法查看返回了哪个 Content-Type 标头
$ lwp-request -de www.bing.com | grep Content
此维基使用 UTF-8。
HTML 字符实体
[edit | edit source]在您的 UTF-8 旅程中,您可能会遇到 HTML 字符实体。从 HTML 4.0 开始,支持 252 个 字符实体。每个实体都有一个 Unicode 代码点和一个实体名称。可以在 HTML 标记中使用它们中的任何一个。例如,注册符号可以在 HTML 中表示为®或®
许多字体支持此字符集,如果该字符集足够满足您的应用程序需求,则可能不需要 UTF-8,但您的应用程序需要在需要特殊字符的地方使用 HTML 编码。
操作系统和 Unicode
[edit | edit source]有趣的是,流行的操作系统使用哪种 Unicode 编码。从 维基百科 中可以看出:“Windows NT(及其后代,Windows 2000、Windows XP、Windows Vista 和 Windows 7)使用 UTF-16 作为唯一的内部字符编码。Java 和 .NET 字节码环境、Mac OS X 和 KDE 也将其用于内部表示。UTF-8 已成为大多数类 Unix 操作系统的主要存储编码(尽管某些库也使用其他编码),因为它可以相对轻松地替换传统的扩展 ASCII 字符集。”
参考资料
[edit | edit source]- 每个软件开发人员必须了解的关于 Unicode 和字符集的绝对必要内容(没有借口!) - 由 Joel Spolsky 撰写
- 关于字符与字节的 FMTYEWTK - Perlmonks
- CGI::Application 和 UTF-8 表单处理示例 - 由 Mark Rajcok 撰写
- Perl Unicode 教程
- Perl Unicode 常见问题解答
- Perl utf8 pragma
- Perl Encode 模块 - 处理所有字符编码和解码
- Unicode - 维基百科
- Perl Unicode 简介
- Perl 中的 Unicode 支持
- Unicode::Semantics - 解决 Perl 5 Unicode 错误
- CPAN 上有许多 Unicode:xxx 模块
- 使用 MySQL 进行 UTF-8 往返 - Perlmonks
- CGI::Application - 处理和输出 utf8 的正确方法 - Perlmonks
- 理解 CGI.pm 和 UTF-8 处理 - Perlmonks
- Unix/Linux 的 UTF-8 和 Unicode 常见问题解答
- Perl Unicode 邮件列表 <[email protected]>
脚注
[edit | edit source]^ - N8CS 是为本文档创造的术语。不要指望在其他地方看到这个术语。