Perl で Unicode Character について触る
Python の unicodedata モジュール - bkブログ を読んで,それ Perl だとどうよと思ったんで対応をまとめてみました。utf8
flag や Encode モジュールについては特に解説しませんので。
文字の名前を取得する
charnames モジュール(Perl 5.6 より付属)の viacode
関数を使うと文字の名前を取得することができます。引数として文字列ではなく文字コードを渡すところが要注意です。
use utf8; use charnames qw( :full ); print charnames::viacode(ord 'A')), "\n"; # 'LATIN CAPITAL LETTER A' print charnames::viacode(ord 'あ')), "\n"; # 'HIRAGANA LETTER A'
文字列リテラルとして標準で Wide character を認識するように,use utf8
を使用しています。
文字の名前から文字を取得する
さきほどの charnames モジュールを use
すると,文字列のエスケープ表記 \N{...}
で文字名を利用することができるようになります。
use utf8; use charnames qw( :full ); binmode \*STDOUT, ':utf8'; print "\N{LATIN CAPITAL LETTER A}", "\n"; # 'A' print "\N{HIRAGANA LETTER A}", "\n"; # 'あ'
Wide character を標準出力に出力できるよう,STDOUT
ハンドルの binmode
として :utf8
を指定しています。
文字の幅を調べる
CPAN モジュールの Unicode::EastAsianWidth を使うと,regex syntax のカスタムパターン(\p{...}
)に InEastAsianAmbiguous
, InEastAsianFullwidth
, InEastAsianHalfwidth
, InEastAsianNarrow
, InEastAsianNeutral
, InEastAsianWide
というパターンが追加されます。
また,convenience pattern として InFullwidth
, InHalfwidth
も追加されます。
わかりにくいので実例をあげます。
use utf8; # 全角のA if ('A' =~ m/ \p{InEastAsianFullwidth} /xmso) { print "East Asian Full-width\n"; }
Python の east_asian_width
っぽくないので,ラッパ関数を書いてみました。
use utf8; use Unicode::EastAsianWidth; sub say { print join(q{}, @_), "\n"; } sub east_asian_width { my ($chr) = @_; my @eaw = ( qr/\p{InEastAsianHalfwidth}/ => 'H', qr/\p{InEastAsianWide}/ => 'W', qr/\p{InEastAsianFullwidth}/ => 'F', qr/\p{InEastAsianNarrow}/ => 'Na', qr/\p{InEastAsianAmbiguous}/ => 'A', qr/\p{InEastAsianNeutral}/ => 'N', ); while (@eaw) { my ($pat, $cat) = splice @eaw, 0, 2; if ($chr =~ m/$pat/) { return $cat; } } return 'N'; } say east_asian_width("A"); # 全角のA # 'F' say east_asian_width("ア"); # 半角のア # 'H' say east_asian_width("あ"); # 'W' say east_asian_width("A"); # ASCIIのA # 'Na' say east_asian_width("\x{2163}"); # IV # 'A' say east_asian_width("\x{0e20}"); # タイ語の文字 # 'N'
また,InFullwidth
, InHalfwidth
を使うと簡単に文字列幅を計算することができます。具体例として http://d.hatena.ne.jp/tokuhirom/20070514/1179108961 をあげておきます。
ところで,実は,Unicode::EastAsianWidth の 1.10 には致命的なバグがあり,上記のような結果にはなりません*1。Bug #31862 for Unicode-EastAsianWidth: Makefile.PL generates invalid EastAsianWidth table のパッチをあて,自力で EastAsianWidth.txt
から生成する必要があります。という部分がちょっと難しいのでわからない方は古いリビジョンを使用するか,オードリー先生が修正するのを待ちましょう。んーテスト大切。
2008/02/12 追記: 1.30 で直ったお。私は未検証ですが。
文字の数値を調べる
この機能,一見 Perl や CPAN モジュールにはなさそうに見えます。
perluniintro - Perl Unicode introduction - metacpan.orgString-To-Number Conversions
Unicode does define several other decimal--and numeric--characters besides the familiar 0 to 9, such as the Arabic and Indic digits. Perl does not support string-to-number conversion for digits other than ASCII 0 to 9 (and ASCII a to f for hexadecimal).
そもそもこのプロパティ(数値・数字へのマッピング)はどこに定義されているのかというと,Unicode Character Database にて定義されています。
UCD Documentation File Replaced
(6) If the character has the decimal digit property, as specified in Chapter 4 of the Unicode Standard, then the value of that digit is represented with an integer value in fields 6, 7, and 8. (7) If the character has the digit property, but is not a decimal digit, then the value of that digit is represented with an integer value in fields 7 and 8. This covers digits that need special handling, such as the compatibility superscript digits. (8) If the character has the numeric property, as specified in Chapter 4 of the Unicode Standard, the value of that character is represented with an positive or negative integer or rational number in this field. This includes fractions as, e.g., "1/5" for U+2155 VULGAR FRACTION ONE FIFTH.
Some characters have these properties based on values from the Unihan data file. See Numeric_Type, Han.
で,この UnicodeData.txt
データベースを扱うモジュールが,Unicode::UCD モジュール(Perl 5.8 より付属*2)です。
UCD の decimal
, digit
, numeric
プロパティに格納されているので charinfo
関数にて取得します*3。
use utf8; use Unicode::UCD qw( charinfo ); sub say { print join(q{}, @_), "\n"; } say charinfo(ord '7')->{decimal}; # decimal # '7' say charinfo(ord "\x{2466}")->{digit}; # circle 7 digit # '7' say charinfo(ord "\x{215b}")->{numeric}; # 1/8 numeric # '1/8' say charinfo(ord "\x{2166}")->{numeric}; # VII numeric # '7' say charinfo(ord "七")->{numeric}; # 漢数字はない # '' say charinfo(ord "\x{3226}")->{numeric}; # でも括弧つき漢数字はある # '7'
Python の関数と異なり,numeric が文字列として返されることに注意が必要です。
文字の種類を調べる
同じく Unicode::UCD のデータベースから取得することができます。
use utf8; use Unicode::UCD qw( charinfo ); sub say { print join(q{}, @_), "\n"; } say charinfo(ord 'a')->{category}; # 'Ll' - Letter, Lowercase say charinfo(ord 'A')->{category}; # 'Lu' - Letter, Uppercase say charinfo(ord 'A')->{category}; # 全角も同様 # 'Lu' - Letter, Uppercase say charinfo(ord 'あ')->{category}; # 'Lo' - Letter, Other
その他(正規化・合成・分解)
Unicode::Normalize モジュール(Perl 5.8 より付属)を使うとテキストの正規化や合成,分解を行うことができます。拙著ですが Unicode::Normalize で遊ぶ - daily dayflower をご参照下さい。
HTML エンティティの変換
CPAN モジュールの HTML::Entities モジュール*4を使うと,utf8 flagged string を escape / unescape することができます。
use utf8; use HTML::Entities; binmode \*STDOUT, ':utf8'; print encode_entities("\x{20ac}"), "\n"; # '€' print HTML::Entities::encode_entities_numeric("\x{20ac}"), "\n"; # '€' printf "%04x\n", ord decode_entities('€'); # '20ac' printf "%04x\n", ord decode_entities("€") # '20ac'