File Coverage

blib/lib/Lingua/ZHO/Num2Word.pm
Criterion Covered Total %
statement 72 90 80.0
branch 21 34 61.7
condition 20 44 45.4
subroutine 12 19 63.1
pod 8 9 88.8
total 133 196 67.8


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8; -*-
2             #
3             # (c) 2003-2010 PetaMem, s.r.o.
4             #
5              
6             package Lingua::ZHO::Num2Word;
7             # ABSTRACT: Converts numeric values into their Chinese string equivalents
8              
9 1     1   147148 use 5.16.0;
  1         5  
10 1     1   7 use utf8;
  1         2  
  1         17  
11 1     1   33 use warnings;
  1         1  
  1         62  
12              
13             # {{{ use block
14              
15 1     1   5 use Carp;
  1         4  
  1         93  
16 1     1   6 use Exporter;
  1         2  
  1         62  
17 1     1   6 use base 'Exporter';
  1         2  
  1         202  
18 1     1   8 use vars qw($Charset @EXPORT_OK);
  1         2  
  1         595  
19              
20             # }}}
21             # {{{ variables declaration
22             our $VERSION = '0.2603300';
23              
24             @EXPORT_OK = ('number_to_zh', 'num2zho_ordinal');
25              
26             $Charset = 'pinyin';
27              
28             our %MAP = (
29             ($] >= 5.006) ? eval ## no critic
30             q(
31             'traditional' => {
32             mag => [ '', split(' ', "萬 億 兆 京 垓 秭 穰 溝 澗 正 載 極 恆河沙 阿僧祇 那由他 不可思議 無量大數") ],
33             ord => [ '', split(' ', "十 百 千") ],
34             dig => [ split(' ', "零 一 二 三 四 五 六 七 八 九 十") ],
35             dot => "點",
36             neg => "負",
37             },
38             'simplified' => {
39             mag => [ '', split(' ', "万 亿 兆 京 垓 秭 穰 沟 涧 正 载 极 恒河沙 阿僧祗 那由他 不可思议 无量大数") ],
40             ord => [ '', split(' ', "十 百 千") ],
41             dig => [ split(' ', "零 一 二 三 四 五 六 七 八 九 十") ],
42             dot => "点",
43             neg => "负",
44             },
45             ) : (),
46             'big5' => {
47             mag => [ '', split(' ', "\xB8U \xBB\xF5 \xA5\xFC \xA8\xCA \xAB\xB2 \xD2\xF1 \xF6\xF8 \xB7\xBE \xBC\xEE \xA5\xBF \xB8\xFC \xB7\xA5 \xAB\xED\xAAe\xA8F \xAA\xFC\xB9\xAC\xAC\xE9 \xA8\xBA\xA5\xD1\xA5L \xA4\xA3\xA5i\xAB\xE4\xC4\xB3 \xB5L\xB6q\xA4j\xBC\xC6") ],
48             ord => [ '', split(' ', "\xA4Q \xA6\xCA \xA4d") ],
49             dig => [ split(' ', "\xB9s \xA4\@ \xA4G \xA4T \xA5| \xA4\xAD \xA4\xBB \xA4C \xA4K \xA4E \xA4Q") ],
50             dot => "\xC2I",
51             neg => "\xADt",
52             },
53             'gb' => {
54             mag => [ '', split(' ', "\xCD\xF2 \xD2\xDA \xD5\xD7 \xBE\xA9 \xDB\xF2 \xEF\xF6 \xF0\xA6 \xB9\xB5 \xBD\xA7 \xD5\xFD \xD4\xD8 \xBC\xAB \xBA\xE3\xBA\xD3\xC9\xB3 \xB0\xA2\xC9\xAE\xEC\xF3 \xC4\xC7\xD3\xC9\xCB\xFB \xB2\xBB\xBF\xC9\xCB\xBC\xD2\xE9 \xCE\xDE\xC1\xBF\xB4\xF3\xCA\xFD") ],
55             ord => [ '', split(' ', "\xCA\xAE \xB0\xD9 \xC7\xA7") ],
56             dig => [ split(' ', "\xC1\xE3 \xD2\xBB \xB6\xFE \xC8\xFD \xCB\xC4 \xCE\xE5 \xC1\xF9 \xC6\xDF \xB0\xCB \xBE\xC5 \xCA\xAE") ],
57             dot => "\xB5\xE3",
58             neg => "\xB8\xBA",
59             },
60             'pinyin' => {
61             mag => [ '', map {$_ } qw(
62             Wan Yi Zhao Jing Gai Zi Rang Gou Jian Zheng Zai Ji
63             HengHeSha AZengZhi NaYouTa BuKeSiYi WuLiangDaShu
64             ) ],
65             ord => [ '', map {$_ } qw(Shi Bai Qian) ],
66             dig => [ qw(Ling Yi Er San Si Wu Liu Qi Ba Jiu Shi) ],
67             dot => ' Dian ',
68             neg => 'Fu ',
69             },
70             );
71             # }}}
72              
73             # {{{ import
74              
75             sub import {
76 1     1   14 my ($class, $charset) = @_;
77 1         5 $class->charset($charset);
78 1         117 return $class->export_to_level(1, $class);
79             }
80              
81             # }}}
82             # {{{ charset
83              
84             sub charset {
85 4     4 1 4542 my ($class, $charset) = @_;
86              
87 1     1   19 no strict 'refs'; ## no critic
  1         3  
  1         1677  
88 4 100       18 return ${"$class\::Charset"} unless defined $charset;
  1         7  
89              
90 3 50 33     27 $charset = 'gb' if $charset =~ /^gb/i or $charset =~ /^euc-cn$/i;
91 3 100       14 $charset = 'big5' if $charset =~ /big5/i;
92 3 50       6 return ${"$class\::Charset"} = lc($charset) if exists ${"$class\::MAP"}{lc($charset)};
  3         15  
  3         25  
93             }
94              
95             # }}}
96             # {{{ map_zho
97              
98             sub map_zho {
99 0     0 1 0 return \%MAP;
100             }
101              
102             # }}}
103             # {{{ new
104              
105             sub new {
106 0     0 1 0 my ($class, $num) = @_;
107 0         0 bless (\$num, $class);
108             }
109              
110             # }}}
111             # {{{ parse
112              
113             sub parse {
114 0     0 1 0 my ($self, $num) = @_;
115 0         0 ${$self} = $num;
  0         0  
116             }
117              
118             # }}}
119             # {{{ get_string
120              
121             sub get_string {
122 0     0 1 0 my ($self) = @_;
123 0         0 return number_to_zh($$self);
124             }
125              
126             # }}}
127             # {{{ number_to_zh
128              
129 0     0 0 0 sub num2zho_cardinal { goto &number_to_zh }
130              
131             sub number_to_zh {
132 12     12 1 229464 my @a = @_;
133 12         60 return __PACKAGE__->_convert($MAP{$Charset}, @a);
134             }
135              
136             # }}}
137             # {{{ convert
138              
139             sub _convert {
140 12     12   32 my ($class, $map, $input) = @_;
141              
142 12 100 66     166 croak 'You should specify a number from interval [0, trillion)'
      66        
143             if !defined $input
144             || $input !~ m{\A[\-\.\d]+\z}xms
145             || $input >= 10 ** 15;
146              
147 8         34 $input =~ s/[^\d\.\-]//;
148              
149 8         13 my @dig = @{$map->{dig}};
  8         51  
150 8         15 my @ord = @{$map->{ord}};
  8         30  
151 8         14 my @mag = @{$map->{mag}};
  8         61  
152 8         16 my $dot = $map->{dot};
153 8         17 my $neg = $map->{neg};
154              
155 8         34 my $out = '';
156 8         9 my $delta;
157 8 50       29 if ($input =~ s/\.(.*)//) {
158 0         0 $delta = $1;
159             }
160              
161 8 50       20 $out = $neg if $input =~ s/^\-//;
162 8         23 $input =~ s/^0+//;
163 8   100     33 $input ||= '0';
164              
165 8         120 my @chunks;
166 8         97 unshift @chunks, $1 while ($input =~ s/(\d{1,4})$//g);
167 8         18 my $mag = $#chunks;
168 8 50       835 my $zero = ($] >= 5.005) ? eval 'qr/$dig[0]$/' : quotemeta($dig[0]) . '$'; ## no critic
169              
170 8         44 foreach my $num (@chunks) {
171 12         23 my $tmp = '';
172              
173 12         32 for (reverse 0..3) {
174 48         133 my $n = int($num / (10 ** $_)) % 10;
175 48 100 100     167 next unless $tmp or $n;
176 20 50 33     87 $tmp .= $dig[$n] unless ($n == 0 and $tmp =~ $zero)
      66        
      33        
      33        
177             or ($_ == 1 and $n == 1 and not $tmp);
178 20 50       53 $tmp .= $ord[$_] if $n;
179             }
180              
181 12 50       56 $tmp =~ s/$zero// unless $tmp eq $dig[0];
182 12 100       32 $tmp .= $mag[$mag] if $tmp;
183 12 50 66     43 $tmp = $dig[0].$tmp if $num < 1000 and $mag != $#chunks
      33        
184             and $out !~ $zero;
185 12         25 $out .= $tmp;
186 12         26 $mag--;
187             }
188              
189 8 50       38 $out =~ s/$zero// unless $out eq $dig[0];
190              
191 8 50       19 if ($delta) {
192 0         0 $delta =~ s/^0\.//;
193 0         0 $out .= $dot;
194 0         0 $out .= $dig[$_] for split(//, $delta);
195             }
196              
197 8   66     106 return $out || $dig[0];
198             }
199              
200             # }}}
201              
202              
203             # {{{ num2zho_ordinal convert number to ordinal text
204              
205             sub num2zho_ordinal {
206 0     0 1   my $number = shift;
207              
208 0 0 0       croak 'You should specify a number from interval [1, 999_999_999_999]'
      0        
      0        
209             if !defined $number
210             || $number !~ m{\A\d+\z}xms
211             || $number < 1
212             || $number > 999_999_999_999;
213              
214             # Chinese ordinals: 第 (dì) + cardinal in traditional characters
215 0           my $cardinal = __PACKAGE__->_convert($MAP{'traditional'}, $number);
216              
217 0           return '第' . $cardinal;
218             }
219              
220             # }}}
221              
222             # {{{ capabilities declare supported features
223              
224             sub capabilities {
225             return {
226 0     0 1   cardinal => 1,
227             ordinal => 1,
228             };
229             }
230              
231             # }}}
232             1;
233             __END__