File Coverage

blib/lib/Locale/CLDR/NumberFormatter.pm
Criterion Covered Total %
statement 256 358 71.5
branch 105 182 57.6
condition 44 71 61.9
subroutine 26 26 100.0
pod 0 6 0.0
total 431 643 67.0


line stmt bran cond sub pod time code
1             package Locale::CLDR::NumberFormatter;
2              
3 20     20   8201 use version;
  20         26  
  20         88  
4              
5             our $VERSION = version->declare('v0.26.10');
6              
7              
8 20     20   1556 use v5.10;
  20         46  
  20         545  
9 20     20   63 use mro 'c3';
  20         26  
  20         95  
10 20     20   519 use utf8;
  20         22  
  20         83  
11 20     20   443 use if $^V ge v5.12.0, feature => 'unicode_strings';
  20         24  
  20         190  
12              
13 20     20   1476 use Moose::Role;
  20         24  
  20         95  
14              
15             sub format_number {
16 749     749 0 1625 my ($self, $number, $format, $currency, $for_cash) = @_;
17            
18             # Check if the locales numbering system is algorithmic. If so ignore the format
19 749         1526 my $numbering_system = $self->default_numbering_system();
20 749 50       18649 if ($self->numbering_system->{$numbering_system}{type} eq 'algorithmic') {
21 0         0 $format = $self->numbering_system->{$numbering_system}{data};
22 0         0 return $self->_algorithmic_number_format($number, $format);
23             }
24            
25 749   100     1928 $format //= '0';
26            
27 749         1188 return $self->_format_number($number, $format, $currency, $for_cash);
28             }
29              
30             sub _format_number {
31 751     751   785 my ($self, $number, $format, $currency, $for_cash) = @_;
32            
33             # First check to see if this is an algorithmic format
34 751         1200 my @valid_formats = $self->_get_valid_algorithmic_formats();
35            
36 751 100       1075 if (grep {$_ eq $format} @valid_formats) {
  13518         11584  
37 5         9 return $self->_algorithmic_number_format($number, $format);
38             }
39            
40             # Some of these algorithmic formats are in locale/type/name format
41 746 50       1497 if (my ($locale_id, $type, $format) = $format =~ m(^(.*?)/(.*?)/(.*?)$)) {
42 0         0 my $locale = Locale::CLDR->new($locale_id);
43 0         0 return $locale->format_number($number, $format);
44             }
45            
46 746         628 my $currency_data;
47            
48             # Check if we need a currency and have not been given one.
49             # In that case we look up the default currency for the locale
50 746 100       1741 if ($format =~ tr/¤/¤/) {
51            
52 1   50     5 $for_cash //=0;
53            
54 1 50       5 $currency = $self->default_currency()
55             if ! defined $currency;
56            
57 1         4 $currency_data = $self->_get_currency_data($currency);
58            
59 1         4 $currency = $self->currency_symbol($currency);
60             }
61            
62 746         1554 $format = $self->parse_number_format($format, $currency, $currency_data, $for_cash);
63            
64 746         1498 $number = $self->get_formatted_number($number, $format, $currency_data, $for_cash);
65            
66 746         2677 return $number;
67             }
68              
69             sub add_currency_symbol {
70 1     1 0 2 my ($self, $format, $symbol) = @_;
71            
72            
73 1         8 $format =~ s/¤/'$symbol'/;
74            
75 1         3 return $format;
76             }
77              
78             sub _get_currency_data {
79 1     1   1 my ($self, $currency) = @_;
80            
81 1         5 my $currency_data = $self->currency_fractions($currency);
82            
83 1         1 return $currency_data;
84             }
85              
86             sub _get_currency_rounding {
87              
88 1     1   1 my ($self, $currency_data, $for_cash) = @_;
89            
90 1 50       2 my $rounder = $for_cash ? 'cashrounding' : 'rounding' ;
91            
92 1         3 return $currency_data->{$rounder};
93             }
94              
95             sub _get_currency_digits {
96 1     1   2 my ($self, $currency_data, $for_cash) = @_;
97            
98 1 50       8 my $digits = $for_cash ? 'cashdigits' : 'digits' ;
99            
100 1         4 return $currency_data->{$digits};
101             }
102              
103             sub parse_number_format {
104 748     748 0 755 my ($self, $format, $currency, $currency_data, $for_cash) = @_;
105              
106 20     20   81374 use feature 'state';
  20         34  
  20         7785  
107            
108 748         529 state %cache;
109            
110 748 100       1991 return $cache{$format} if exists $cache{$format};
111            
112 10 100       20 $format = $self->add_currency_symbol($format, $currency)
113             if defined $currency;
114            
115 10         61 my ($positive, $negative) = $format =~ /^((?:(?:'[^']*')*+[^';]+)+) (?:;(.+))?$/x;
116            
117 10         15 my $type = 'positive';
118 10         17 foreach my $to_parse ( $positive, $negative ) {
119 20 100       35 last unless defined $to_parse;
120 11         11 my ($prefix, $suffix);
121 11 100       43 if (($prefix) = $to_parse =~ /^((?:[^0-9@#.,E+'*-] | (?:'[^']*')++)+)/x) {
122 1         3 $to_parse =~ s/^((?:[^0-9@#.,E+'*-] | (?:'[^']*')++)+)//x;
123             }
124 11 100       54 if( ($suffix) = $to_parse =~ /((?:[^0-9@#.,E+'-] | (?:'[^']*')++)+)$/x) {
125 4         19 $to_parse =~ s/((?:[^0-9@#.,E+'-] | (?:'[^']*')++)+)$//x;
126             }
127            
128             # Fix escaped '
129 11         15 foreach my $str ($prefix, $suffix) {
130 22   100     52 $str //= '';
131 22         29 $str =~ s/'((?:'')++ | [^']+)'/$1/gx;
132 22         24 $str =~ s/''/'/g;
133             }
134            
135             # Look for padding
136 11         10 my ($pad_character, $pad_location);
137 11 50       75 if (($pad_character) = $prefix =~ /^\*(\p{Any})/ ) {
    50          
    50          
    100          
138 0         0 $prefix =~ s/^\*(\p{Any})//;
139 0         0 $pad_location = 'before prefix';
140             }
141             elsif ( ($pad_character) = $prefix =~ /\*(\p{Any})$/ ) {
142 0         0 $prefix =~ s/\*(\p{Any})$//;
143 0         0 $pad_location = 'after prefix';
144             }
145             elsif (($pad_character) = $suffix =~ /^\*(\p{Any})/ ) {
146 0         0 $suffix =~ s/^\*(\p{Any})//;
147 0         0 $pad_location = 'before suffix';
148             }
149             elsif (($pad_character) = $suffix =~ /\*(\p{Any})$/ ) {
150 1         2 $suffix =~ s/\*(\p{Any})$//;
151 1         2 $pad_location = 'after suffix';
152             }
153            
154 11 100       27 my $pad_length = defined $pad_character
155             ? length($prefix) + length($to_parse) + length($suffix) + 2
156             : 0;
157            
158             # Check for a multiplier
159 11         10 my $multiplier = 1;
160 11 100 33     43 $multiplier = 100 if $prefix =~ tr/%/%/ || $suffix =~ tr/%/%/;
161 11 100 33     55 $multiplier = 1000 if $prefix =~ tr/‰/‰/ || $suffix =~ tr/‰/‰/;
162            
163 11         21 my $rounding = $to_parse =~ /([1-9][0-9]*(?:\.[0-9]+)?)/;
164 11   50     33 $rounding ||= 0;
165            
166 11 100       23 $rounding = $self->_get_currency_rounding($currency_data, $for_cash)
167             if defined $currency;
168            
169 11         32 my ($integer, $decimal) = split /\./, $to_parse;
170            
171 11         9 my ($minimum_significant_digits, $maximum_significant_digits, $minimum_digits);
172 11 50       26 if (my ($digits) = $to_parse =~ /(\@+)/) {
173 0         0 $minimum_significant_digits = length $digits;
174 0         0 ($digits ) = $to_parse =~ /\@(#+)/;
175 0   0     0 $maximum_significant_digits = $minimum_significant_digits + length ($digits // '');
176             }
177             else {
178 11         18 $minimum_digits = $integer =~ tr/0-9/0-9/;
179             }
180            
181             # Check for exponent
182 11         9 my $exponent_digits = 0;
183 11         10 my $need_plus = 0;
184 11         9 my $exponent;
185             my $major_group;
186 0         0 my $minor_group;
187 11 50       27 if ($to_parse =~ tr/E/E/) {
188 0         0 ($need_plus, $exponent) = $to_parse =~ m/E(\+?)([0-9]+)/;
189 0         0 $exponent_digits = length $exponent;
190             }
191             else {
192             # Check for grouping
193 11         20 my ($grouping) = split /\./, $to_parse;
194 11         24 my @groups = split /,/, $grouping;
195 11         12 shift @groups;
196 11         14 ($major_group, $minor_group) = map {length} @groups;
  9         16  
197 11   100     36 $minor_group //= $major_group;
198             }
199            
200 11   50     153 $cache{$format}{$type} = {
      50        
      100        
      50        
201             prefix => $prefix // '',
202             suffix => $suffix // '',
203             pad_character => $pad_character,
204             pad_location => $pad_location // 'none',
205             pad_length => $pad_length,
206             multiplier => $multiplier,
207             rounding => $rounding,
208             minimum_significant_digits => $minimum_significant_digits,
209             maximum_significant_digits => $maximum_significant_digits,
210             minimum_digits => $minimum_digits // 0,
211             exponent_digits => $exponent_digits,
212             exponent_needs_plus => $need_plus,
213             major_group => $major_group,
214             minor_group => $minor_group,
215             };
216            
217 11         21 $type = 'negative';
218             }
219            
220 10   66     41 $cache{$format}{negative} //= $cache{$format}{positive};
221 10         23 return $cache{$format};
222             }
223              
224             # Rounding function
225             sub round {
226 1     1 0 2 my ($self, $number, $increment, $decimal_digits) = @_;
227              
228 1 50       4 if ($increment ) {
229 0         0 $number /= $increment;
230 0         0 $number = int ($number + .5 );
231 0         0 $number *= $increment;
232             }
233            
234 1 50       3 if ( $decimal_digits ) {
235 1         5 $number *= 10 ** $decimal_digits;
236 1         1 $number = int $number;
237 1         4 $number /= 10 ** $decimal_digits;
238            
239 1         14 my ($decimal) = $number =~ /(\..*)/;
240 1   50     12 $decimal //= '.'; # No fraction so add a decimal point
241            
242 1         7 $number = int ($number) . $decimal . ('0' x ( $decimal_digits - length( $decimal ) +1 ));
243             }
244             else {
245             # No decimal digits wanted
246 0         0 $number = int $number;
247             }
248            
249 1         3 return $number;
250             }
251              
252             sub get_formatted_number {
253 746     746 0 705 my ($self, $number, $format, $currency_data, $for_cash) = @_;
254            
255 746         1174 my @digits = $self->get_digits;
256 746         1986 my @number_symbols_bundles = reverse $self->_find_bundle('number_symbols');
257 746         733 my %symbols;
258 746         929 foreach my $bundle (@number_symbols_bundles) {
259 1492         42313 my $current_symbols = $bundle->number_symbols;
260 1492         5262 foreach my $type (keys %$current_symbols) {
261 32078         19246 foreach my $symbol (keys %{$current_symbols->{$type}}) {
  32078         37693  
262 64156         93635 $symbols{$type}{$symbol} = $current_symbols->{$type}{$symbol};
263             }
264             }
265             }
266            
267 746         2134 my $symbols_type = $self->default_numbering_system;
268            
269 746 50       1565 $symbols_type = $symbols{$symbols_type}{alias} if exists $symbols{$symbols_type}{alias};
270            
271 746 50       1301 my $type = $number < 0 ? 'negative' : 'positive';
272            
273 746         1030 $number *= $format->{$type}{multiplier};
274            
275 746 100 66     2714 if ($format->{rounding} || defined $for_cash) {
276 1         2 my $decimal_digits = 0;
277            
278 1 50       4 if (defined $for_cash) {
279 1         6 $decimal_digits = $self->_get_currency_digits($currency_data, $for_cash)
280             }
281            
282 1         6 $number = $self->round($number, $format->{$type}{rounding}, $decimal_digits);
283             }
284            
285 746         1275 my $pad_zero = $format->{$type}{minimum_digits} - length "$number";
286 746 100       1087 if ($pad_zero > 0) {
287 4         7 $number = ('0' x $pad_zero) . $number;
288             }
289            
290             # Handle grouping
291 746         1423 my ($integer, $decimal) = split /\./, $number;
292              
293 746         1895 my $minimum_grouping_digits = $self->_find_bundle('minimum_grouping_digits');
294 746 50       20995 $minimum_grouping_digits = $minimum_grouping_digits
295             ? $minimum_grouping_digits->minimum_grouping_digits()
296             : 0;
297            
298 746         1463 my ($separator, $decimal_point) = ($symbols{$symbols_type}{group}, $symbols{$symbols_type}{decimal});
299 746 50 33     2885 if (($minimum_grouping_digits && length $integer >= $minimum_grouping_digits) || ! $minimum_grouping_digits) {
      33        
300 746         1144 my ($minor_group, $major_group) = ($format->{$type}{minor_group}, $format->{$type}{major_group});
301            
302 746 100 66     1365 if (defined $minor_group && $separator) {
303             # Fast commify using unpack
304 7         12 my $pattern = "(A$minor_group)(A$major_group)*";
305 7         35 $number = reverse join $separator, grep {length} unpack $pattern, reverse $integer;
  18         27  
306             }
307             }
308             else {
309 0         0 $number = $integer;
310             }
311            
312 746 100       1012 $number.= "$decimal_point$decimal" if defined $decimal;
313            
314             # Fix digits
315 746         2419 $number =~ s/([0-9])/$digits[$1]/eg;
  793         1989  
316            
317 746         1391 my ($prefix, $suffix) = ( $format->{$type}{prefix}, $format->{$type}{suffix});
318            
319             # This needs fixing for escaped symbols
320 746         833 foreach my $string ($prefix, $suffix) {
321 1492         973 $string =~ s/%/$symbols{$symbols_type}{percentSign}/;
322 1492         1506 $string =~ s/‰/$symbols{$symbols_type}{perMille}/;
323             }
324            
325 746         1037 $number = $prefix . $number . $suffix;
326            
327 746         10191 return $number;
328             }
329              
330             # Get the digits for the locale. Assumes a numeric numbering system
331             sub get_digits {
332 747     747 0 627 my $self = shift;
333            
334 747         1529 my $numbering_system = $self->default_numbering_system();
335            
336 747 50       18707 $numbering_system = 'latn' unless $self->numbering_system->{$numbering_system}{type} eq 'numeric'; # Fall back to latn if the numbering system is not numeric
337            
338 747         18335 my $digits = $self->numbering_system->{$numbering_system}{data};
339            
340 747         2120 return @$digits;
341             }
342              
343             # RBNF
344             # Note that there are a couple of assumptions with the way
345             # I handle Rule Base Number Formats.
346             # 1) The number is treated as a string for as long as possible
347             # This allows things like -0.0 to be correctly formatted
348             # 2) There is no fall back. All the rule sets are self contained
349             # in a bundle. Fall back is used to find a bundle but once a
350             # bundle is found no further processing of the bundle chain
351             # is done. This was found by trial and error when attempting
352             # to process -0.0 correctly into English.
353             sub _get_valid_algorithmic_formats {
354 751     751   658 my $self = shift;
355            
356 751         1531 my @formats = map { @{$_->valid_algorithmic_formats()} } $self->_find_bundle('valid_algorithmic_formats');
  1502         1093  
  1502         41278  
357            
358 751         1100 my %seen;
359 751         722 return sort grep { ! $seen{$_}++ } @formats;
  17273         25169  
360             }
361              
362             # Main entry point to RBNF
363             sub _algorithmic_number_format {
364 8     8   14 my ($self, $number, $format_name, $type) = @_;
365            
366 8         14 my $format_data = $self->_get_algorithmic_number_format_data_by_name($format_name, $type);
367            
368 8 50       12 return $number unless $format_data;
369            
370 8         24 return $self->_process_algorithmic_number_data($number, $format_data);
371             }
372              
373             sub _get_algorithmic_number_format_data_by_name {
374 8     8   8 my ($self, $format_name, $type) = @_;
375            
376             # Some of these algorithmic formats are in locale/type/name format
377 8 50       18 if (my ($locale_id, undef, $format) = $format_name =~ m(^(.*?)/(.*?)/(.*?)$)) {
378 0         0 my $locale = Locale::CLDR->new($locale_id);
379 0 0       0 return $locale->_get_algorithmic_number_format_data_by_name($format, $type)
380             if $locale;
381              
382 0         0 return undef;
383             }
384            
385 8   100     17 $type //= 'public';
386            
387 8         9 my %data = ();
388            
389 8         17 my @data_bundles = $self->_find_bundle('algorithmic_number_format_data');
390 8         13 foreach my $data_bundle (@data_bundles) {
391 10         240 my $data = $data_bundle->algorithmic_number_format_data();
392 10 100       20 next unless $data->{$format_name};
393 8 50       15 next unless $data->{$format_name}{$type};
394            
395 8         4 foreach my $rule (keys %{$data->{$format_name}{$type}}) {
  8         39  
396 186         197 $data{$rule} = $data->{$format_name}{$type}{$rule};
397             }
398            
399 8         13 last;
400             }
401            
402 8 50       20 return keys %data ? \%data : undef;
403             }
404              
405             sub _get_plural_form {
406 1     1   2 my ($self, $plural, $from) = @_;
407            
408 1         9 my ($result) = $from =~ /$plural\{(.+?)\}/;
409 1 50       3 ($result) = $from =~ /other\{(.+?)\}/ unless defined $result;
410            
411 1         2 return $result;
412             }
413              
414             sub _process_algorithmic_number_data {
415 14     14   14 my ($self, $number, $format_data, $plural, $in_fraction_rule_set) = @_;
416            
417 14   100     29 $in_fraction_rule_set //= 0;
418            
419 14         17 my $format = $self->_get_algorithmic_number_format($number, $format_data);
420            
421 14         754 my $format_rule = $format->{rule};
422 14 100 66     57 if (! $plural && $format_rule =~ /(cardinal|ordinal)/) {
423 3         5 my $type = $1;
424 3         9 $plural = $self->plural($number, $type);
425 3         8 $plural = [$type, $plural];
426             }
427            
428             # Sort out plural forms
429 14 100       19 if ($plural) {
430 3         40 $format_rule =~ s/\$\($plural->[0],(.+)\)\$/$self->_get_plural_form($plural->[1],$1)/eg;
  1         4  
431             }
432            
433 14         14 my $divisor = $format->{divisor};
434 14   100     24 my $base_value = $format->{base_value} // '';
435            
436             # Negative numbers
437 14 100       31 if ($number =~ /^-/) {
    100          
438 2         2 my $positive_number = $number;
439 2         5 $positive_number =~ s/^-//;
440            
441 2 100       18 if ($format_rule =~ /→→/) {
    50          
    50          
    50          
    0          
442 1         4 $format_rule =~ s/→→/$self->_process_algorithmic_number_data($positive_number, $format_data, $plural)/e;
  1         2  
443             }
444             elsif((my $rule_name) = $format_rule =~ /→(.+)→/) {
445 0         0 my $type = 'public';
446 0 0       0 if ($rule_name =~ s/^%%/%/) {
447 0         0 $type = 'private';
448             }
449 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
450 0 0       0 if($format_data) {
451             # was a valid name
452 0         0 $format_rule =~ s/→(.+)→/$self->_process_algorithmic_number_data($positive_number, $format_data, $plural)/e;
  0         0  
453             }
454             else {
455             # Assume a format
456 0         0 $format_rule =~ s/→(.+)→/$self->_format_number($positive_number, $1)/e;
  0         0  
457             }
458             }
459             elsif($format_rule =~ /=%%.*=/) {
460 0         0 $format_rule =~ s/=%%(.*?)=/$self->_algorithmic_number_format($number, $1, 'private')/eg;
  0         0  
461             }
462             elsif($format_rule =~ /=%.*=/) {
463 1         4 $format_rule =~ s/=%(.*?)=/$self->_algorithmic_number_format($number, $1, 'public')/eg;
  1         3  
464             }
465             elsif($format_rule =~ /=.*=/) {
466 0         0 $format_rule =~ s/=(.*?)=/$self->_format_number($number, $1)/eg;
  0         0  
467             }
468             }
469             # Fractions
470             elsif( $number =~ /\./ ) {
471 1         2 my $in_fraction_rule_set = 1;
472 1         4 my ($integer, $fraction) = $number =~ /^([^.]*)\.(.*)$/;
473            
474 1 50 33     6 if ($number >= 0 && $number < 1) {
475 1         4 $format_rule =~ s/\[.*\]//;
476             }
477             else {
478 0         0 $format_rule =~ s/[\[\]]//g;
479             }
480            
481 1 50       3 if ($format_rule =~ /→→/) {
    0          
482 1         3 $format_rule =~ s/→→/$self->_process_algorithmic_number_data_fractions($fraction, $format_data, $plural)/e;
  1         3  
483             }
484             elsif((my $rule_name) = $format_rule =~ /→(.*)→/) {
485 0         0 my $type = 'public';
486 0 0       0 if ($rule_name =~ s/^%%/%/) {
487 0         0 $type = 'private';
488             }
489 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
490 0 0       0 if ($format_data) {
491 0         0 $format_rule =~ s/→(.*)→/$self->_process_algorithmic_number_data_fractions($fraction, $format_data, $plural)/e;
  0         0  
492             }
493             else {
494 0         0 $format_rule =~ s/→(.*)→/$self->_format_number($fraction, $1)/e;
  0         0  
495             }
496             }
497            
498 1 50       2 if ($format_rule =~ /←←/) {
    0          
499 1         4 $format_rule =~ s/←←/$self->_process_algorithmic_number_data($integer, $format_data, $plural, $in_fraction_rule_set)/e;
  1         3  
500             }
501             elsif((my $rule_name) = $format_rule =~ /←(.+)←/) {
502 0         0 my $type = 'public';
503 0 0       0 if ($rule_name =~ s/^%%/%/) {
504 0         0 $type = 'private';
505             }
506 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
507 0 0       0 if ($format_data) {
508 0         0 $format_rule =~ s/←(.*)←/$self->_process_algorithmic_number_data($integer, $format_data, $plural, $in_fraction_rule_set)/e;
  0         0  
509             }
510             else {
511 0         0 $format_rule =~ s/←(.*)←/$self->_format_number($integer, $1)/e;
  0         0  
512             }
513             }
514            
515 1 50       4 if($format_rule =~ /=.*=/) {
516 0 0       0 if($format_rule =~ /=%%.*=/) {
    0          
517 0         0 $format_rule =~ s/=%%(.*?)=/$self->_algorithmic_number_format($number, $1, 'private')/eg;
  0         0  
518             }
519             elsif($format_rule =~ /=%.*=/) {
520 0         0 $format_rule =~ s/=%(.*?)=/$self->_algorithmic_number_format($number, $1, 'public')/eg;
  0         0  
521             }
522             else {
523 0         0 $format_rule =~ s/=(.*?)=/$self->_format_number($integer, $1)/eg;
  0         0  
524             }
525             }
526             }
527            
528             # Everything else
529             else {
530             # At this stage we have a non negative integer
531 11 100       23 if ($format_rule =~ /\[.*\]/) {
532 3 50 33     16 if ($in_fraction_rule_set && $number * $base_value == 1) {
    50 33        
533 0         0 $format_rule =~ s/\[.*\]//;
534             }
535             # Not fractional rule set Number is a multiple of $divisor and the multiple is even
536             elsif (! $in_fraction_rule_set && ! ($number % $divisor) ) {
537 0         0 $format_rule =~ s/\[.*\]//;
538             }
539             else {
540 3         11 $format_rule =~ s/[\[\]]//g;
541             }
542             }
543            
544 11 100       11 if ($in_fraction_rule_set) {
545 2 50       10 if (my ($rule_name) = $format_rule =~ /←(.*)←/) {
    50          
546 0 0       0 if (length $rule_name) {
547 0         0 my $type = 'public';
548 0 0       0 if ($rule_name =~ s/^%%/%/) {
549 0         0 $type = 'private';
550             }
551 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
552 0 0       0 if ($format_data) {
553 0         0 $format_rule =~ s/←(.*)←/$self->_process_algorithmic_number_data($number * $base_value, $format_data, $plural, $in_fraction_rule_set)/e;
  0         0  
554             }
555             else {
556 0         0 $format_rule =~ s/←(.*)←/$self->_format_number($number * $base_value, $1)/e;
  0         0  
557             }
558             }
559             else {
560 0         0 $format_rule =~ s/←←/$self->_process_algorithmic_number_data($number * $base_value, $format_data, $plural, $in_fraction_rule_set)/e;
  0         0  
561             }
562             }
563             elsif($format_rule =~ /=.*=/) {
564 0         0 $format_rule =~ s/=(.*?)=/$self->_format_number($number, $1)/eg;
  0         0  
565             }
566             }
567             else {
568 9 100       27 if (my ($rule_name) = $format_rule =~ /→(.*)→/) {
569 3 50       5 if (length $rule_name) {
570 0         0 my $type = 'public';
571 0 0       0 if ($rule_name =~ s/^%%/%/) {
572 0         0 $type = 'private';
573             }
574 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
575 0 0       0 if ($format_data) {
576 0         0 $format_rule =~ s/→(.+)→/$self->_process_algorithmic_number_data($number % $divisor, $format_data, $plural)/e;
  0         0  
577             }
578             else {
579 0         0 $format_rule =~ s/→(.*)→/$self->_format_number($number % $divisor, $1)/e;
  0         0  
580             }
581             }
582             else {
583 3         6 $format_rule =~ s/→→/$self->_process_algorithmic_number_data($number % $divisor, $format_data, $plural)/e;
  3         6  
584             }
585             }
586            
587 9 50       14 if (my ($rule_name) = $format_rule =~ /←(.*)←/) {
588 0 0       0 if (length $rule_name) {
589 0         0 my $type = 'public';
590 0 0       0 if ($rule_name =~ s/^%%/%/) {
591 0         0 $type = 'private';
592             }
593 0         0 my $format_data = $self->_get_algorithmic_number_format_data_by_name($rule_name, $type);
594 0 0       0 if ($format_data) {
595 0         0 $format_rule =~ s|←(.*)←|$self->_process_algorithmic_number_data(int ($number / $divisor), $format_data, $plural)|e;
  0         0  
596             }
597             else {
598 0         0 $format_rule =~ s|←(.*)←|$self->_format_number(int($number / $divisor), $1)|e;
  0         0  
599             }
600             }
601             else {
602 0         0 $format_rule =~ s|←←|$self->_process_algorithmic_number_data(int($number / $divisor), $format_data, $plural)|e;
  0         0  
603             }
604             }
605            
606 9 100       22 if($format_rule =~ /=.*=/) {
607 4 50       18 if($format_rule =~ /=%%.*=/) {
    100          
608 0         0 $format_rule =~ s/=%%(.*?)=/$self->_algorithmic_number_format($number, $1, 'private')/eg;
  0         0  
609             }
610             elsif($format_rule =~ /=%.*=/) {
611 2         8 $format_rule =~ s/=%(.*?)=/$self->_algorithmic_number_format($number, $1, 'public')/eg;
  2         8  
612             }
613             else {
614 2         6 $format_rule =~ s/=(.*?)=/$self->_format_number($number, $1)/eg;
  2         6  
615             }
616             }
617             }
618             }
619            
620 14         64 return $format_rule;
621             }
622              
623             sub _process_algorithmic_number_data_fractions {
624 1     1   1 my ($self, $fraction, $format_data, $plural) = @_;
625            
626 1         7 my $result = '';
627 1         3 foreach my $digit (split //, $fraction) {
628 1         5 $result .= $self->_process_algorithmic_number_data($digit, $format_data, $plural, 1);
629             }
630            
631 1         3 return $result;
632             }
633              
634             sub _get_algorithmic_number_format {
635 14     14   12 my ($self, $number, $format_data) = @_;
636            
637 20     20   106110 use bignum;
  20         75856  
  20         93  
638 14 100 100     36 return $format_data->{'-x'} if $number =~ /^-/ && exists $format_data->{'-x'};
639 13 100 100     26 return $format_data->{'x.x'} if $number =~ /\./ && exists $format_data->{'x.x'};
640 12 100 66     67 return $format_data->{0} if $number == 0 || $number =~ /^-/;
641 6 100       397 return $format_data->{max} if $number >= $format_data->{max}{base_value};
642            
643 4         4 my $previous = 0;
644 4         62 foreach my $key (sort { $a <=> $b } grep /^[0-9]+$/, keys %$format_data) {
  512         292  
645 71 100       433 next if $key == 0;
646 67 100       3084 return $format_data->{$key} if $number == $key;
647 66 100       73 return $format_data->{$previous} if $number < $key;
648 63         51 $previous = $key;
649             }
650             }
651            
652 20     20   820752 no Moose::Role;
  20         41  
  20         209  
653              
654             1;
655              
656             # vim: tabstop=4