File Coverage

blib/lib/Lingua/SPA/Numeros.pm
Criterion Covered Total %
statement 230 236 97.4
branch 130 142 91.5
condition 26 28 92.8
subroutine 30 30 100.0
pod 17 17 100.0
total 433 453 95.5


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; mode:folding; coding:utf-8; -*-
2             #
3             # Jose Luis Rey Barreira (C) 2001-2009
4             #
5              
6             package Lingua::SPA::Numeros;
7             # ABSTRACT: Number 2 word conversion in SPA.
8              
9             # {{{ use block
10              
11 4     4   126925 use 5.10.1;
  4         15  
  4         176  
12 4     4   22 use utf8;
  4         7  
  4         30  
13 4     4   81 use strict;
  4         73  
  4         112  
14 4     4   19 use warnings;
  4         8  
  4         149  
15              
16 4     4   19 use Carp;
  4         6  
  4         484  
17              
18             # }}}
19             # {{{ variables declarations
20              
21             our $VERSION = 0.0682;
22              
23              
24             our @EXPORT_OK = qw( $MALE $FEMALE $NEUTRAL $MALE $FEMALE $NEUTRAL);
25              
26 4     4   21 no warnings; ## no critic
  4         7  
  4         305  
27             our $MALE => 'o';
28             our $FEMALE => 'a';
29             our $NEUTRAL => '';
30 4     4   21 use warnings;
  4         7  
  4         170  
31              
32 4         23 use fields qw/ ACENTOS MAYUSCULAS UNMIL HTML DECIMAL SEPARADORES GENERO
33 4     4   3441 POSITIVO NEGATIVO FORMATO /;
  4         6637  
