File Coverage

blib/lib/Lingua/SWE/Word2Num.pm
Criterion Covered Total %
statement 23 45 51.1
branch 0 12 0.0
condition 2 4 50.0
subroutine 9 10 90.0
pod 3 3 100.0
total 37 74 50.0


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8; -*-
2              
3             package Lingua::SWE::Word2Num;
4             # ABSTRACT: Word to number conversion in Swedish
5              
6 1     1   95952 use 5.16.0;
  1         3  
7 1     1   4 use utf8;
  1         2  
  1         14  
8 1     1   35 use warnings;
  1         2  
  1         78  
9              
10             # {{{ use block
11              
12 1     1   1809 use Export::Attrs;
  1         9111  
  1         5  
13 1     1   1157 use Parse::RecDescent;
  1         34410  
  1         8  
14              
15             # }}}
16             # {{{ variable declarations
17             our $VERSION = '0.2603300';
18             my $parser = sv_numerals();
19              
20             # }}}
21              
22             # {{{ w2n convert text to number
23              
24             sub w2n :Export {
25 4   100 4 1 253024 my $input = shift // return;
26              
27 3         44 return $parser->numeral($input);
28 1     1   134 }
  1         5  
  1         8  
29              
30             # }}}
31             # {{{ sv_numerals create parser for numerals
32              
33             sub sv_numerals {
34 1     1 1 5 return Parse::RecDescent->new(q{
35              
36             numeral: million { return $item[1]; } # root parse. go from maximum to minimum value
37             | millenium { return $item[1]; }
38             | century { return $item[1]; }
39             | decade { return $item[1]; }
40             | number { return $item[1]; }
41             | { return undef; }
42              
43             number: 'nitton' { $return = 19; } # try to find a word from 0 to 19
44             | 'arton' { $return = 18; }
45             | 'sjutton' { $return = 17; }
46             | 'sexton' { $return = 16; }
47             | 'femton' { $return = 15; }
48             | 'fjorton' { $return = 14; }
49             | 'tretton' { $return = 13; }
50             | 'tolv' { $return = 12; }
51             | 'elva' { $return = 11; }
52             | 'tio' { $return = 10; }
53             | 'nio' { $return = 9; }
54             | 'åtta' { $return = 8; }
55             | 'sju' { $return = 7; }
56             | 'sex' { $return = 6; }
57             | 'fem' { $return = 5; }
58             | 'fyra' { $return = 4; }
59             | 'tre' { $return = 3; }
60             | 'två' { $return = 2; }
61             | 'ett' { $return = 1; }
62             | 'noll' { $return = 0; }
63              
64             tens: 'tjugo' { $return = 20; } # try to find a word that represents
65             | 'trettio' { $return = 30; } # values 20,30,..,90
66             | 'fyrtio' { $return = 40; }
67             | 'femtio' { $return = 50; }
68             | 'sextio' { $return = 60; }
69             | 'sjutio' { $return = 70; }
70             | 'åttio' { $return = 80; }
71             | 'nittio' { $return = 90; }
72              
73             decade: tens(?) number(?) # try to find words that represents values
74             { $return = 0; # from 0 to 99
75             for (@item) {
76             $return += $$_[0] if (ref $_ && defined $$_[0]);
77             }
78             $return = undef if(!$return);
79             }
80             century: number(?) 'hundra' decade(?) # try to find words that represents values
81             { $return = 0; # from 100 to 999
82             for (@item) {
83             if (ref $_ && defined $$_[0]) {
84             $return += $$_[0];
85             } elsif ($_ eq 'hundra') {
86             $return = ($return>0) ? $return * 100 : 100;
87             }
88             }
89             $return = undef if(!$return);
90             }
91             millenium: century(?) decade(?) 'tusen' century(?) decade(?) # try to find words that represents values
92             { $return = 0; # from 1.000 to 999.999
93             for (@item) {
94             if (ref $_ && defined $$_[0]) {
95             $return += $$_[0];
96             } elsif ($_ eq "tusen") {
97             $return = ($return>0) ? $return * 1000 : 1000;
98             }
99             }
100             $return = undef if(!$return);
101             }
102              
103             million: millenium(?) century(?) decade(?) # try to find words that represents values
104             'miljoner' # from 1.000.000 to 999.999.999.999
105             millenium(?) century(?) decade(?)
106             { $return = 0;
107             for (@item) {
108             if (ref $_ && defined $$_[0]) {
109             $return += $$_[0];
110             } elsif ($_ eq 'miljoner') {
111             $return = $return ? $return * 1000000 : 1000000;
112             }
113             }
114             $return = undef if(!$return);
115             }
116             });
117             }
118              
119             # }}}
120             # {{{ ordinal2cardinal convert ordinal text to cardinal text
121              
122             sub ordinal2cardinal :Export {
123 0   0 0 1   my $input = shift // return;
124              
125             # Swedish ordinal→cardinal: reverse lookup for irregular forms,
126             # suffix stripping for regular/compound forms.
127              
128             # Fully irregular 1-12
129 0           my %irregular = (
130             'första' => 'ett',
131             'andra' => 'två',
132             'tredje' => 'tre',
133             'fjärde' => 'fyra',
134             'femte' => 'fem',
135             'sjätte' => 'sex',
136             'sjunde' => 'sju',
137             'åttonde' => 'åtta',
138             'nionde' => 'nio',
139             'tionde' => 'tio',
140             'elfte' => 'elva',
141             'tolfte' => 'tolv',
142             );
143              
144             # Teens 13-19 (regular -de suffix on cardinal stem)
145 0           my %teens = (
146             'trettonde' => 'tretton',
147             'fjortonde' => 'fjorton',
148             'femtonde' => 'femton',
149             'sextonde' => 'sexton',
150             'sjuttonde' => 'sjutton',
151             'artonde' => 'arton',
152             'nittonde' => 'nitton',
153             );
154              
155             # Tens ordinals (-onde/-ionde suffix)
156 0           my %tens = (
157             'tjugonde' => 'tjugo',
158             'trettionde' => 'trettio',
159             'fyrtionde' => 'fyrtio',
160             'femtionde' => 'femtio',
161             'sextionde' => 'sextio',
162             'sjuttionde' => 'sjutio',
163             'åttionde' => 'åttio',
164             'nittionde' => 'nittio',
165             );
166              
167             # Exact match: standalone ordinals
168 0 0         return $irregular{$input} if exists $irregular{$input};
169 0 0         return $teens{$input} if exists $teens{$input};
170 0 0         return $tens{$input} if exists $tens{$input};
171              
172             # Round hundreds: "hundrade" → "hundra", "tvåhundrade" → "tvåhundra"
173 0 0         $input =~ s{hundrade\z}{hundra}xms and return $input;
174              
175             # Thousands ordinal: "tusende" → "tusen" (e.g. "etttusende" → "etttusen")
176 0 0         $input =~ s{tusende\z}{tusen}xms and return $input;
177              
178             # Compound matching — longest suffix first to avoid partial matches.
179             # E.g. "sjuttionde" (10 chars) must match before "tionde" (6 chars).
180             # Merge all suffix maps, sort by key length descending.
181 0           my @all_suffixes;
182 0           push @all_suffixes, map { [ $_, $tens{$_} ] } keys %tens;
  0            
183 0           push @all_suffixes, map { [ $_, $teens{$_} ] } keys %teens;
  0            
184 0           push @all_suffixes, map { [ $_, $irregular{$_} ] } keys %irregular;
  0            
185              
186 0           for my $pair (sort { length $b->[0] <=> length $a->[0] } @all_suffixes) {
  0            
187 0           my ($ord, $cardinal) = @$pair;
188 0 0         if ($input =~ m{\A(.+)\Q$ord\E\z}xms) {
189 0           return $1 . $cardinal;
190             }
191             }
192              
193 0           return; # not an ordinal
194 1     1   745 }
  1         2  
  1         11  
195              
196             # }}}
197              
198             1;
199              
200             __END__