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   962 use strict;
  1         2  
  1         32  
51 1     1   4 use XAO::Utils;
  1         2  
  1         75  
52 1     1   5 use XAO::Objects;
  1         2  
  1         23  
53 1     1   4 use Error qw(:try);
  1         2  
  1         8  
54 1     1   294 use base XAO::Objects->load(objname => 'Web::Page');
  1         3  
  1         5  
55              
56             ###############################################################################
57              
58             # Some useful functions that are not a part of standard perl
59              
60             sub min (@) {
61 1     1 0 4 my $a=shift;
62 1         4 foreach my $b (@_) {
63 1 50       5 $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         6 foreach my $b (@_) {
71 2 100       7 $a=$b if $a<$b;
72             }
73 1         7 return $a;
74             }
75              
76             sub sum (@) {
77 1     1 0 3 my $a=0;
78 1         3 foreach my $b (@_) {
79 3         5 $a+=$b;
80             }
81 1         7 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 22 my $self=shift;
98 18         33 my $args=get_args(\@_);
99              
100 18   33     136 my $formula=$args->{'formula'} ||
101             throw $self "- need a formula";
102              
103 18 100       34 my $default=defined $args->{'default'} ? $args->{'default'} : '';
104              
105 18         23 my $format=$args->{'format'};
106              
107 18         37 my $result;
108             my $error;
109 18         0 my $errcode;
110              
111             try {
112 18     18   517 my @fparts=split(/(\{\w+\})/,$formula);
113              
114 18         35 foreach my $part (@fparts) {
115 76 100       134 if($part =~ /^\{(\w+)\}$/) {
116 31   50     81 my $value=$args->{'value.'.$1} || 0;
117 31         60 $value=~s/[\s\$\,_]//g;
118 31 50       74 $value =~ /^([\d\.\+e-]+)$/ ||
119             throw $self "- {{VALUE: Illegal value for '$part'}}";
120 31         49 $part=$value;
121             }
122             else {
123 45 50       113 $part=~/^[\s\w\(\)\.\+\*\/,-]*$/ ||
124             throw $self "- {{FORMULA: Illegal formula part '$part'}}";
125              
126 45 100       95 if($part=~/(\w+)\s*\(/) {
127 8 100       34 $functions{$1} ||
128             throw $self "- {{FUNCTION: Illegal function '$1'}}";
129             }
130             }
131             }
132              
133             ### dprint ".'$formula'";
134              
135 16         44 $formula=join('',@fparts);
136              
137             ### dprint "..->'$formula'";
138              
139 16         841 $result=eval '0.0+('.$formula.')';
140              
141             ### dprint "....=",$result;
142              
143 16 100       81 $@ && throw $self "- {{CALCULATE: Unable to calculate '$formula'}} ($@)";
144              
145             # Formatting if necessary
146             #
147 12 100       30 if($format) {
148 4         61 $result=sprintf($format,$result);
149             ### dprint "....=$result (formatted)";
150             }
151             }
152             otherwise {
153 6     6   1454 my $e=shift;
154 6         14 my $etext="$e";
155              
156 6 50       111 if($etext=~/\{\{(\w+):\s*(.*?)\s*\}\}/) {
157 6         12 $errcode=$1;
158 6         12 $error=$2;
159             }
160             else {
161 0         0 $errcode='SYSTEM';
162 0         0 $error=$etext;
163             }
164              
165 6         9 $result=$default;
166              
167 6         25 dprint "Math error in '$formula': $error ($errcode)";
168 18         116 };
169              
170 18 100 66     357 if($args->{'path'} || $args->{'template'}) {
171 4   50     18 $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         43 $self->textout($result);
180             }
181             }
182              
183             ###############################################################################
184             1;
185             __END__