34              
35             my %opt_alias = qw(
36             ACCENTS ACENTOS
37             UPPERCASE MAYUSCULAS
38             SEPARATORS SEPARADORES
39             GENDER GENERO
40             POSITIVE POSITIVO
41             NEGATIVE NEGATIVO
42             FORMAT FORMATO );
43              
44             my %new_defaults = (
45             ACENTOS => 1,
46             MAYUSCULAS => 0,
47             UNMIL => 1,
48             HTML => 0,
49             DECIMAL => '.',
50             SEPARADORES => '_',
51             GENERO => $MALE,
52             POSITIVO => '',
53             NEGATIVO => 'menos',
54             FORMATO => 'con %02d ctms.',
55             );
56              
57             # }}}
58             # {{{ new
59              
60             sub new {
61 5     5 1 3178 my $self = shift;
62 5 100       26 unless ( ref $self ) {
63 4         26 $self = fields::new($self);
64             }
65              
66             #%$self = (%new_defaults, @_);
67             { # Compatibility conversion of SEXO into GENERO
68 5         11446 my %opts = ( %new_defaults, @_ );
  5         49  
69 5 100       26 if ( $opts{'SEXO'} ) {
70 1         3 $opts{'GENERO'} = $opts{'SEXO'};
71 1         4 delete $opts{'SEXO'};
72             }
73 5         41 %$self = %opts
74             }
75 5         22 return $self;
76             }
77              
78             # }}}
79             # {{{ cardinal
80              
81             sub cardinal {
82 20899     20899 1 4830548 my $self = shift;
83 20899         29470 my $num = shift;
84 20899         55651 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
85 20899         69254 my @words = cardinal_simple( $ent, $exp, $self->{'UNMIL'}, $self->{'GENERO'} );
86 20899 100       50124 if (@words) {
87 20898 100 66     45813 unshift @words, $self->{'NEGATIVO'} if $sgn < 0 and $self->{'NEGATIVO'};
88 20898 100 100     110049 unshift @words, $self->{'POSITIVO'} if $sgn > 0 and $self->{'POSITIVO'};
89 20898         108461 return $self->retval( join( " ", @words ) );
90             }
91             else {
92 1         4 return $self->retval('cero');
93             }
94             }
95              
96             # }}}
97             # {{{ real
98              
99             sub real {
100 10456     10456 1 4837107 my $self = shift;
101 10456         18726 my ( $num, $genf, $genm ) = @_;
102 10456         34223 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
103              
104 10455         20579 my $gen = $self->{'GENERO'};
105 10455 50       23341 $genf = $gen unless defined $genf;
106 10455 50       22137 $genm = $genf unless defined $genm;
107              
108             # Convertir la parte entera ajustando el sexo
109             #my @words = cardinal_simple($ent, $exp, $self->{'UNMIL'}, $gen);
110              
111             # Traducir la parte decimal de acuerdo al formato
112 10455         20777 for ( $self->{'FORMATO'} ) {
113 10455 100       41373 /%([0-9]*)s/ && do {
114              
115             # Textual, se traduce según el genero
116 7 50       23 $frc = substr( '0' x $exp . $frc, 0, $1 ) if $1;
117 7         19 $frc = join( " ", fraccion_simple( $frc, $exp, $self->{'UNMIL'}, $genf, $genm ) );
118 7 50       35 $frc = $frc ? sprintf( $self->{'FORMATO'}, $frc ) : '';
119 7         13 last;
120             };
121 10448 100       37781 /%([0-9]*)d/ && do {
122              
123             # Numérico, se da formato a los dígitos
124 10447         33577 $frc = substr( '0' x $exp . $frc, 0, $1 );
125 10447         40418 $frc = sprintf( $self->{'FORMATO'}, $frc );
126 10447         14310 last;
127             };
128 1         2 do {
129              
130             # Sin formato, se ignoran los decimales
131 1         2 $frc = '';
132 1         3 last;
133             };
134             }
135 10455 100       18662 if ($ent) {
136 10454 100       38358 $ent = $self->cardinal( ( $sgn < 0 ? '-' : '+' ) . $ent );
137             }
138             else {
139 1         1 $ent = 'cero';
140             }
141 10455 100 66     60673 $ent .= ' ' . $frc if $ent and $frc;
142 10455         23234 return $self->retval($ent);
143             }
144              
145             # }}}
146             # {{{ ordinal
147              
148             sub ordinal {
149 10448     10448 1 9170605 my $self = shift;
150 10448         15372 my $num = shift;
151 10448         29790 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
152              
153 10448 100       26472 croak "Ordinal negativo" if $sgn < 0;
154 10447 50       17912 carp "Ordinal con decimales" if $frc;
155              
156 10447 50       27214 if ( $ent =~ /^0*$/ ) {
157 0         0 carp "Ordinal cero";
158 0         0 return '';
159             }
160              
161 10447         25565 my $text = join( " ", ordinal_simple( $ent, $exp, $self->{'GENERO'} ) );
162              
163 10446         37456 return $self->retval($text);
164             }
165              
166             # }}}
167              
168             { # Build the accessors
169             my @a = @_;
170             my %names = ( ( map { $_ => $_ } keys %new_defaults ), %opt_alias );
171             while ( my ( $opt, $alias ) = each %names ) {
172             $opt = lc $opt;
173 4     4   4109 no strict 'refs'; ## no critic
  4         9  
  4         9767  
174             *$opt = sub {
175 24     24   37 my $self = shift;
176 24 50       114 return $self->{$alias} unless @a;
177 0         0 $self->{$alias} = shift @a;
178 0         0 return $self;
179             }
180             }
181             }
182              
183             # }}}
184              
185             #####################################################################
186             #
187             # Soporte para números CARDINALES
188             #
189             ####################################################################
190             # {{{ variable declarations II
191              
192             my @cardinal_30 = qw/ cero un dos tres cuatro cinco seis siete ocho nueve diez
193             once doce trece catorce quince dieciséis diecisiete dieciocho diecinueve
194             veinte veintiun veintidós veintitrés veinticuatro veinticinco veintiséis
195             veintisiete veintiocho veintinueve /;
196              
197             my @cardinal_dec = qw/
198             0 1 2 treinta cuarenta cincuenta sesenta setenta ochenta noventa /;
199              
200             my @cardinal_centenas = (
201             "", qw/
202             ciento doscientos trescientos cuatrocientos quinientos
203             seiscientos setecientos ochocientos novecientos /
204             );
205              
206             my @cardinal_megas = (
207             "", qw/ m b tr cuatr quint sext sept oct non dec undec
208             dudec tredec cuatordec quindec sexdec sepdec octodec novendec vigint /
209             );
210              
211             my $MAX_DIGITS = 6 * @cardinal_megas;
212              
213             # }}}
214             # {{{ cardinal_e2
215              
216             sub cardinal_e2 {
217 142592     142592 1 158977 my ( $n, $nn ) = @_;
218              
219 142592 100       264709 return if $n == 0;
220 142286 100       254918 do { push @$nn, $cardinal_30[$n]; return } if $n < 30;
  83959         135061  
  83959         121116  
221 58327         139848 $n =~ /^(.)(.)$/;
222 58327 100       216724 push @$nn, $cardinal_30[$2], "y" if $2;
223 58327         105246 push @$nn, $cardinal_dec[$1];
224              
225 58327         80776 return;
226             }
227              
228             # }}}
229             # {{{ cardinal_e3
230              
231             sub cardinal_e3 {
232 145267     145267 1 177306 my ( $n, $nn ) = @_;
233              
234 145267 100       254311 return if $n == 0;
235 143679 100       248229 $n == 100 and do { push @$nn, "cien"; return };
  1087         1787  
  1087         1892  
236 142592         262994 cardinal_e2( $n % 100, $nn );
237 142592 100       336245 $n >= 100 and push @$nn, $cardinal_centenas[ int( $n / 100 ) ];
238              
239 142592         176795 return;
240             }
241              
242             # }}}
243             # {{{ cardinal_e6
244              
245             sub cardinal_e6 {
246 83763     83763 1 166860 my ( $n, $nn, $mag, $un_mil, $postfix ) = @_;
247              
248 83763 100       286657 return if $n == 0;
249 76623 100       170811 push @$nn, $cardinal_megas[$mag] . $postfix->[ $n == 1 ] if $mag;
250 76623         150305 cardinal_e3( $n % 1000, $nn );
251 76623         110093 my $n3 = int( $n / 1000 );
252 76623 100       139240 if ($n3) {
253 50786         63610 push @$nn, "mil";
254 50786 100 100     152670 cardinal_e3( $n3, $nn ) if $n3 != 1 or $un_mil;
255             }
256              
257 76623         609020 return;
258             }
259              
260             # }}}
261             # {{{ cardinal_generic
262              
263             sub cardinal_generic {
264 34388     34388 1 58579 my ( $n, $exp, $fmag, $gen ) = @_;
265 34388   100     88556 $gen //= '';
266              
267 34388         114847 $n =~ s/^0*//; # eliminar ceros a la izquierda
268 34388 100       78393 return () unless $n;
269 34386 100       80451 croak("Fuera de rango") if length($n) + $exp > $MAX_DIGITS;
270 34383         62408 $n .= "0" x ( $exp % 6 ); # agregar ceros a la derecha
271 34383         59235 my $mag = int( $exp / 6 );
272 34383         55967 my @group = ();
273 34383         252283 $fmag->( $1, \@group, $mag++ ) while $n =~ s/(.{1,6})$//x;
274 34383 100       92289 $group[0] .= $gen if $group[0] =~ /un$/;
275 34383         250016 return reverse @group;
276             }
277              
278             # }}}
279             # {{{ cardinal_simple
280              
281             sub cardinal_simple {
282 33160     33160 1 4099284 my ( $n, $exp, $un_mil, $gen ) = @_;
283              
284 33160 100       61690 $un_mil = $un_mil ? 1 : 0;
285 33160 100       59497 $gen = $NEUTRAL unless $gen;
286             my $format = sub {
287 65498     65498   205417 cardinal_e6( $_[0], $_[1], $_[2], $un_mil, [ 'illones', 'illón' ] );
288 33160         128820 };
289 33160         74588 return cardinal_generic( $n, $exp, $format, $gen );
290             }
291              
292              
293             # }}}
294             # {{{ fraccion_mag_prefix
295              
296             sub fraccion_mag_prefix {
297 1244     1244 1 1682 my ( $mag, $gp ) = @_;
298              
299 1244 50       2177 return "" unless $mag;
300 1244 100       2083 return "décim" . $gp if $mag == 1;
301 1236 100       1897 return "centésim" . $gp if $mag == 2;
302             my $format = sub {
303 1225     1225   3587 cardinal_e6( $_[0], $_[1], $_[2], 0, [ 'illon', 'illon' ] );
304 1228         3299 };
305 1228         2457 my @name = cardinal_generic( 1, $mag, $format, "" );
306 1225 100       2686 shift @name unless $mag % 6;
307 1225         7747 return join( "", @name, "ésim", $gp );
308             }
309              
310             # }}}
311             # {{{ fraccion_simple
312              
313             sub fraccion_simple {
314 1247     1247 1 336710 my ( $n, $exp, $un_mil, $gen, $ngen ) = @_;
315              
316 1247         15729 $n =~ s/0*$//; # eliminar 0 a la derecha
317 1247 50       4306 return () if $n == 0;
318 1247 100       2469 $ngen = $gen unless defined $ngen;
319 1247         1599 $exp = -$exp + length $n; # adjust exponent
320 1247 100       2590 croak("Fuera de rango") if $exp > $MAX_DIGITS;
321 1244 100       4056 $gen .= "s" unless $n =~ /^0*1$/;
322 1244         2376 return ( cardinal_simple( $n, 0, $un_mil, $ngen ), fraccion_mag_prefix( $exp, $gen ) );
323             }
324              
325             #####################################################################
326             #
327             # Soporte para números ORDINALES
328             #
329             ####################################################################
330             # {{{ variable declarations III
331              
332             my @ordinal_13 = (
333             '', qw/ primer_ segund_ tercer_ cuart_ quint_ sext_
334             séptim_ octav_ noven_ décim_ undécim_ duodécim_ /
335             );
336              
337             my @ordinal_dec = qw/ 0 1 vi tri cuadra quicua sexa septua octo nona /;
338              
339             my @ordinal_cen = qw/ 0 c duoc tric cuadring quing sexc septig octing noning /;
340              
341             # }}}
342             # {{{ ordinal_e2
343              
344             sub ordinal_e2 {
345 20526     20526 1 25961 my ( $n, $nn ) = @_;
346              
347 20526 100       34689 return if $n == 0;
348 20222 100       33329 if ( $n < 13 ) {
349 5162         9494 push @$nn, $ordinal_13[$n];
350 5162         7550 return;
351             }
352 15060         43503 $n =~ /^(.)(.)$/;
353 15060         38141 my $lo = $ordinal_13[$2];
354 15060 100       43632 if ( $1 <= 2 ) {
355 7212 100       27148 my $name = $2
    50          
    100          
356             ? ( $1 == 1 ? 'decimo' : 'vigesimo' )
357             : ( $1 == 1 ? 'décim_' : 'vigésim_' );
358 7212 100       17754 $name =~ s/o$// if $2 == 8; # special case vowels colapsed
359 7212         17431 push @$nn, $name . $lo;
360 7212         14173 return;
361             }
362 7848 100       21989 push @$nn, $lo if $2;
363 7848         19799 push @$nn, $ordinal_dec[$1] . 'gésim_';
364 7848         12436 return;
365             }
366              
367             # }}}
368             # {{{ ordinal_e3
369              
370             sub ordinal_e3 {
371 20634     20634 1 25917 my ( $n, $nn ) = @_;
372              
373 20634 100       38646 return if $n == 0;
374 20526         47283 ordinal_e2( $n % 100, $nn );
375 20526 100       65824 push @$nn, $ordinal_cen[ int( $n / 100 ) ] . 'entésim_' if $n > 99;
376              
377 20526         26508 return;
378             }
379              
380             # }}}
381             # {{{ ordinal_e6
382              
383             sub ordinal_e6 {
384 21154     21154 1 44872 my ( $n, $nn, $mag ) = @_;
385              
386 21154 100       51807 return if $n == 0;
387 20634 50       35305 push @$nn, $cardinal_megas[$mag] . 'illonésim_' if $mag;
388 20634         41452 ordinal_e3( $n % 1000, $nn );
389 20634         33054 my $n3 = int( $n / 1000 );
390 20634 100       40006 if ($n3) {
391 18863 100       30056 if ( $n3 > 1 ) {
392 18723         21461 my $pos = @$nn; # keep pos to adjust number
393 18723         35141 cardinal_e3( $n3, $nn ); # this is not a typo, its cardinal
394 18723         42491 $nn->[$pos] .= 'milésim_';
395             }
396             else {
397 140         336 push @$nn, "milésim_";
398             }
399             }
400              
401 20634         39250 return;
402             }
403              
404             # }}}
405             # {{{ ordinal_simple
406              
407             sub ordinal_simple {
408 21396     21396 1 5530780 my ( $n, $exp, $gen ) = @_;
409              
410 21396         83687 $n =~ s/^0*//; # eliminar ceros a la izquierda
411 21396 100       47731 return () unless $n;
412 21395 100       47886 croak("Fuera de rango") if length($n) + $exp > $MAX_DIGITS;
413 21394         43378 $n .= "0" x ( $exp % 6 ); # agregar ceros a la derecha
414 21394         37260 my $mag = int( $exp / 6 );
415              
416 21394         31506 my @group = ();
417 21394 100       45469 if ( $mag == 0 ) {
418 21154         110415 $n =~ s/(.{1,6})$//x;
419 21154         55254 ordinal_e6( $1, \@group, $mag++ );
420             }
421              
422 21394         62518 while ( $n =~ s/(.{1,6})$//x ) {
423 22520 100       56513 if ( $1 == 0 ) {
424 4940         5719 $mag++;
425 4940         18709 next;
426             }
427 17580         24464 my $words = [];
428 17580 100       37737 if ( $1 == 1 ) {
429 540         1041 push @$words, '';
430             }
431             else {
432 17040         35475 cardinal_e6( $1, $words, 0, 0, [] );
433             }
434 17580         49159 $words->[0] .= $cardinal_megas[ $mag++ ] . 'illonésim_';
435 17580         95659 push @group, @$words;
436             }
437              
438 21394 50       36908 unless ($gen) {
439 0         0 $group[0] =~ s/r_$/r/; # Ajustar neutros en 1er, 3er, etc.
440 0         0 $gen = $MALE;
441             }
442 21394         274599 s/_/$gen/g for @group;
443 21394         129171 return reverse @group;
444             }
445              
446             # }}}
447             # {{{ parse_num
448              
449             sub parse_num {
450 41839     41839 1 74567 my ( $num, $dec, $sep ) = @_;
451              
452             # Eliminar blancos y separadores
453 41839         154272 $num =~ s/[\s\Q$sep\E]//g;
454 41839 100       129699 $dec = '\\' . $dec if $dec eq '.';
455 41839 100       400503 my ( $sgn, $int, $frc, $exp ) = $num =~ /^
456             ([+-]?) (?= \d | $dec\d ) # signo
457             (\d*) # parte entera
458             (?: $dec (\d*) )? # parte decimal
459             (?: [Ee] ([+-]?\d+) )? # exponente
460             $/x or croak("Error de sintaxis");
461              
462 41838 100       101199 $sgn = $sgn eq '-' ? -1 : 1; # ajustar signo
463 41838 100 100     420602 return ( $sgn, $int || 0, $frc || 0, $exp ) unless $exp ||= 0;
      100        
      100        
464              
465 33   100     61 $int ||= '';
466 33   100     63 $frc ||= '';
467              
468             # reducir la magnitud del exponente
469 33 100       56 if ( $exp > 0 ) {
470 12 100       26 if ( $exp > length $frc ) {
471 4         6 $exp -= length $frc;
472 4         6 $int .= $frc;
473 4         5 $frc = '';
474             }
475             else {
476 8         13 $int .= substr( $frc, 0, $exp );
477 8         13 $frc = substr( $frc, $exp );
478 8         11 $exp = 0;
479             }
480             }
481             else {
482 21 100       38 if ( -$exp > length $int ) {
483 4         5 $exp += length $int;
484 4         5 $frc = $int . $frc;
485 4         5 $int = '';
486             }
487             else {
488 17         34 $frc = substr( $int, $exp + length $int ) . $frc;
489 17         30 $int = substr( $int, 0, $exp + length $int );
490 17         21 $exp = 0;
491             }
492             }
493 33   100     197 return ( $sgn, $int || 0, $frc || 0, $exp );
      100        
494             }
495              
496             # }}}
497             # {{{ retval
498              
499             sub retval {
500 41800     41800 1 55222 my $self = shift;
501 41800         51311 my $rv = shift;
502 41800 100       87499 if ( $self->{ACENTOS} ) {
503 41794 100       97793 if ( $self->{HTML} ) {
504 4         32 $rv =~ s/([áéíóú])/&$1acute;/g;
505 4     4   55 $rv =~ tr/áéíóú/aeiou/;
  4         8  
  4         66  
  4         23  
506             }
507             }
508             else {
509 6         22 $rv =~ tr/áéíóú/aeiou/;
510             }
511 41800 100       202126 return $self->{MAYUSCULAS} ? uc $rv : $rv;
512             }
513              
514             # }}}
515              
516             1;
517             __END__