UTF-8 には、文字ではなく UTF-8 を扱いながら、ユーザーが望むことを実行できる優れたプロパティがいくつかあります。まず、UTF-8 が必要です。
use Encode qw( encode_utf8 );
my $bytes = encode_utf8($str);
次に、コードポイント間で分割します。0b0xxxxxxx
すべてのコード ポイントの UTF-8 エンコーディングは、またはに一致するバイトで始まり0b11xxxxxx
、コード ポイントの途中でこれらのバイトが見つかることはありません。つまり、前に切り捨てたい
[\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"
いいですよね?いいえ。上記は、書記素の途中で切り捨てられる可能性があります。書記素 (具体的には、「拡張書記素クラスター」) は、誰かが単一の視覚的単位として認識するものです。たとえば、「é」は書記素ですが、2 つのコードポイントを使用してエンコードできます ( "\x{0065}\x{0301}"
)。2 つのコード ポイントの間を切り取ると、有効な UTF-8 になりますが、「é」は「e」になります。それが受け入れられない場合は、上記の解決策もありません。(オレグのソリューションも同じ問題に悩まされています。)
残念ながら、UTF-8 のプロパティは、ここで私たちを助けるにはもはや十分ではありません。一度に 1 つの書記素を取得し、収まらなくなるまで出力に追加する必要があります。
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"