File Coverage

blib/lib/Lingua/IND/Num2Word.pm
Criterion Covered Total %
statement 90 98 91.8
branch 32 42 76.1
condition 50 84 59.5
subroutine 18 21 85.7
pod 11 12 91.6
total 201 257 78.2


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1) -*-
2              
3             package Lingua::IND::Num2Word;
4             # ABSTRACT: convert number to Indonesian verbage.
5              
6 1     1   127727 use 5.16.0;
  1         4  
7 1     1   8 use utf8;
  1         2  
  1         17  
8 1     1   35 use warnings;
  1         2  
  1         73  
9              
10             # {{{ use block
11              
12 1     1   8 use Carp;
  1         2  
  1         105  
13 1     1   636 use Export::Attrs;
  1         12714  
  1         9  
14              
15             # }}}
16             # {{{ variables declaration
17             our $VERSION = '0.2603300';
18              
19             our $Dec_char = ".";
20             our $Neg_word = "negatif";
21             our $Dec_word = "koma";
22             our $Exp_word = "dikali sepuluh pangkat";
23             our $Zero_word = "nol";
24              
25             our %Digit_words = (
26             0 => $Zero_word,
27             1 => 'satu',
28             2 => 'dua',
29             3 => 'tiga',
30             4 => 'empat',
31             5 => 'lima',
32             6 => 'enam',
33             7 => 'tujuh',
34             8 => 'delapan',
35             9 => 'sembilan'
36             );
37              
38             our %Mult_words = (
39             0 => '',
40             1 => 'ribu',
41             2 => 'juta',
42             3 => 'milyar',
43             4 => 'triliun'
44             );
45              
46             # }}}
47              
48             ### public subs
49             # {{{ nums2words
50              
51 1     1 0 284 sub num2ind_cardinal :Export { goto &nums2words }
  1     0   3  
  1         17  
  0         0  
52              
53             sub nums2words :Export {
54 15     15 1 170518 my @a = @_;
55 15         28 return join_it(n2w1(@a));
56 1     1   358 }
  1         2  
  1         6  
57              
58             # }}}
59             # {{{ nums2words_simple
60              
61             sub nums2words_simple :Export {
62 6     6 1 3095 my @a = @_;
63 6         11 return join_it(n2w5(@a));
64 1     1   297 }
  1         2  
  1         8  
65              
66             # }}}
67              
68             ### private subs
69              
70             # for debugging
71             our $DEBUG = 0;
72 13 50   13 1 38 sub hmm___ { my @a = @_;print "(", (caller 1)[3], ") Hmm, ", @a if $DEBUG; return; }
  13         19  
  13         16  
