将utf8字节修剪为'length'并清理数据

时间:2012-06-08 17:01:56

标签: perl utf-8

我有utf8字节序列,需要将其修剪为30bytes。这可能导致最后的序列不完整。我需要弄清楚如何删除不完整的序列。

e.g

$b="\x{263a}\x{263b}\x{263c}";
my $sstr;

print STDERR "length in utf8 bytes =" . length(Encode::encode_utf8($b)) . "\n";
{
use bytes;
$sstr= substr($b,0,29);
}

#After this $sstr contains "\342\230\272\342"\0 
# How to remove \342 from the end

2 个答案:

答案 0 :(得分:6)

UTF-8有一些简洁的属性,允许我们在处理UTF-8而不是字符时做你想做的事。首先,你需要UTF-8。

use Encode qw( encode_utf8 );
my $bytes = encode_utf8($str);

现在,要在代码点之间进行拆分。每个代码点的UTF-8编码将以匹配0b0xxxxxxx0b11xxxxxx的字节开头,您将永远不会在代码点中间找到这些字节。这意味着你想在

之前截断
[\x00-\x7F\xC0-\xFF]

我们一起得到:

use Encode qw( encode_utf8 );

my $max_bytes = 8;
my $str = "\x{263a}\x{263b}\x{263c}";  # ☺☻☼

my $bytes = encode_utf8($str);
$bytes =~ s/^.{0,$max_bytes}(?![^\x00-\x7F\xC0-\xFF])\K.*//s;

# $bytes contains encode_utf8("\x{263a}\x{263b}")
#      instead of encode_utf8("\x{263a}\x{263b}") . "\xE2\x98"

太好了,是吗?不。以上可以在字形中间截断。字形(特别是“扩展的字形簇”)是人们将其视为单个视觉单元的东西。例如,“é”是字形,但可以使用两个代码点("\x{0065}\x{0301}")进行编码。如果你在两个代码点之间切换,它将是有效的UTF-8,但“é”将成为“e”!如果这是不可接受的,上述解决方案也不是。 (奥列格的解决方案也遇到了同样的问题。)

不幸的是,UTF-8的属性不再足以帮助我们。我们需要一次抓取一个字素,然后将其添加到输出中,直到我们无法使用它为止。

my $max_bytes = 6;
my $str = "abcd\x{0065}\x{0301}fg";  # abcdéfg

my $bytes = '';
my $bytes_left = $max_bytes;
while ($str =~ /(\X)/g) {
   my $grapheme = $1;
   my $grapheme_bytes = encode_utf8($grapheme);
   $bytes_left -= length($grapheme_bytes);
   last if $bytes_left < 0;
   $bytes .= $grapheme_bytes;
}

# $bytes contains encode_utf8("abcd")
#      instead of encode_utf8("abcde")
#              or encode_utf8("abcde") . "\xCC"

答案 1 :(得分:4)

首先,请不要使用bytes(并且绝不假设Perl中的任何内部编码)。正如文档所说:这个实用程序反映了将Unicode合并到perl中的早期尝试,并且已被取代&lt; ...&gt;强烈建议不要将此模块用于调试以外的任何其他目的。

要在行尾删除不完整的序列,假设它包含八位字节,请使用Encode::decode的{​​{1}}处理模式,一旦命中无效序列就停止处理,然后只返回编码结果:

Encode::FB_QUIET

请注意,如果您计划将来使用其他编码,并非所有编码都支持此处理方法。