File Coverage

blib/lib/Lingua/SPA/Numeros.pm
Criterion Covered Total %
statement 220 226 97.3
branch 129 142 90.8
condition 26 28 92.8
subroutine 27 27 100.0
pod 17 17 100.0
total 419 440 95.2


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8; -*-
2             #
3             # Jose Luis Rey Barreira (c) 2001-2009
4             # Copyright (c) PetaMem, s.r.o. 2010-present
5             #
6              
7             package Lingua::SPA::Numeros;
8             # ABSTRACT: Number 2 word conversion in SPA.
9              
10             # {{{ use block
11              
12 4     4   440794 use 5.16.0;
  4         15  
13 4     4   21 use utf8;
  4         7  
  4         52  
14              
15 4     4   149 use Carp;
  4         7  
  4         538  
16              
17             # }}}
18             # {{{ var block
19             our $VERSION = '0.2603230';
20              
21              
22             our @EXPORT_OK = qw( $MALE $FEMALE $NEUTRAL $MALE $FEMALE $NEUTRAL);
23              
24 4     4   23 no warnings; ## no critic
  4         18  
  4         343  
25             our $MALE => 'o';
26             our $FEMALE => 'a';
27             our $NEUTRAL => '';
28 4     4   19 use warnings;
  4         9  
  4         211  
29              
30 4         34 use fields qw/ ACENTOS MAYUSCULAS UNMIL HTML DECIMAL SEPARADORES GENERO
31 4     4   2301 POSITIVO NEGATIVO FORMATO /;
  4         6118  
