File Coverage

blib/lib/Lingua/ZHO/Word2Num.pm
Criterion Covered Total %
statement 25 28 89.2
branch 2 4 50.0
condition 6 9 66.6
subroutine 9 10 90.0
pod 3 3 100.0
total 45 54 83.3


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8; -*-
2              
3             package Lingua::ZHO::Word2Num;
4             # ABSTRACT: Word to number conversion in Chinese
5              
6 1     1   94183 use 5.16.0;
  1         3  
7 1     1   4 use utf8;
  1         1  
  1         10  
8 1     1   19 use warnings;
  1         3  
  1         66  
9              
10             # {{{ use block
11              
12 1     1   603 use Export::Attrs;
  1         9323  
  1         6  
13 1     1   1167 use Parse::RecDescent;
  1         33657  
  1         6  
14              
15             # }}}
16             # {{{ variable declarations
17             our $VERSION = '0.2603300';
18             my $parser = zho_numerals();
19              
20             # }}}
21              
22             # {{{ w2n convert text to number
23              
24             sub w2n :Export {
25 4   100 4 1 334881 my $input = shift // return;
26              
27 3 100 66     32 return 0 if ($input =~ m{\b(nul|ling)\b}xmsi || $input =~ m{\A零\z}xms);
28              
29 2         5 $input .= " "; # Grant space at the end
30              
31 2   100     20 return $parser->numeral($input) || undef;
32 1     1   156 }
  1         5  
  1         7  
