File Coverage

blib/lib/Lingua/Num2Word.pm
Criterion Covered Total %
statement 50 112 44.6
branch 5 54 9.2
condition 5 28 17.8
subroutine 17 21 80.9
pod 8 8 100.0
total 85 223 38.1


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1) -*-
2             #
3             # Copyright (c) PetaMem, s.r.o. 2002-present
4             #
5             package Lingua::Num2Word;
6             # ABSTRACT: Multi-language number to word conversion wrapper
7              
8 1     1   119385 use 5.16.0;
  1         4  
9 1     1   7 use utf8;
  1         2  
  1         22  
10 1     1   36 use warnings;
  1         2  
  1         94  
11              
12             # {{{ use block
13              
14 1     1   7 use Carp;
  1         2  
  1         94  
15 1     1   635 use Encode;
  1         14401  
  1         101  
16 1     1   1962 use Export::Attrs;
  1         7937  
  1         5  
17              
18             # }}}
19             # {{{ var block
20             our $VERSION = '0.2603300';
21             our %known;
22              
23             # }}}
24              
25             # {{{ templates for functional and object interface
26              
27             my $template_func = q{ use __PACKAGE_WITH_VERSION__ ();
28             $result = __PACKAGE__::__FUNCTION__($number);
29             };
30              
31             my $template_obj = q{ use __PACKAGE_WITH_VERSION__ ();
32             my $tmp_obj = new __PACKAGE__;
33             $result = $tmp_obj->__FUNCTION__($number);
34             };
35              
36             # ISO 639-1 to 639-3 mapping for supported languages
37             # {{{ ISO 639-1 to 639-3 mapping
38             my %iso1_to_3 = (
39             af => 'afr', ar => 'ara', az => 'aze', bg => 'bul', ca => 'cat',
40             cs => 'ces', da => 'dan', de => 'deu', el => 'ell',
41             en => 'eng', es => 'spa', et => 'est', eu => 'eus',
42             fa => 'fas', fi => 'fin', fr => 'fra', he => 'heb',
43             hi => 'hin', hr => 'hrv', hu => 'hun', hy => 'hye',
44             id => 'ind', is => 'isl', it => 'ita', ja => 'jpn',
45             kk => 'kaz', ko => 'kor', ky => 'kir', la => 'lat',
46             lt => 'lit', lv => 'lav', mn => 'mon', nl => 'nld',
47             'no' => 'nor',
48             pl => 'pol', pt => 'por', ro => 'ron', ru => 'rus',
49             sk => 'slk', sv => 'swe', sw => 'swa', th => 'tha',
50             be => 'bel', cy => 'cym', ga => 'gle', gl => 'glg',
51             lb => 'ltz', mk => 'mkd', mt => 'mlt', oc => 'oci',
52             sc => 'srd',
53             sl => 'slv', so => 'som', sq => 'sqi', sr => 'srp',
54             tr => 'tur', ug => 'uig', uk => 'ukr', vi => 'vie',
55             yi => 'yid', zh => 'zho',
56             );
57             # }}}
58              
59             # {{{ %known — auto-discovered from lib/Lingua/*/Num2Word.pm
60              
61             # Override table for legacy modules with non-standard API
62             # Override table: only for modules with non-default limits or legacy function names.
63             # All modules now live in Num2Word.pm with num2XXX_cardinal as canonical function.
64             my %n2w_override = (
65             afr => { limit_hi => 99_999_999_999 },
66             eng => { limit_lo => 1, limit_hi => 999_999_999_999_999 },
67             eus => { limit_hi => 999_999_999_999 },
68             fra => { limit_hi => 999_999_999_999_999 },
69             ind => { limit_hi => 999_999_999_999_999 },
70             ita => { limit_hi => 999_999_999_999 },
71             jpn => { limit_lo => 1, limit_hi => 999_999_999_999_999,
72             code => q{ use __PACKAGE_WITH_VERSION__ ();
73             my @words = __PACKAGE__::to_string($number);
74             $result = join ' ', @words;
75             },
76             },
77             nor => { function => 'num2no_cardinal', code => $template_obj },
78             pol => { limit_hi => 9_999_999_999_999 },
79             por => { limit_hi => 999_999_999_999_999 },
80             rus => { limit_hi => 999_999_999_999_999 },
81             spa => { limit_hi => 999_999_999_999_999 },
82             swe => { function => 'num2sv_cardinal' },
83             zho => { limit_lo => 1, limit_hi => 999_999_999_999_999,
84             code => q{ use Lingua::ZHO::Num2Word qw(traditional);
85             $result = Lingua::ZHO::Num2Word::number_to_zh($number);
86             },
87             },
88             );
89              
90             # auto-discover: scan for Lingua::*::Num2Word modules
91             {
92             my $lingua_dir;
93             for my $inc (@INC) {
94             my $try = "$inc/Lingua";
95             if (-d $try) { $lingua_dir = $try; last; }
96             }
97              
98             if ($lingua_dir) {
99             for my $dir (glob "$lingua_dir/*/") {
100             my ($lang) = $dir =~ m{/([A-Z]{3})/\z};
101             next unless $lang;
102             $lang = lc $lang;
103              
104             # standard pattern: Num2Word.pm with num2XXX_cardinal function
105             if (-e "$dir/Num2Word.pm" || exists $n2w_override{$lang}) {
106             my $ov = $n2w_override{$lang} // {};
107             $known{$lang} = {
108             package => $ov->{package} // 'Num2Word',
109             version => $ov->{version} // '',
110             limit_lo => $ov->{limit_lo} // 0,
111             limit_hi => $ov->{limit_hi} // 999_999_999,
112             function => $ov->{function} // "num2${lang}_cardinal",
113             code => $ov->{code} // $template_func,
114             };
115             }
116             }
117             }
118             }
119              
120             # }}}
121             # {{{ default capabilities
122              
123             my %default_capabilities = (
124             cardinal => 1,
125             ordinal => 0,
126             negative => 0,
127             decimal => 0,
128             currency => 0,
129             );
130              
131             # }}}
132             # {{{ capabilities query what a language module can do
133              
134             sub capabilities :Export {
135 0 0   0 1 0 my $self = ref($_[0]) ? shift : undef;
136 0   0     0 my $lang = shift // return;
137              
138 0         0 $lang = lc $lang;
139 0 0       0 $lang = $iso1_to_3{$lang} if exists $iso1_to_3{$lang};
140              
141 0 0       0 return if !exists $known{$lang};
142              
143             # try to load the module's capabilities() if it has one
144 0         0 my $pkg = 'Lingua::' . uc($lang) . '::' . $known{$lang}{package};
145 0         0 my $caps;
146 0         0 eval {
147 0         0 (my $file = $pkg) =~ s{::}{/}g;
148 0         0 require "$file.pm";
149 0 0       0 if ($pkg->can('capabilities')) {
150 0         0 $caps = $pkg->capabilities();
151             }
152             };
153              
154             # merge with defaults — module caps override defaults
155 0         0 my %result = %default_capabilities;
156 0 0 0     0 if ($caps && ref $caps eq 'HASH') {
157 0         0 $result{$_} = $caps->{$_} for keys %{$caps};
  0         0  
158             }
159              
160             # add range from %known
161 0   0     0 $result{range} = [$known{$lang}{limit_lo} // 0, $known{$lang}{limit_hi} // 999_999_999];
      0        
162              
163 0         0 return \%result;
164 1     1   620 }
  1         2  
  1         4  
165              
166             # }}}
167             # {{{ has_capability check if a language supports a feature
168              
169             sub has_capability :Export {
170 0 0   0 1 0 my $self = ref($_[0]) ? shift : undef;
171 0   0     0 my $lang = shift // return 0;
172 0   0     0 my $feature = shift // return 0;
173              
174 0         0 my $caps = capabilities($lang);
175 0 0       0 return 0 unless $caps;
176 0 0       0 return $caps->{$feature} ? 1 : 0;
177 1     1   269 }
  1         3  
  1         4  
178              
179             # }}}
180             # {{{ ordinal convert number to ordinal text
181              
182             sub ordinal :Export {
183 0 0   0 1 0 my $self = ref($_[0]) ? shift : Lingua::Num2Word->new();
184 0         0 my $result = '';
185 0   0     0 my $lang = shift // return $result;
186 0   0     0 my $number = shift // return $result;
187              
188 0         0 $lang = lc $lang;
189 0 0       0 $lang = $iso1_to_3{$lang} if exists $iso1_to_3{$lang};
190              
191 0 0       0 return $result if !exists $known{$lang};
192 0 0       0 return $result if !has_capability($lang, 'ordinal');
193              
194 0         0 my $pkg = 'Lingua::' . uc($lang) . '::' . $known{$lang}{package};
195             # derive ordinal function name from cardinal (handles legacy names like num2sv_)
196 0   0     0 my $cardinal_func = $known{$lang}{function} // "num2${lang}_cardinal";
197 0         0 my $func;
198 0 0       0 if ($cardinal_func =~ /_cardinal$/) {
199 0         0 ($func = $cardinal_func) =~ s/_cardinal$/_ordinal/;
200             }
201             else {
202 0         0 $func = "num2${lang}_ordinal"; # fallback for OO/legacy modules
203             }
204              
205 0         0 eval "use $pkg (); \$result = ${pkg}::${func}(\$number);"; ## no critic
206 0 0       0 carp $@ if $@;
207              
208 0         0 return $result;
209 1     1   384 }
  1         2  
  1         2  
210              
211             # }}}
212             # {{{ new constructor
213              
214             sub new {
215 5     5 1 146404 return bless {}, shift;
216             }
217              
218             # }}}
219             # {{{ known_langs list of currently supported languages
220              
221             sub known_langs :Export {
222 0 0   0 1 0 return wantarray ? sort keys %known : [ sort keys %known ];
223 1     1   188 }
  1         1  
  1         3  
224              
225             # }}}
226             # {{{ get_interval get minimal and maximal supported number
227              
228             # Return:
229             # undef for unsupported language
230             # list or list reference (depending to calling context) with
231             # minimal and maximal supported number
232             #
233             sub get_interval :Export {
234 2 50   2 1 564 my $self = ref($_[0]) ? shift : Lingua::Num2Word->new();
235 2   100     7 my $lang = shift // return;
236 1         2 $lang = lc $lang;
237 1 50       5 $lang = $iso1_to_3{$lang} if exists $iso1_to_3{$lang};
238              
239 1 50       7 return if (!defined $known{$lang});
240              
241 0         0 my @limits = ($known{$lang}{limit_lo}, $known{$lang}{limit_hi});
242              
243 0 0       0 return wantarray ? @limits : \@limits;
244 1     1   252 }
  1         2  
  1         3  
245              
246             # }}}
247             # {{{ cardinal convert number to text
248              
249             sub cardinal :Export {
250 2 50   2 1 804 my $self = ref($_[0]) ? shift : Lingua::Num2Word->new();
251 2         3 my $result = '';
252 2   50     7 my $lang = shift // return $result;
253 0   0     0 my $number = shift // return $result;
254              
255 0         0 $lang = lc $lang;
256 0 0       0 $lang = $iso1_to_3{$lang} if exists $iso1_to_3{$lang};
257              
258 0 0       0 return $result if (!defined $known{$lang});
259              
260 0 0       0 if (defined $known{$lang}{lang}) {
261 0         0 eval $self->preprocess_code($known{$lang}{lang}); ## no critic
262 0 0       0 carp $@ if ($@);
263             }
264             else {
265 0         0 eval $self->preprocess_code($lang); ## no critic
266 0 0       0 carp $@ if ($@);
267             }
268              
269 0         0 return $result;
270 1     1   292 }
  1         1  
  1         3  
271              
272             # }}}
273             # {{{ preprocess_code prepare code for evaluation
274              
275             sub preprocess_code :Export {
276 2     2 1 482 my $self = shift;
277 2   100     6 my $lang = shift // return;
278              
279 1 50       4 return if !exists $known{$lang};
280              
281 0           my $result = $known{$lang}{code};
282 0           my $pkg_name = 'Lingua::' . uc($lang) . '::' . $known{$lang}{package};
283 0 0         my $pkg_name_with_version = $known{$lang}{version} ne ''
284             ? "$pkg_name $known{$lang}{version}"
285             : $pkg_name
286             ;
287              
288 0           my $function = $known{$lang}{function};
289              
290 0           $result =~ s/__PACKAGE_WITH_VERSION__/$pkg_name_with_version/g;
291 0           $result =~ s/__PACKAGE__/$pkg_name/g;
292 0           $result =~ s/__FUNCTION__/$function/g;
293              
294 0           return $result;
295 1     1   328 }
  1         2  
  1         3  
296             # }}}
297              
298             1;
299             __END__