File Coverage

blib/lib/EB/Format.pm
Criterion Covered Total %
statement 119 199 59.8
branch 45 114 39.4
condition 16 45 35.5
subroutine 10 22 45.4
pod 0 17 0.0
total 190 397 47.8


line stmt bran cond sub pod time code
1             #! perl
2              
3             # Format.pm --
4             # Author : Johan Vromans
5             # Created On : Thu Jul 14 12:54:08 2005
6             # Last Modified By: Johan Vromans
7             # Last Modified On: Tue Mar 8 20:26:23 2011
8             # Update Count : 102
9             # Status : Unknown, Use with caution!
10              
11             package main;
12              
13             our $cfg;
14             our $dbh;
15              
16             package EB::Format;
17              
18 6     6   1038 use strict;
  6         11  
  6         150  
19              
20 6     6   28 use EB;
  6         10  
  6         1341  
21              
22 6     6   35 use base qw(Exporter);
  6         12  
  6         813  
23              
24             my $stdfmt0;
25             my $stdfmtw;
26             my $btwfmt0;
27             my $btwfmtw;
28             my $numpat;
29             my $btwpat;
30             my $decimalpt;
31             my $thousandsep;
32              
33             our @EXPORT;
34             our $amount_width;
35             our $date_width;
36              
37             sub numround_ieee {
38             # This somethimes does odd things.
39             # E.g. 892,5 -> 892 and 891,5 -> 892.
40 0     0 0 0 0 + sprintf("%.0f", $_[0]);
41             }
42              
43 6     6   1845 use POSIX qw(floor ceil);
  6         18006  
  6         34  
44              
45             sub numround_posix {
46 0     0 0 0 my ($val) = @_;
47 0 0       0 if ( $val < 0 ) {
48 0         0 ceil($val - 0.5);
49             }
50             else {
51 0         0 floor($val + 0.5);
52             }
53             }
54              
55 6     6   5064 use POSIX qw(floor);
  6         12  
  6         25  