32              
33             my %opt_alias = qw(
34             ACCENTS ACENTOS
35             UPPERCASE MAYUSCULAS
36             SEPARATORS SEPARADORES
37             GENDER GENERO
38             POSITIVE POSITIVO
39             NEGATIVE NEGATIVO
40             FORMAT FORMATO );
41              
42             my %new_defaults = (
43             ACENTOS => 1,
44             MAYUSCULAS => 0,
45             UNMIL => 1,
46             HTML => 0,
47             DECIMAL => '.',
48             SEPARADORES => '_',
49             GENERO => $MALE,
50             POSITIVO => '',
51             NEGATIVO => 'menos',
52             FORMATO => 'con %02d ctms.',
53             );
54              
55             # }}}
56             # {{{ new
57              
58             sub new {
59 5     5 1 223739 my $self = shift;
60 5 100       28 unless ( ref $self ) {
61 4         52 $self = fields::new($self);
62             }
63              
64             #%$self = (%new_defaults, @_);
65             { # Compatibility conversion of SEXO into GENERO
66 5         17499 my %opts = ( %new_defaults, @_ );
  5         45  
67 5 100       41 if ( $opts{'SEXO'} ) {
68 1         3 $opts{'GENERO'} = $opts{'SEXO'};
69 1         27 delete $opts{'SEXO'};
70             }
71 5         37 %$self = %opts
72             }
73 5         39 return $self;
74             }
75              
76             # }}}
77             # {{{ cardinal
78              
79             sub cardinal {
80 20899     20899 1 8450566 my $self = shift;
81 20899         34755 my $num = shift;
82 20899         60407 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
83 20899         58313 my @words = cardinal_simple( $ent, $exp, $self->{'UNMIL'}, $self->{'GENERO'} );
84 20899 100       45455 if (@words) {
85 20898 50 66     41243 unshift @words, $self->{'NEGATIVO'} if $sgn < 0 and $self->{'NEGATIVO'};
86 20898 100 100     93959 unshift @words, $self->{'POSITIVO'} if $sgn > 0 and $self->{'POSITIVO'};
87 20898         104007 return $self->retval( join( " ", @words ) );
88             }
89             else {
90 1         18 return $self->retval('cero');
91             }
92             }
93              
94             # }}}
95             # {{{ real
96              
97             sub real {
98 10456     10456 1 8383777 my $self = shift;
99 10456         28724 my ( $num, $genf, $genm ) = @_;
100 10456         35628 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
101              
102 10455         23819 my $gen = $self->{'GENERO'};
103 10455 50       25556 $genf = $gen unless defined $genf;
104 10455 50       22738 $genm = $genf unless defined $genm;
105              
106             # Convertir la parte entera ajustando el sexo
107             #my @words = cardinal_simple($ent, $exp, $self->{'UNMIL'}, $gen);
108              
109             # Traducir la parte decimal de acuerdo al formato
110 10455         23080 for ( $self->{'FORMATO'} ) {
111 10455 100       59559 /%([0-9]*)s/ && do {
112              
113             # Textual, se traduce según el genero
114 7 50       20 $frc = substr( '0' x $exp . $frc, 0, $1 ) if $1;
115 7         25 $frc = join( " ", fraccion_simple( $frc, $exp, $self->{'UNMIL'}, $genf, $genm ) );
116 7 50       25 $frc = $frc ? sprintf( $self->{'FORMATO'}, $frc ) : '';
117 7         12 last;
118             };
119 10448 100       36672 /%([0-9]*)d/ && do {
120              
121             # Numérico, se da formato a los dígitos
122 10447         52775 $frc = substr( '0' x $exp . $frc, 0, $1 );
123 10447         46779 $frc = sprintf( $self->{'FORMATO'}, $frc );
124 10447         19577 last;
125             };
126 1         2 do {
127              
128             # Sin formato, se ignoran los decimales
129 1         2 $frc = '';
130 1         25 last;
131             };
132             }
133 10455 100       24885 if ($ent) {
134 10454 100       39135 $ent = $self->cardinal( ( $sgn < 0 ? '-' : '+' ) . $ent );
135             }
136             else {
137 1         4 $ent = 'cero';
138             }
139 10455 100 66     61596 $ent .= ' ' . $frc if $ent and $frc;
140 10455         23029 return $self->retval($ent);
141             }
142              
143             # }}}
144             # {{{ ordinal
145              
146             sub ordinal {
147 10448     10448 1 16257939 my $self = shift;
148 10448         19917 my $num = shift;
149 10448         36965 my ( $sgn, $ent, $frc, $exp ) = parse_num( $num, $self->{'DECIMAL'}, $self->{'SEPARADORES'} );
150              
151 10448 100       25058 croak "Ordinal negativo" if $sgn < 0;
152 10447 50       21902 carp "Ordinal con decimales" if $frc;
153              
154 10447 50       32456 if ( $ent =~ /^0*$/ ) {
155 0         0 carp "Ordinal cero";
156 0         0 return '';
157             }
158              
159 10447         25761 my $text = join( " ", ordinal_simple( $ent, $exp, $self->{'GENERO'} ) );
160              
161 10446         36146 return $self->retval($text);
162             }
163              
164             # }}}
165             # {{{ Build the accessors
166              
167             {
168             my @a = @_;
169             my %names = ( ( map { $_ => $_ } keys %new_defaults ), %opt_alias );
170             while ( my ( $opt, $alias ) = each %names ) {
171             $opt = lc $opt;
172 4     4   4440 no strict 'refs'; ## no critic
  4         10  
  4         10340  
173             *$opt = sub {
174 24     24   64 my $self = shift;
175 24 50       208 return $self->{$alias} unless @a;
176 0         0 $self->{$alias} = shift @a;
177 0         0 return $self;
178             }
179             }
180             }
181              
182             # }}}
183              
184             #####################################################################
185             #
186             # Soporte para números CARDINALES
187             #
188             ####################################################################
189             # {{{ variable declarations II
190              
191             my @cardinal_30 = qw/ cero un dos tres cuatro cinco seis siete ocho nueve diez
192             once doce trece catorce quince dieciséis diecisiete dieciocho diecinueve
193             veinte veintiun veintidós veintitrés veinticuatro veinticinco veintiséis
194             veintisiete veintiocho veintinueve /;
195              
196             my @cardinal_dec = qw/
197             0 1 2 treinta cuarenta cincuenta sesenta setenta ochenta noventa /;
198              
199             my @cardinal_centenas = (
200             "", qw/
201             ciento doscientos trescientos cuatrocientos quinientos
202             seiscientos setecientos ochocientos novecientos /
203             );
204              
205             my @cardinal_megas = (
206             "", qw/ m b tr cuatr quint sext sept oct non dec undec
207             dudec tredec cuatordec quindec sexdec sepdec octodec novendec vigint /
208             );
209              
210             my $MAX_DIGITS = 6 * @cardinal_megas;
211              
212             # }}}
213             # {{{ cardinal_e2
214              
215             sub cardinal_e2 {
216 142592     142592 1 232170 my ( $n, $nn ) = @_;
217              
218 142592 100       246648 return if $n == 0;
219 142286 100       284060 do { push @$nn, $cardinal_30[$n]; return } if $n < 30;
  83959         181197  
  83959         139568  
220 58327         153076 $n =~ /^(.)(.)$/;
221 58327 100       211498 push @$nn, $cardinal_30[$2], "y" if $2;
222 58327         125260 push @$nn, $cardinal_dec[$1];
223              
224 58327         95766 return;
225             }
226              
227             # }}}
228             # {{{ cardinal_e3
229              
230             sub cardinal_e3 {
231 145267     145267 1 258615 my ( $n, $nn ) = @_;
232              
233 145267 100       260731 return if $n == 0;
234 143679 100       265426 $n == 100 and do { push @$nn, "cien"; return };
  1087         2496  
  1087         2314  
235 142592         325815 cardinal_e2( $n % 100, $nn );
236 142592 100       337267 $n >= 100 and push @$nn, $cardinal_centenas[ int( $n / 100 ) ];
237              
238 142592         218484 return;
239             }
240              
241             # }}}
242             # {{{ cardinal_e6
243              
244             sub cardinal_e6 {
245 83763     83763 1 220171 my ( $n, $nn, $mag, $un_mil, $postfix ) = @_;
246              
247 83763 100       299601 return if $n == 0;
248 76623 100       175044 push @$nn, $cardinal_megas[$mag] . $postfix->[ $n == 1 ] if $mag;
249 76623         183138 cardinal_e3( $n % 1000, $nn );
250 76623         137082 my $n3 = int( $n / 1000 );
251 76623 100       141431 if ($n3) {
252 50786         87962 push @$nn, "mil";
253 50786 100 100     139623 cardinal_e3( $n3, $nn ) if $n3 != 1 or $un_mil;
254             }
255              
256 76623         569713 return;
257             }
258              
259             # }}}
260             # {{{ cardinal_generic
261              
262             sub cardinal_generic {
263 34388     34388 1 76126 my ( $n, $exp, $fmag, $gen ) = @_;
264 34388   100     112668 $gen //= '';
265              
266 34388         181533 $n =~ s/^0*//; # eliminar ceros a la izquierda
267 34388 100       90323 return () unless $n;
268 34386 100       96138 croak("Fuera de rango") if length($n) + $exp > $MAX_DIGITS;
269 34383         85031 $n .= "0" x ( $exp % 6 ); # agregar ceros a la derecha
270 34383         81326 my $mag = int( $exp / 6 );
271 34383         54374 my @group = ();
272 34383         258191 $fmag->( $1, \@group, $mag++ ) while $n =~ s/(.{1,6})$//x;
273 34383 100       117921 $group[0] .= $gen if $group[0] =~ /un$/;
274 34383         291974 return reverse @group;
275             }
276              
277             # }}}
278             # {{{ cardinal_simple
279              
280             sub cardinal_simple {
281 33160     33160 1 9825909 my ( $n, $exp, $un_mil, $gen ) = @_;
282              
283 33160 100       79758 $un_mil = $un_mil ? 1 : 0;
284 33160 100       86293 $gen = $NEUTRAL unless $gen;
285             my $format = sub {
286 65498     65498   190779 cardinal_e6( $_[0], $_[1], $_[2], $un_mil, [ 'illones', 'illón' ] );
287 33160         155891 };
288 33160         86014 return cardinal_generic( $n, $exp, $format, $gen );
289             }
290              
291             # }}}
292             # {{{ fraccion_mag_prefix
293              
294             sub fraccion_mag_prefix {
295 1244     1244 1 2121 my ( $mag, $gp ) = @_;
296              
297 1244 50       2053 return "" unless $mag;
298 1244 100       2011 return "décim" . $gp if $mag == 1;
299 1236 100       1909 return "centésim" . $gp if $mag == 2;
300             my $format = sub {
301 1225     1225   2714 cardinal_e6( $_[0], $_[1], $_[2], 0, [ 'illon', 'illon' ] );
302 1228         2934 };
303 1228         2043 my @name = cardinal_generic( 1, $mag, $format, "" );
304 1225 100       2487 shift @name unless $mag % 6;
305 1225         7308 return join( "", @name, "ésim", $gp );
306             }
307              
308             # }}}
309             # {{{ fraccion_simple
310              
311             sub fraccion_simple {
312 1247     1247 1 838766 my ( $n, $exp, $un_mil, $gen, $ngen ) = @_;
313              
314 1247         19076 $n =~ s/0*$//; # eliminar 0 a la derecha
315 1247 50       4100 return () if $n == 0;
316 1247 100       2813 $ngen = $gen unless defined $ngen;
317 1247         1896 $exp = -$exp + length $n; # adjust exponent
318 1247 100       2526 croak("Fuera de rango") if $exp > $MAX_DIGITS;
319 1244 100       5190 $gen .= "s" unless $n =~ /^0*1$/;
320 1244         2648 return ( cardinal_simple( $n, 0, $un_mil, $ngen ), fraccion_mag_prefix( $exp, $gen ) );
321             }
322              
323             # }}}
324             #####################################################################
325             #
326             # Soporte para números ORDINALES
327             #
328             ####################################################################
329             # {{{ variable declarations III
330              
331             my @ordinal_13 = (
332             '', qw/ primer_ segund_ tercer_ cuart_ quint_ sext_
333             séptim_ octav_ noven_ décim_ undécim_ duodécim_ /
334             );
335              
336             my @ordinal_dec = qw/ 0 1 vi tri cuadra quicua sexa septua octo nona /;
337              
338             my @ordinal_cen = qw/ 0 c duoc tric cuadring quing sexc septig octing noning /;
339              
340             # }}}
341             # {{{ ordinal_e2
342              
343             sub ordinal_e2 {
344 20526     20526 1 41429 my ( $n, $nn ) = @_;
345              
346 20526 100       49842 return if $n == 0;
347 20222 100       43371 if ( $n < 13 ) {
348 5162         15983 push @$nn, $ordinal_13[$n];
349 5162         13614 return;
350             }
351 15060         46503 $n =~ /^(.)(.)$/;
352 15060         50490 my $lo = $ordinal_13[$2];
353 15060 100       44227 if ( $1 <= 2 ) {
354 7212 100       32977 my $name = $2
    50          
    100          
355             ? ( $1 == 1 ? 'decimo' : 'vigesimo' )
356             : ( $1 == 1 ? 'décim_' : 'vigésim_' );
357 7212 100       25809 $name =~ s/o$// if $2 == 8; # special case vowels colapsed
358 7212         24208 push @$nn, $name . $lo;
359 7212         19235 return;
360             }
361 7848 100       31912 push @$nn, $lo if $2;
362 7848         27374 push @$nn, $ordinal_dec[$1] . 'gésim_';
363 7848         17001 return;
364             }
365              
366             # }}}
367             # {{{ ordinal_e3
368              
369             sub ordinal_e3 {
370 20634     20634 1 43626 my ( $n, $nn ) = @_;
371              
372 20634 100       54854 return if $n == 0;
373 20526         60188 ordinal_e2( $n % 100, $nn );
374 20526 100       79025 push @$nn, $ordinal_cen[ int( $n / 100 ) ] . 'entésim_' if $n > 99;
375              
376 20526         32971 return;
377             }
378              
379             # }}}
380             # {{{ ordinal_e6
381              
382             sub ordinal_e6 {
383 21154     21154 1 68158 my ( $n, $nn, $mag ) = @_;
384              
385 21154 100       65507 return if $n == 0;
386 20634 50       52760 push @$nn, $cardinal_megas[$mag] . 'illonésim_' if $mag;
387 20634         69891 ordinal_e3( $n % 1000, $nn );
388 20634         43249 my $n3 = int( $n / 1000 );
389 20634 100       64025 if ($n3) {
390 18863 100       38918 if ( $n3 > 1 ) {
391 18723         36653 my $pos = @$nn; # keep pos to adjust number
392 18723         52968 cardinal_e3( $n3, $nn ); # this is not a typo, its cardinal
393 18723         58168 $nn->[$pos] .= 'milésim_';
394             }
395             else {
396 140         364 push @$nn, "milésim_";
397             }
398             }
399              
400 20634         39971 return;
401             }
402              
403             # }}}
404             # {{{ ordinal_simple
405              
406             sub ordinal_simple {
407 21396     21396 1 11833546 my ( $n, $exp, $gen ) = @_;
408              
409 21396         119149 $n =~ s/^0*//; # eliminar ceros a la izquierda
410 21396 100       73310 return () unless $n;
411 21395 100       68560 croak("Fuera de rango") if length($n) + $exp > $MAX_DIGITS;
412 21394         57295 $n .= "0" x ( $exp % 6 ); # agregar ceros a la derecha
413 21394         61213 my $mag = int( $exp / 6 );
414              
415 21394         39220 my @group = ();
416 21394 100       55214 if ( $mag == 0 ) {
417 21154         132435 $n =~ s/(.{1,6})$//x;
418 21154         67960 ordinal_e6( $1, \@group, $mag++ );
419             }
420              
421 21394         98211 while ( $n =~ s/(.{1,6})$//x ) {
422 22520 100       67085 if ( $1 == 0 ) {
423 4940         6868 $mag++;
424 4940         17003 next;
425             }
426 17580         26347 my $words = [];
427 17580 100       38706 if ( $1 == 1 ) {
428 540         1159 push @$words, '';
429             }
430             else {
431 17040         32907 cardinal_e6( $1, $words, 0, 0, [] );
432             }
433 17580         44653 $words->[0] .= $cardinal_megas[ $mag++ ] . 'illonésim_';
434 17580         87721 push @group, @$words;
435             }
436              
437 21394 50       56531 unless ($gen) {
438 0         0 $group[0] =~ s/r_$/r/; # Ajustar neutros en 1er, 3er, etc.
439 0         0 $gen = $MALE;
440             }
441 21394         239937 s/_/$gen/g for @group;
442 21394         142488 return reverse @group;
443             }
444              
445             # }}}
446             # {{{ parse_num
447              
448             sub parse_num {
449 41839     41839 1 131003 my ( $num, $dec, $sep ) = @_;
450              
451             # Eliminar blancos y separadores
452 41839         274977 $num =~ s/[\s\Q$sep\E]//g;
453 41839 100       152184 $dec = '\\' . $dec if $dec eq '.';
454 41839 100       480420 my ( $sgn, $int, $frc, $exp ) = $num =~ /^
455             ([+-]?) (?= \d | $dec\d ) # signo
456             (\d*) # parte entera
457             (?: $dec (\d*) )? # parte decimal
458             (?: [Ee] ([+-]?\d+) )? # exponente
459             $/x or croak("Error de sintaxis");
460              
461 41838 100       112875 $sgn = $sgn eq '-' ? -1 : 1; # ajustar signo
462 41838 100 100     346358 return ( $sgn, $int || 0, $frc || 0, $exp ) unless $exp ||= 0;
      100        
      100        
463              
464 33   100     81 $int ||= '';
465 33   100     3154 $frc ||= '';
466              
467             # reducir la magnitud del exponente
468 33 100       97 if ( $exp > 0 ) {
469 12 100       31 if ( $exp > length $frc ) {
470 4         8 $exp -= length $frc;
471 4         9 $int .= $frc;
472 4         10 $frc = '';
473             }
474             else {
475 8         21 $int .= substr( $frc, 0, $exp );
476 8         17 $frc = substr( $frc, $exp );
477 8         16 $exp = 0;
478             }
479             }
480             else {
481 21 100       53 if ( -$exp > length $int ) {
482 4         8 $exp += length $int;
483 4         11 $frc = $int . $frc;
484 4         54 $int = '';
485             }
486             else {
487 17         51 $frc = substr( $int, $exp + length $int ) . $frc;
488 17         36 $int = substr( $int, 0, $exp + length $int );
489 17         34 $exp = 0;
490             }
491             }
492 33   100     221 return ( $sgn, $int || 0, $frc || 0, $exp );
      100        
493             }
494              
495             # }}}
496             # {{{ retval
497              
498             sub retval {
499 41800     41800 1 63250 my $self = shift;
500 41800         60233 my $rv = shift;
501 41800 100       90069 if ( $self->{ACENTOS} ) {
502 41794 100       90086 if ( $self->{HTML} ) {
503 4         38 $rv =~ s/([áéíóú])/&$1acute;/g;
504 4         10 $rv =~ tr/áéíóú/aeiou/;
505             }
506             }
507             else {
508 6         12 $rv =~ tr/áéíóú/aeiou/;
509             }
510 41800 100       185740 return $self->{MAYUSCULAS} ? uc $rv : $rv;
511             }
512              
513             # }}}
514              
515             1;
516             __END__