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   984 use strict;
  1         3  
  1         38  
51 1     1   6 use XAO::Utils;
  1         3  
  1         72  
52 1     1   5 use XAO::Objects;
  1         2  
  1         22  
53 1     1   6 use Error qw(:try);
  1         5  
  1         7  
54 1     1   319 use base XAO::Objects->load(objname => 'Web::Page');
  1         2  
  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 3 my $a=shift;
62 1         3 foreach my $b (@_) {
63 1 50       3 $a=$b if $a>$b;
64             }
65 1         7 return $a;
66             }
67              
68             sub max (@) {
69 1     1 0 3 my $a=shift;
70 1         3 foreach my $b (@_) {
71 2 100       6 $a=$b if $a<$b;
72             }
73 1         16 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 27 my $self=shift;
98 18         33 my $args=get_args(\@_);
99              
100 18   33     163 my $formula=$args->{'formula'} ||
101             throw $self "- need a formula";
102              
103 18 100       41 my $default=defined $args->{'default'} ? $args->{'default'} : '';
104              
105 18         28 my $format=$args->{'format'};
106              
107 18         40 my $result;
108             my $error;
109 18         0 my $errcode;
110              
111             try {
112 18     18   511 my @fparts=split(/(\{\w+\})/,$formula);
113              
114 18         36 foreach my $part (@fparts) {
115 76 100       151 if($part =~ /^\{(\w+)\}$/) {
116 31   50     91 my $value=$args->{'value.'.$1} || 0;
117 31         67 $value=~s/[\s\$\,_]//g;
118 31 50       91 $value =~ /^([\d\.\+e-]+)$/ ||
119             throw $self "- {{VALUE: Illegal value for '$part'}}";
120 31         54 $part=$value;
121             }
122             else {
123 45 50       125 $part=~/^[\s\w\(\)\.\+\*\/,-]*$/ ||
124             throw $self "- {{FORMULA: Illegal formula part '$part'}}";
125              
126 45 100       104 if($part=~/(\w+)\s*\(/) {
127 8 100       35 $functions{$1} ||
128             throw $self "- {{FUNCTION: Illegal function '$1'}}";
129             }
130             }
131             }
132              
133             ### dprint ".'$formula'";
134              
135 16         45 $formula=join('',@fparts);
136              
137             ### dprint "..->'$formula'";
138              
139 16         898 $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       37 if($format) {
148 4         75 $result=sprintf($format,$result);
149             ### dprint "....=$result (formatted)";
150             }
151             }
152             otherwise {
153 6     6   1645 my $e=shift;
154 6         14 my $etext="$e";
155              
156 6 50       115 if($etext=~/\{\{(\w+):\s*(.*?)\s*\}\}/) {
157 6         16 $errcode=$1;
158 6         11 $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         130 };
169              
170 18 100 66     429 if($args->{'path'} || $args->{'template'}) {
171 4   50     16 $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         41 $self->textout($result);
180             }
181             }
182              
183             ###############################################################################
184             1;
185             __END__