56              
57             my $_half;
58              
59             sub numround_bankers {
60              
61             # Based on Math::Round::round_even.
62              
63 0     0 0 0 my $x = shift;
64 0 0       0 return 0 unless $x;
65              
66 0 0       0 my $sign = ($x >= 0) ? 1 : -1;
67 0         0 $x = abs($x);
68 0         0 my $in = int($x);
69              
70             # Round to next even if exactly 0.5.
71 0 0       0 if ( ($x - $in) == 0.5 ) {
72 0 0       0 return $sign * (($in % 2 == 0) ? $in : $in + 1);
73             }
74              
75 0 0       0 unless ( defined($_half) ) {
76              
77             # Determine what value to use for "one-half". Because of the
78             # perversities of floating-point hardware, we must use a value
79             # slightly larger than 1/2. We accomplish this by determining
80             # the bit value of 0.5 and increasing it by a small amount in
81             # a lower-order byte. Since the lowest-order bits are still
82             # zero, the number is mathematically exact.
83              
84 0         0 my $halfhex = unpack('H*', pack('d', 0.5));
85 0 0 0     0 if ( substr($halfhex,0,2) ne '00' && substr($halfhex, -2) eq '00' ) {
86             # Big-endian.
87 0         0 substr($halfhex, -4) = '1000';
88             } else {
89             # Little-endian.
90 0         0 substr($halfhex, 0, 4) = '0010';
91             }
92 0         0 $_half = unpack('d', pack('H*', $halfhex));
93             }
94              
95 0         0 $sign * POSIX::floor($x + $_half);
96             }
97              
98             sub init_formats {
99              
100 6     6 0 32 assert( NUMGROUPS != AMTPRECISION, "NUMGROUPS != AMTPRECISION" );
101              
102             ################ BTW display format ################
103              
104 6         14 $btwfmt0 = '%.' . (BTWPRECISION-2) . 'f';
105 6         13 $btwfmtw = '%' . BTWWIDTH . "." . (BTWPRECISION-2) . 'f';
106 6         14 $btwpat = qr/^([-+])?(\d+)?(?:[.,])?(\d{1,@{[BTWPRECISION-2]}})?$/;
  6         246  
107              
108             ################ Amount display format ################
109              
110 6         42 $amount_width = $cfg->val(qw(text numwidth), AMTWIDTH);
111 6 50       69 if ( $amount_width =~ /^\+(\d+)$/ ) {
    50          
    50          
    50          
112 0         0 $amount_width = AMTWIDTH + $1;
113             }
114             elsif ( $amount_width =~ /^\-(\d+)$/ ) {
115 0         0 $amount_width = AMTWIDTH - $1;
116             }
117             elsif ( $amount_width =~ /^(\d+)%$/ ) {
118 0         0 $amount_width = int((AMTWIDTH * $1) / 100);
119             }
120             elsif ( $amount_width !~ /^\d+$/ ) {
121 0         0 warn("?"._T("Configuratiefout: [format]numwidth moet een getal zijn")."\n");
122 0         0 $amount_width = AMTWIDTH;
123             }
124              
125 6         21 $decimalpt = $cfg->val(qw(locale decimalpt), undef);
126 6         21 $thousandsep = $cfg->val(qw(locale thousandsep), undef);
127              
128 6         20 my $fmt = $cfg->val(qw(format amount), undef);
129 6 50 33     33 if ( $fmt || !defined($decimalpt) ) {
130 6 50       29 $fmt = _T("1.234,56") unless defined $fmt;
131 6 50 33     69 Carp::croak(__x("Configuratiefout: ongeldige waarde voor {item}",
      33        
132             item => "format".':'."amount")."\n")
133             unless $fmt =~ /^\d+([.,])\d\d$/
134             || $fmt =~ /^\d+(\.\d\d\d)*(\,)\d\d$/
135             || $fmt =~ /^\d+(\,\d\d\d)*(\.)\d\d$/;
136 6 50       24 if ( defined $2 ) {
137 6         17 $decimalpt = $2;
138 6         20 $thousandsep = substr($1, 0, 1);
139             }
140             else {
141 0         0 $decimalpt = $1;
142 0         0 $thousandsep = "";
143             }
144 6 50       19 $amount_width = length($fmt) if length($fmt) > $amount_width;
145             }
146             else {
147 0 0       0 $amount_width += int(($amount_width - AMTPRECISION - 2) / 3) if $thousandsep;
148             }
149              
150 6         13 $stdfmt0 = '%.' . AMTPRECISION . 'f';
151 6         17 $stdfmtw = '%' . $amount_width . "." . AMTPRECISION . 'f';
152              
153 6         10 my $sub = "";
154              
155 6         12 $sub .= <
156             my \$v = shift;
157             if ( \$v == int(\$v) && \$v >= 0 ) {
158 6         26 \$v = ("0" x (@{[AMTPRECISION + 1]} - length(\$v))) . \$v if length(\$v) <= @{[AMTPRECISION]};
  6         19  
159 6         20 substr(\$v, length(\$v) - @{[AMTPRECISION]}, 0) = q\000$decimalpt\000;
160             }
161             else {
162 6         22 \$v = sprintf("$stdfmt0", \$v/@{[AMTSCALE]});
163             EOD
164 6 50       29 $sub .= <
165             \$v =~ s/\\./$decimalpt/;
166             EOD
167 6         12 $sub .= <
168             }
169             \$v =~ s/^\\+//;
170             EOD
171              
172 6 0 0 0 0 676 eval("sub numfmt_plain { $sub; \$v }");
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
173 6 50       30 die($@) if $@;
174              
175 6 50       20 $sub .= <
176             \$v = reverse(\$v);
177 6         34 \$v =~ s/(\\d\\d\\d)(?=\\d)(?!\\d*@{[quotemeta($decimalpt)]})/\${1}$thousandsep/g;
178             \$v = scalar(reverse(\$v));
179             EOD
180              
181 6 0 0 0 0 878 eval("sub numfmt { $sub; \$v }");
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
182 6 50       34 die($@) if $@;
183              
184 6         13 $numpat = qr/^([-+])?(\d+)?(?:[.,])?(\d{1,@{[AMTPRECISION]}})?$/;
  6         192  
185              
186             ################ Rounding Algorithms ################
187              
188 6         55 my $numround = lc($cfg->val(qw(strategy round), "ieee"));
189 6 50       14 unless ( defined &{"numround_$numround"} ) {
  6         25  
190 0         0 die("?".__x("Onbekende afrondingsmethode: {meth}",
191             meth => $numround)."\n");
192             }
193 6         12 *numround = \&{"numround_$numround"};
  6         25  
194              
195             ################ Date display format ################
196              
197 6         22 $fmt = $cfg->val(qw(format date), "YYYY-MM-DD");
198              
199 6         13 $sub = "sub datefmt { \$_[0] }";
200 6         14 my $sub_full = "sub datefmt_full { \$_[0] }";
201 6         9 my $sub_plain = "sub datefmt_plain { \$_[0] }";
202 6 50       38 if ( lc($fmt) eq "dd-mm-yyyy" ) {
    50          
    50          
203 0         0 $sub = q;
204 0         0 $sub_full = q;
205             }
206             elsif ( lc($fmt) eq "dd-mm" ) {
207 0         0 $sub = q;
208 0         0 $sub_full = q;
209             }
210             elsif ( lc($fmt) ne "yyyy-mm-dd" ) {
211 0         0 die("?".__x("Ongeldige datumformaatspecificatie: {fmt}",
212             fmt => $fmt)."\n");
213             }
214 6         14 for ( $sub, $sub_full, $sub_plain ) {
215 18     6 0 562 eval($_);
  6     0 0 27  
  0     0 0 0  
  0         0  
216 18 50       76 die($_."\n".$@) if $@;
217             }
218 6         157 $date_width = length(datefmt("2006-01-01"));
219             }
220              
221             sub numxform_strict {
222 14     14 0 24 $_ = shift;
223 14         34 my $err = __x("Ongeldig bedrag: {num}", num => $_);
224              
225 14         27 my $sign = "";
226 14 50 33     46 $sign = $1 if s/^([-+])// && $1 eq '-';
227              
228             # NNNN -> NNNN.00
229 14 100       48 if ( /^\d+$/ ) {
230 1         2 s/^0+(\d)$/$1/;
231 1         5 return $sign . $_ . "." . ("0" x AMTPRECISION);
232             }
233              
234             # N,NNN -> NNNN.00
235 13 50 66     20 if ( /^(\d{1,@{[NUMGROUPS]}})(\,\d{@{[NUMGROUPS]}})*$/ && $1 ) {
  13         27  
  13         95  
236 1         3 s/\,//g;
237 1         3 s/^0+(\d)$/$1/;
238 1         5 return $sign . $_ . "." . ("0" x AMTPRECISION);
239             }
240              
241             # N.NNN -> NNNN.00
242 12 50 66     29 if ( /^(\d{1,@{[NUMGROUPS]}})(\.\d{@{[NUMGROUPS]}})*$/ && $1 ) {
  12         20  
  12         66  
243 1         4 s/\.//g;
244 1         2 s/^0+(\d)$/$1/;
245 1         6 return $sign . $_ . "." . ("0" x AMTPRECISION);
246             }
247              
248             # N.NNN,NN or N,NNN.NN
249 11 100 100     22 return $err
250 11         79 unless /^([\d.]+)(\,)(\d{@{[AMTPRECISION]}})$/
251 9         88 || /^([\d,]+)(\.)(\d{@{[AMTPRECISION]}})$/;
252              
253 4         18 my ($mant, $sep, $frac) = ( $1, $2, $3 );
254              
255             # N.NNN , NN -> NNNN NN
256 4 100       13 if ( $sep eq "," ) {
257 2 100       3 $mant =~ s/\.//g
258 2         5 if $mant =~ /^\d{1,@{[NUMGROUPS]}}(\.\d{@{[NUMGROUPS]}})*$/;
  2         29  
259             }
260              
261             # N,NNN . NN -> NNNN NN
262             else {
263 2 100       5 $mant =~ s/\,//g
264 2         4 if $mant =~ /^\d{1,@{[NUMGROUPS]}}(\,\d{@{[NUMGROUPS]}})*$/;
  2         38  
265             }
266              
267             # NNNN NN -> NNNN.NN
268 4         13 $mant =~ s/^0+(\d)$/$1/;
269 4 50       28 return $sign . $mant . "." . $frac if $mant =~ /^\d+$/;
270              
271 0         0 die("?$err\n"); # not well-formed
272              
273             }
274              
275             sub numxform_loose {
276 20     20 0 39 $_ = shift;
277 20         46 my $err = __x("Ongeldig getal: {num}", num => $_);
278              
279             # If there's a single comma, make decimal point.
280 20 100       66 s/,/./ if /^.*,.*$/;
281              
282 20 100       93 return $_ if /^[-+]*\d+(\.\d+)?$/;
283              
284 1         7 die("?$err\n"); # not well-formed
285              
286             }
287              
288             sub numxform {
289 0     0 0 0 my ($n) = @_;
290 0         0 my $res = numxform_strict($n);
291 0 0       0 return $res if defined $res;
292             # return $n if $n =~ /^[-a+]?\d+[.,]\d+$/; # a ?
293 0 0       0 return $n if $n =~ /^[-+]?\d+[.,]\d+$/;
294 0         0 return undef;
295             }
296              
297             sub amount($) {
298 23     23 0 11432 my $val = shift;
299 23         80 my $debug = $cfg->val(__PACKAGE__, "debugexpr", 0);
300 23 100       78 if ( $val =~ /.[-+*\/\(\)]/ ) {
301 9 50       22 print STDERR ("val \"$val\" -> ") if $debug;
302 9         35 $val =~ s/([.,\d]+)/numxform_loose($1)/ge;
  20         41  
303 8 50       21 print STDERR ("\"$val\" -> ") if $debug;
304              
305 8         330 my $res = eval($val);
306 8 0 33     33 warn("$val: $@"), return undef if $debug && $@;
307 8 50       18 return undef if $@;
308 8         51 $val = sprintf($stdfmt0, $res);
309 8 50       22 print STDERR ("$val\n") if $debug;
310             }
311             else {
312             return undef
313 14 50       58 unless $val = numxform_strict($val); # fortunately, 0.00 is true
314             }
315              
316 22 100       137 return undef unless $val =~ $numpat;
317 15   50     139 my ($s, $w, $f) = ($1 || "", $2 || 0, $3 || 0);
      100        
      50        
318 15         42 $f .= "0" x (AMTPRECISION - length($f));
319 15         52 return 0 + ($s.$w.$f);
320             }
321              
322             sub numfmtw {
323 0     0 0   my $v = shift;
324 0 0 0       if ( $v == int($v) && $v >= 0 ) {
325 0 0         $v = ("0" x (AMTPRECISION - length($v) + 1)) . $v if length($v) <= AMTPRECISION;
326 0 0         $v = (" " x (AMTWIDTH - length($v))) . $v if length($v) < AMTWIDTH;
327 0           substr($v, length($v) - AMTPRECISION, 0) = $decimalpt;
328             }
329             else {
330 0           $v = sprintf($stdfmtw, $v/AMTSCALE);
331 0           $v =~ s/\./$decimalpt/;
332             }
333 0           $v;
334             }
335              
336             #### UNUSED
337             sub numfmtv {
338 0     0 0   my $v = shift;
339 0 0 0       if ( $v == int($v) && $v >= 0 ) {
340 0 0         $v = ("0" x (AMTPRECISION - length($v) + 1)) . $v if length($v) <= AMTPRECISION;
341 0 0         $v = (" " x ($_[0] - length($v))) . $v if length($v) < $_[0];
342 0           substr($v, length($v) - AMTPRECISION, 0) = $decimalpt;
343             }
344             else {
345 0           $v = sprintf('%'.$_[0].'.'.AMTPRECISION.'f', $v/AMTSCALE);
346 0           $v =~ s/\./$decimalpt/;
347             }
348 0           $v;
349             }
350              
351             sub btwfmt {
352 0     0 0   my $v = sprintf($btwfmt0, 100*$_[0]/BTWSCALE);
353 0           $v =~ s/\./$decimalpt/;
354 0           $v;
355             }
356              
357 0     0 0   sub btwpat { $btwpat }
358              
359             ################ Code ################
360              
361             push( @EXPORT,
362             qw(amount numround btwfmt),
363             qw($amount_width numfmt numfmt_plain),
364             qw($date_width datefmt datefmt_full datefmt_plain),
365             );
366              
367             1;