Unicode::Numeric
Perl で Unicode Character について触る - daily dayflower で書いたように文字の数値を調べるには Unicode::UCD を使えばよかったんですが,それを知らずに途中まで作りかけていた数値変換モジュールを一通りインプリメントしたので,さらしておきます。
利点は…… Perl 5.6 でも使えることくらい?
package Unicode::Numeric; use strict; use warnings; use 5.006; use base qw( Exporter ); our @EXPORT = qw( decimal digit numeric numeric_as_string ); our $VERSION = '0.01'; our $USE_UCD; BEGIN { $USE_UCD = eval { require Unicode::UCD; 1; }; } my $NumericMap; my $BundledVersion; sub decimal { return _retrieve_item(@_)->[0]; } sub digit { return _retrieve_item(@_)->[1]; } sub numeric_as_string { return _retrieve_item(@_)->[2]; } sub numeric { my $v = numeric_as_string(@_); return if ! defined $v; return eval $v; } sub UnicodeVersion { if ($USE_UCD) { return Unicode::UCD::UnicodeVersion(); } else { _init_map(); return $BundledVersion; } } sub _retrieve_item { my ($c) = @_; return [] if ! defined $c; if ($USE_UCD) { my $ci = Unicode::UCD::charinfo(ord $c); return [] if ! $ci; for (qw( decimal digit numeric )) { $ci->{$_} = undef if $ci->{$_} eq q{}; } return [ $ci->{decimal}, $ci->{digit}, $ci->{numeric} ]; } $c = sprintf '%X', ord $c; _init_map(); return [] if ! exists $NumericMap->{$c}; return $NumericMap->{$c}; } sub _init_map { return if ref $NumericMap eq 'HASH'; $NumericMap = {}; foreach my $line (split /[\r\n]+/, _map_data()) { next if $line =~ m'^###'o; if ($line =~ m' \A \# v ( \d+ \S+ ) 'xmso) { $BundledVersion = $1; next; } my @c = split /\t/, $line; my $code = shift @c; $code =~ s{ \A 0+ }{}xmso; @c = map { $_ eq q{} ? undef : $_ } @c; $NumericMap->{$code} = \@c; } } sub _map_data { return <<"END_MAP"; ### BEGIN ### #v5.1.0 0030\t0\t0\t0 0031\t1\t1\t1 0032\t2\t2\t2 ### snip ### 1D7FD\t7\t7\t7 1D7FE\t8\t8\t8 1D7FF\t9\t9\t9 ### END ### END_MAP } 1; __END__ =head1 NAME Unicode::Numeric - Unicode::Numeric =cut
実際のテーブルが snip してあるんでこのままでは使えません。Makefile.PL
で自動生成するようにしています。
ということで Makefile.PL
の実装はこちら。Unicode::EastAsianWidth
のをパクった参考にしました。
use strict; use inc::Module::Install; my $TargetModule = 'lib/Unicode/Numeric.pm'; my $UnicodeDataFile = 'unicore/UnicodeData.txt'; my $UnicodeVerFile = 'unicore/version'; my $BundledVersion = '5.1.0'; _build_pm(); name 'Unicode-Numeric'; all_from 'lib/Unicode/Numeric.pm'; build_requires 'Test::More'; use_test_base; auto_include; WriteAll; exit; sub _build_pm { my ($path, $fname, $version); foreach ('.', @INC) { $path = $_; last if -e "$_/$UnicodeDataFile" && -e "$_/$UnicodeVerFile"; } my $use_bundled = 1; TRY: { $fname = "$path/$UnicodeVerFile"; if (! -e $fname) { print {*STDERR} "*** Cannot find $UnicodeVerFile.\n"; last TRY; } if (! open my $UCV, '<', $fname) { print {*STDERR} "*** Cannot read $fname ($!).\n"; last TRY; } else { $version = do { local $/; <$UCV> }; close $UCV; chomp $version; if ($version <= $BundledVersion) { if (-e $TargetModule) { print {*STDERR} "*** Installed table not newer than the bundled.\n"; last TRY; } } } $use_bundled = 0; } if ($use_bundled) { print {*STDERR} "*** Using bundled table.\n"; return; } $fname = "$path/$UnicodeDataFile"; print {*STDERR} "*** Using ${fname}.\n"; my $table = q{}; if (! open my $UCD, '<', $fname) { print {*STDERR} "*** Cannot read table ($!), falling back to default.\n"; return; } else { while (<$UCD>) { chomp; my @c = split ';'; next if join(q{}, @c[6..8]) eq q{}; $table .= join('\t', @c[0, 6..8]) . "\n"; } close $UCD; } $fname = (-e 'Numeric.pm.in') ? 'Numeric.pm.in' : $TargetModule; my $out = q{}; if (! open my $PM, '<', $fname) { print {*STDERR} "*** Cannot read module ($!), falling back to default.\n"; return; } else { while (<$PM>) { $out .= $_; last if /^### BEGIN ###$/ } $out .= sprintf "#v%s\n", $version; $out .= $table; while (<$PM>) { $out .= $_ and last if /^### END ###$/ } while (<$PM>) { $out .= $_ } close $PM; } chmod 0644, $TargetModule if -e $TargetModule; if (! open my $PM, '>', $TargetModule) { print {*STDERR} "*** Cannot write to module ($!), falling back to default.\n"; return; } else { print {$PM} $out; close $PM; } }
実は Perl 5.8.x 付属の UnicodeData.txt
だと Unicode Data のバージョンが 4.1.0 なので上記のような出力にはならないです。
いつの日か某所にあげときます。