73             # {{{ n2w1 handle scientific notation
74             sub n2w1 {
75 15   100 15 1 40 my $num = shift // return '';
76              
77 14 100       31 return $Zero_word if $num >= 10 ** 15; # not quadrillion and more
78              
79 13         16 my @words;
80              
81 13 50 33     54 $num =~ /^(.+)[Ee](.+)$/ and
82             @words = (n2w2($1), $Exp_word, n2w2($2)) or
83             @words = n2w2($num);
84              
85 13         39 return @words;
86             }
87              
88             # }}}
89             # {{{ n2w2 handle negative sign and decimal
90              
91             sub n2w2 {
92 13   50 13 1 24 my $num = shift // return '';
93 13         15 my $is_neg;
94 13         22 my @words = ();
95              
96             # negative
97 13 100       24 $num < 0 and $is_neg++;
98 13         55 $num =~ s/^[\s\t]*[+-]*(.*)/$1/;
99              
100             # decimal
101             $num =~ /^(.+)\Q$Dec_char\E(.+)$/o and
102             @words = (n2w3($1), $Dec_word, n2w5($2)) or
103              
104             $num =~ /^\Q$Dec_char\E(.+)$/o and
105 13 50 50     131 @words = ($Digit_words{0}, $Dec_word, n2w5($1)) or
      33        
      33        
      33        
106              
107             $num =~ /^(.+)(?:\Q$Dec_char\E)?$/o and
108             @words = n2w3($1);
109              
110 13 100       20 $is_neg and
111             unshift @words, $Neg_word;
112              
113 13         29 return @words;
114             }
115              
116             # }}}
117             # {{{ n2w3 handle digits before decimal
118              
119             sub n2w3 {
120 13   50 13 1 53 my $num = shift // return '';
121 13         14 my @words = ();
122 13         13 my $order = 0;
123 13         13 my $t;
124              
125 13         38 while($num =~ /^(.*?)([\d\D*]{1,3})$/) {
126 25         34 $num = $1;
127 25         45 ($t = $2) =~ s/\D//g;
128 25   100     49 $t = $t || 0;
129 25 100       77 unshift @words, $Mult_words{$order} if $t > 0;
130 25         32 unshift @words, n2w4($t, $order);
131 25         55 $order++;
132             }
133              
134 13 100       44 @words = ($Zero_word) if not join('',@words)=~/\S/;
135 13         40 hmm___ "for the left part of decimal i get: @words\n";
136 13         37 return @words;
137             }
138              
139             # }}}
140             # {{{ n2w4 handle clusters of thousands
141              
142             sub n2w4 {
143 25   50 25 1 38 my $num = shift // return '';
144 25         26 my $order = shift;
145 25         23 my @words = ();
146              
147 25         28 my $n1 = $num % 10;
148 25         30 my $n2 = ($num % 100 - $n1) / 10;
149 25         34 my $n3 = ($num - $n2*10 - $n1) / 100;
150              
151             ($n3 == 0 && $n2 == 0 && $n1 > 0) && (((
152             $n1 == 1 && $order == 1) && (@words = ("se"))) ||
153 25 100 100     85 (@words = ($Digit_words{$n1}) ));
      66        
      100        
      100        
      100        
154              
155             $n3 == 1 and @words = ("seratus") or
156 25 100 100     57 $n3 > 1 and @words = ($Digit_words{$n3}, "ratus");
      66        
157              
158             $n2 == 1 and (
159             $n1 == 0 and push(@words, "sepuluh") or
160             $n1 == 1 and push(@words, "sebelas") or
161 25 100 33     41 push(@words, $Digit_words{$n1}, "belas")
      0        
      33        
      0        
162             );
163              
164 25 100       34 $n2 > 1 and do {
165 7         14 push @words, $Digit_words{$n2}, "puluh";
166 7 100       14 push @words, $Digit_words{$n1} if $n1 > 0;
167             };
168              
169             ($n3 > 0 && $n2 == 0 && $n1 > 0) &&
170 25 50 100     50 push @words, $Digit_words{$n1} ;
      66        
171              
172 25 100 100     108 ($n3 != 0 || $n2 != 0 || $n1 != 0) &&
      100        
173             return @words;
174             }
175              
176             # }}}
177             # {{{ n2w5 handle digits after decimal
178             sub n2w5 {
179 6   100 6 1 14 my $num = shift // return '';
180              
181 5 100       11 return $Zero_word if $num >= 10 ** 15; # not quadrillion and more
182              
183 4         5 my @words = ();
184 4         5 my $i;
185             my $t;
186              
187 4         9 for( $i=0 ; $i<=length($num)-1 ; $i++ ) {
188 22         26 $t = substr($num, $i, 1);
189             exists $Digit_words{$t} and
190 22 50       47 push @words, $Digit_words{$t};
191             }
192              
193 4 50       20 @words = ($Zero_word) if not join('',@words)=~/\S/;
194 4         9 return @words;
195             }
196              
197             # }}}
198             # {{{ join_it join array of words, also join (se, ratus) -> seratus, etc.
199             sub join_it {
200 21     21 1 39 my @a = @_;
201 21         23 my $words = '';
202 21         22 my $w;
203              
204 21         35 while(defined( $w = shift @a)) {
205 95         102 $words .= $w;
206 95 100 100     268 $words .= ' ' unless not length $w or $w eq 'se' or not @a;
      100        
207             }
208 21         64 return $words;
209             }
210              
211             # }}}
212              
213              
214             # {{{ num2ind_ordinal convert number to ordinal text
215              
216             sub num2ind_ordinal :Export {
217 0     0 1   my $number = shift;
218              
219 0 0 0       croak 'You should specify a number from interval [1, 999_999_999_999]'
      0        
      0        
220             if !defined $number
221             || $number !~ m{\A\d+\z}xms
222             || $number < 1
223             || $number > 999_999_999_999;
224              
225             # Indonesian ordinals: "ke-" + cardinal
226             # Special case: 1st = "pertama"
227 0 0         return 'pertama' if $number == 1;
228              
229 0           my $cardinal = nums2words($number);
230 0           $cardinal =~ s{\A\s+|\s+\z}{}gxms; # trim whitespace
231              
232 0           return 'ke' . $cardinal;
233 1     1   1905 }
  1         2  
  1         6  
234              
235             # }}}
236              
237             # {{{ capabilities declare supported features
238              
239             sub capabilities {
240             return {
241 0     0 1   cardinal => 1,
242             ordinal => 1,
243             };
244             }
245              
246             # }}}
247             1;
248             __END__