File Coverage

blib/lib/XAO/DO/Web/Math.pm
Criterion Covered Total %
statement 60 62 96.7
branch 20 24 83.3
condition 6 13 46.1
subroutine 11 11 100.0
pod 1 4 25.0
total 98 114 85.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::DO::Web::Math - calculate and output a value
4              
5             =head1 SYNOPSIS
6              
7             <%Math formula='{x}+{y}' value.x='2' value.y='3'%>
8              
9             =head1 DESCRIPTION
10              
11             Given a formula and some values calculates the result and displays it
12             optionally formatting according to the given format:
13              
14             <%Math formula='{x}+{y}' value.x='2' value.y='3'%>
15             -- output '5'
16              
17             <%Math formula='1/{x}' value.x='3' format='%.3'%>
18             -- output '0.333'
19              
20             <%Math formula='1 / ({a} - {b})' value.a='7' value.b='7' default='-'%>
21             -- output '-'
22              
23             Formulas should not be received from untrusted sources. They are
24             'eval'ed to calculate the result. Some care is taken to avoid illegal
25             formulas, but there are no guarantees.
26              
27             When an operation cannot be performed (division by zero for instance)
28             the result is 'default' value or empty if not set. Illegal arguments,
29             such as non-numeric, produce the same result.
30              
31             If a 'path' or 'template' is given then the result is shown in that
32             template with the following parameters:
33              
34             FORMULA => formula with substituted values, as calculated
35             RESULT => calculation result
36             ERROR => error message if calculation could not be performed
37             ERRCODE => more concise error code (FORMULA, VALUE, FUNCTION, CALCULATE)
38              
39             Some mathematical functions are also supported: min(), max(), sum(),
40             abs(), and sqrt(). The first three work on any number of arguments.
41              
42             =head1 METHODS
43              
44             =over
45              
46             =cut
47              
48             ###############################################################################
49             package XAO::DO::Web::Math;
50 1     1   769 use strict;
  1         2  
  1         30  
51 1     1   5 use XAO::Utils;
  1         2  
  1         52  
52 1     1   5 use XAO::Objects;
  1         2  
  1         21  
53 1     1   4 use Error qw(:try);
  1         3  
  1         14  
54 1     1   295 use base XAO::Objects->load(objname => 'Web::Page');
  1         2  
  1         4  
55              
56             ###############################################################################
57              
58             # Some useful functions that are not a part of standard perl
59              
60             sub min (@) {
61 1     1 0 2 my $a=shift;
62 1         3 foreach my $b (@_) {
63 1 50       4 $a=$b if $a>$b;
64             }
65 1         7 return $a;
66             }
67              
68             sub max (@) {
69 1     1 0 2 my $a=shift;
70 1         3 foreach my $b (@_) {
71 2 100       6 $a=$b if $a<$b;
72             }
73 1         8 return $a;
74             }
75              
76             sub sum (@) {
77 1     1 0 2 my $a=0;
78 1         2 foreach my $b (@_) {
79 3         5 $a+=$b;
80             }
81 1         8 return $a;
82             }
83              
84             ###############################################################################
85              
86             my %functions=map { $_ => 1 } qw(
87             min
88             max
89             sum
90             abs
91             sqrt
92             );
93              
94             ###############################################################################
95              
96             sub display ($%) {
97 18     18 1 23 my $self=shift;
98 18         36 my $args=get_args(\@_);
99              
100 18   33     202 my $formula=$args->{'formula'} ||
101             throw $self "- need a formula";
102              
103 18 100       75 my $default=defined $args->{'default'} ? $args->{'default'} : '';
104              
105 18         26 my $format=$args->{'format'};
106              
107 18         39 my $result;
108             my $error;
109 18         0 my $errcode;
110              
111             try {
112 18     18   530 my @fparts=split(/(\{\w+\})/,$formula);
113              
114 18         40 foreach my $part (@fparts) {
115 76 100       154 if($part =~ /^\{(\w+)\}$/) {
116 31   50     95 my $value=$args->{'value.'.$1} || 0;
117 31         61 $value=~s/[\s\$\,_]//g;
118 31 50       90 $value =~ /^([\d\.\+e-]+)$/ ||
119             throw $self "- {{VALUE: Illegal value for '$part'}}";
120 31         54 $part=$value;
121             }
122             else {
123 45 50       117 $part=~/^[\s\w\(\)\.\+\*\/,-]*$/ ||
124             throw $self "- {{FORMULA: Illegal formula part '$part'}}";
125              
126 45 100       108 if($part=~/(\w+)\s*\(/) {
127 8 100       33 $functions{$1} ||
128             throw $self "- {{FUNCTION: Illegal function '$1'}}";
129             }
130             }
131             }
132              
133             ### dprint ".'$formula'";
134              
135 16         47 $formula=join('',@fparts);
136              
137             ### dprint "..->'$formula'";
138              
139 16         859 $result=eval '0.0+('.$formula.')';
140              
141             ### dprint "....=",$result;
142              
143 16 100       82 $@ && throw $self "- {{CALCULATE: Unable to calculate '$formula'}} ($@)";
144              
145             # Formatting if necessary
146             #
147 12 100       34 if($format) {
148 4         59 $result=sprintf($format,$result);
149             ### dprint "....=$result (formatted)";
150             }
151             }
152             otherwise {
153 6     6   1491 my $e=shift;
154 6         15 my $etext="$e";
155              
156 6 50       128 if($etext=~/\{\{(\w+):\s*(.*?)\s*\}\}/) {
157 6         19 $errcode=$1;
158 6         10 $error=$2;
159             }
160             else {
161 0         0 $errcode='SYSTEM';
162 0         0 $error=$etext;
163             }
164              
165 6         10 $result=$default;
166              
167 6         26 dprint "Math error in '$formula': $error ($errcode)";
168 18         120 };
169              
170 18 100 66     418 if($args->{'path'} || $args->{'template'}) {
171 4   50     15 $self->object->display($args,{
      33        
172             FORMULA => $formula,
173             RESULT => $result,
174             ERROR => $error || '',
175             ERRCODE => $errcode || ($error ? 'UNKNOWN' : ''),
176             });
177             }
178             else {
179 14         46 $self->textout($result);
180             }
181             }
182              
183             ###############################################################################
184             1;
185             __END__