33              
34             # }}}
35             # {{{ zho_numerals create parser for numerals
36              
37             sub zho_numerals {
38 1     1 1 4 return Parse::RecDescent->new(q{
39             numeral:
40             numeral: hundred_million { return $item[1]; } # traditional 億-level
41             | trad_million { return $item[1]; } # traditional 萬-level
42             | trad_sub9999 { return $item[1]; } # traditional sub-萬
43             | million { return $item[1]; } # pinyin 萬-level
44             | millenium2 { return $item[1]; }
45             | millenium1 { return $item[1]; }
46             | century { return $item[1]; }
47             | decade { return $item[1]; }
48             | { return undef; }
49              
50             number: 'nul ' { $return = 0; } # try to find a word from 0 to 10
51             | 'Yi ' { $return = 1; }
52             | 'Er ' { $return = 2; }
53             | 'San ' { $return = 3; }
54             | 'Si ' { $return = 4; }
55             | 'Wu ' { $return = 5; }
56             | 'Liu ' { $return = 6; }
57             | 'Qi ' { $return = 7; }
58             | 'Ba ' { $return = 8; }
59             | 'Jiu ' { $return = 9; }
60             | 'Shi ' { $return = 10; }
61             | /零/ { $return = 0; }
62             | /一/ { $return = 1; }
63             | /二/ { $return = 2; }
64             | /三/ { $return = 3; }
65             | /四/ { $return = 4; }
66             | /五/ { $return = 5; }
67             | /六/ { $return = 6; }
68             | /七/ { $return = 7; }
69             | /八/ { $return = 8; }
70             | /九/ { $return = 9; }
71             | /十/ { $return = 10; }
72              
73             tens: 'YiShi ' { $return = 10; } # try to find a word that represents
74             | 'ErShi ' { $return = 20; } # values 20,30,..,90
75             | 'SanShi ' { $return = 30; }
76             | 'SiShi ' { $return = 40; }
77             | 'WuShi ' { $return = 50; }
78             | 'LiuShi ' { $return = 60; }
79             | 'QiShi ' { $return = 70; }
80             | 'BaShi ' { $return = 80; }
81             | 'JiuShi ' { $return = 90; }
82             | /一十/ { $return = 10; }
83             | /二十/ { $return = 20; }
84             | /三十/ { $return = 30; }
85             | /四十/ { $return = 40; }
86             | /五十/ { $return = 50; }
87             | /六十/ { $return = 60; }
88             | /七十/ { $return = 70; }
89             | /八十/ { $return = 80; }
90             | /九十/ { $return = 90; }
91              
92             hundreds: 'YiBai ' { $return = 100; } # try to find a word that represents
93             | 'ErBai ' { $return = 200; } # values 100,200,..,900
94             | 'SanBai ' { $return = 300; }
95             | 'SiBai ' { $return = 400; }
96             | 'WuBai ' { $return = 500; }
97             | 'LiuBai ' { $return = 600; }
98             | 'QiBai ' { $return = 700; }
99             | 'BaBai ' { $return = 800; }
100             | 'JiuBai ' { $return = 900; }
101             | /一百/ { $return = 100; }
102             | /二百/ { $return = 200; }
103             | /三百/ { $return = 300; }
104             | /四百/ { $return = 400; }
105             | /五百/ { $return = 500; }
106             | /六百/ { $return = 600; }
107             | /七百/ { $return = 700; }
108             | /八百/ { $return = 800; }
109             | /九百/ { $return = 900; }
110              
111             thousands: 'YiQian ' { $return = 1000; } # try to find a word that represents
112             | 'ErQian ' { $return = 2000; } # values 1000,2000,..,9000
113             | 'SanQian ' { $return = 3000; }
114             | 'SiQian ' { $return = 4000; }
115             | 'WuQian ' { $return = 5000; }
116             | 'LiuQian ' { $return = 6000; }
117             | 'QiQian ' { $return = 7000; }
118             | 'BaQian ' { $return = 8000; }
119             | 'JiuQian ' { $return = 9000; }
120             | /一千/ { $return = 1000; }
121             | /二千/ { $return = 2000; }
122             | /三千/ { $return = 3000; }
123             | /四千/ { $return = 4000; }
124             | /五千/ { $return = 5000; }
125             | /六千/ { $return = 6000; }
126             | /七千/ { $return = 7000; }
127             | /八千/ { $return = 8000; }
128             | /九千/ { $return = 9000; }
129              
130             tenthousands: 'YiWan ' { $return = 10000; } # try to find a word that represents
131             | 'ErWan ' { $return = 20000; } # values 10000,20000,..,90000
132             | 'SanWan ' { $return = 30000; }
133             | 'SiWan ' { $return = 40000; }
134             | 'WuWan ' { $return = 50000; }
135             | 'LiuWan ' { $return = 60000; }
136             | 'QiWan ' { $return = 70000; }
137             | 'BaWan ' { $return = 80000; }
138             | 'JiuWan ' { $return = 90000; }
139             | /一萬/ { $return = 10000; }
140             | /二萬/ { $return = 20000; }
141             | /三萬/ { $return = 30000; }
142             | /四萬/ { $return = 40000; }
143             | /五萬/ { $return = 50000; }
144             | /六萬/ { $return = 60000; }
145             | /七萬/ { $return = 70000; }
146             | /八萬/ { $return = 80000; }
147             | /九萬/ { $return = 90000; }
148              
149             decade: tens(?) number(?) number(?) # try to find words that represents values
150             { $return = 0; # from 0 to 20
151             for (@item) {
152             $return += $$_[0] if (ref $_ && defined $$_[0]);
153             }
154             }
155              
156             century: hundreds(?) decade(?) # try to find words that represents values
157             { $return = 0; # from 100 to 999
158             for (@item) {
159             $return += $$_[0] if (ref $_ && defined $$_[0]);
160             }
161             }
162              
163             millenium1: thousands(1) century(?) # try to find words that represents values
164             { $return = 0; # from 1.000 to 999.999
165             for (@item) {
166             if (ref $_ && defined $$_[0]) {
167             $return += $$_[0] if (ref $_ && defined $$_[0]);
168             }
169             }
170             }
171              
172             millenium2: tenthousands(1) thousands(?) century(?) decade(?) # try to find words that represents values
173             { $return = 0; # from 1.000 to 999.999
174             for (@item) {
175             if (ref $_ && defined $$_[0]) {
176             $return += $$_[0] if (ref $_ && defined $$_[0]);
177             }
178             }
179             }
180              
181             million: millenium2(?) millenium1(?) century(?) decade(?) # try to find words that represents values
182             wanmark # from 1.000.000 to 999.999.999.999
183             zeromark(?)
184             millenium1(?) century(?) decade(?)
185             { $return = 0;
186             for my $i (1..4) { # sum before 萬
187             $return += $item[$i][0] if ref $item[$i] && defined $item[$i][0];
188             }
189             # item[5] = wanmark
190             $return = ($return > 0) ? $return * 10000 : 10000;
191             # item[6] = zeromark (ignored)
192             for my $i (7..9) { # sum after 萬
193             $return += $item[$i][0] if ref $item[$i] && defined $item[$i][0];
194             }
195             }
196              
197             wanmark: ' Wan ' { $return = "Wan"; }
198             | /萬/ { $return = "Wan"; }
199              
200             yimark: /億/ { $return = "Yi"; }
201              
202             zeromark: /零/ { $return = 0; } # pinyin path placeholder
203              
204             # dedicated traditional character rules — isolated from pinyin path
205             trad_digit: /九/ {9} | /八/ {8} | /七/ {7} | /六/ {6} | /五/ {5}
206             | /四/ {4} | /三/ {3} | /二/ {2} | /一/ {1}
207              
208             trad_tens: /九十/ {90} | /八十/ {80} | /七十/ {70} | /六十/ {60}
209             | /五十/ {50} | /四十/ {40} | /三十/ {30} | /二十/ {20}
210             | /一十/ {10}
211              
212             trad_decade: trad_tens trad_digit { $item[1] + $item[2] }
213             | trad_tens { $item[1] }
214             | trad_digit { $item[1] }
215              
216             trad_hundreds: /九百/ {900} | /八百/ {800} | /七百/ {700} | /六百/ {600}
217             | /五百/ {500} | /四百/ {400} | /三百/ {300} | /二百/ {200}
218             | /一百/ {100}
219              
220             trad_century: trad_hundreds /零/ trad_decade { $item[1] + $item[3] }
221             | trad_hundreds trad_decade { $item[1] + $item[2] }
222             | trad_hundreds { $item[1] }
223              
224             trad_thousands: /九千/ {9000} | /八千/ {8000} | /七千/ {7000} | /六千/ {6000}
225             | /五千/ {5000} | /四千/ {4000} | /三千/ {3000} | /二千/ {2000}
226             | /一千/ {1000}
227              
228             trad_sub9999: trad_thousands /零/ trad_century { $item[1] + $item[3] }
229             | trad_thousands /零/ trad_decade { $item[1] + $item[3] }
230             | trad_thousands trad_century { $item[1] + $item[2] }
231             | trad_thousands { $item[1] }
232             | trad_century
233             | trad_decade
234              
235             # traditional 億 rule — fully self-contained, no pinyin contamination
236             hundred_million:
237             trad_sub9999 /億/ # multiplier × 10^8
238             trad_sub9999 /萬/ # middle group × 10^4
239             /零/ trad_sub9999 # 零 + remainder
240             { $return = $item[1] * 100000000 + $item[3] * 10000 + $item[6]; }
241             | trad_sub9999 /億/
242             trad_sub9999 /萬/
243             trad_sub9999 # remainder without 零
244             { $return = $item[1] * 100000000 + $item[3] * 10000 + $item[5]; }
245             | trad_sub9999 /億/
246             trad_sub9999 /萬/ # no remainder
247             { $return = $item[1] * 100000000 + $item[3] * 10000; }
248             | trad_sub9999 /億/
249             /零/ trad_sub9999 /萬/ # 億 + 零 + N萬 + 零 + remainder
250             /零/ trad_sub9999
251             { $return = $item[1] * 100000000 + $item[4] * 10000 + $item[7]; }
252             | trad_sub9999 /億/
253             /零/ trad_sub9999 /萬/ # 億 + 零 + N萬 + remainder (no 零)
254             trad_sub9999
255             { $return = $item[1] * 100000000 + $item[4] * 10000 + $item[6]; }
256             | trad_sub9999 /億/
257             /零/ trad_sub9999 /萬/ # 億 + 零 + N萬 (no remainder)
258             { $return = $item[1] * 100000000 + $item[4] * 10000; }
259             | trad_sub9999 /億/
260             /零/ trad_sub9999 # 億 + 零 + small remainder (no 萬)
261             { $return = $item[1] * 100000000 + $item[4]; }
262             | trad_sub9999 /億/
263             trad_sub9999 # 億 + remainder (no 萬, no 零)
264             { $return = $item[1] * 100000000 + $item[3]; }
265             | trad_sub9999 /億/ # bare N億
266             { $return = $item[1] * 100000000; }
267              
268             # traditional 萬 rule
269             trad_million:
270             trad_sub9999 /萬/
271             /零/ trad_sub9999 # 萬 + 零 + remainder
272             { $return = $item[1] * 10000 + $item[4]; }
273             | trad_sub9999 /萬/
274             trad_sub9999 # 萬 + remainder
275             { $return = $item[1] * 10000 + $item[3]; }
276             | trad_sub9999 /萬/ # bare N萬
277             { $return = $item[1] * 10000; }
278             });
279             }
280              
281             # }}}
282             # {{{ ordinal2cardinal convert ordinal text to cardinal text
283              
284             sub ordinal2cardinal :Export {
285 0   0 0 1   my $input = shift // return;
286              
287             # Chinese ordinals: prefix 第 (dì) to cardinal.
288             # Strip 第 prefix; return cardinal remainder.
289 0 0         $input =~ s{\A第}{}xms and return $input;
290              
291 0           return; # not an ordinal
292 1     1   558 }
  1         2  
  1         4  
293              
294             # }}}
295              
296             1;
297              
298             __END__