ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.ilDclExpressionParser.php
Go to the documentation of this file.
1<?php
2
10{
11 const N_DECIMALS = 1;
12 const SCIENTIFIC_NOTATION_UPPER = 1000000000000;
13 const SCIENTIFIC_NOTATION_LOWER = 0.000000001;
17 protected $record;
21 protected $field;
25 protected $expression;
29 protected static $operators = array(
30 '+' => array( 'precedence' => 1 ),
31 '-' => array( 'precedence' => 1 ),
32 '*' => array( 'precedence' => 2 ),
33 '/' => array( 'precedence' => 2 ),
34 '^' => array( 'precedence' => 3 ),
35 );
39 protected static $cache_tokens = array();
43 protected static $cache_fields = array();
47 protected static $cache_math_tokens = array();
51 protected static $cache_math_function_tokens = array();
55 protected static $functions = array(
56 'SUM',
57 'AVERAGE',
58 'MIN',
59 'MAX',
60 );
61
62
69 {
70 $this->expression = $expression;
71 $this->record = $record;
72 $this->field = $field;
73 }
74
75
84 public function parse()
85 {
86 if (isset(self::$cache_tokens[$this->field->getId()])) {
87 $tokens = self::$cache_tokens[$this->field->getId()];
88 } else {
89 $tokens = ilDclTokenizer::getTokens($this->expression);
90 self::$cache_tokens[$this->field->getId()] = $tokens;
91 }
92 // ilUtil::sendInfo( "<pre>" . print_r($tokens, 1) . "</pre>");
93 $parsed = '';
94 foreach ($tokens as $token) {
95 if (empty($token)) {
96 continue;
97 }
98 if ($this->isMathToken($token)) {
99 $token = $this->calculateFunctions($token);
100 $math_tokens = ilDclTokenizer::getMathTokens($token);
101 $value = $this->parseMath($this->substituteFieldValues($math_tokens));
102
103 $value = $this->formatScientific($value);
104
105 $parsed .= $value;
106 } else {
107 // Token is a string, either a field placeholder [[Field name]] or a string starting with "
108 if (strpos($token, '"') === 0) {
109 $parsed .= strip_tags(trim($token, '"'));
110 } elseif (strpos($token, '[[') === 0) {
111 $parsed .= trim(strip_tags($this->substituteFieldValue($token)));
112 } else {
113 throw new ilException("Unrecognized string token: '$token'");
114 }
115 }
116 }
117
118 return $parsed;
119 }
120
121
127 protected function formatScientific($value)
128 {
129 if (abs($value) >= self::SCIENTIFIC_NOTATION_UPPER) {
130 return sprintf("%e", $value);
131 }
132 if (abs($value) <= self::SCIENTIFIC_NOTATION_LOWER && $value != 0) {
133 return sprintf("%e", $value);
134 }
135 if (is_float($value)) {
136 return $value;
137 }
138
139 return $value;
140 }
141
142
146 public static function getOperators()
147 {
148 return self::$operators;
149 }
150
151
155 public static function getFunctions()
156 {
157 return self::$functions;
158 }
159
160
168 protected function isMathToken($token)
169 {
170 if (isset(self::$cache_math_tokens[$this->field->getId()][$token])) {
171 return self::$cache_math_tokens[$this->field->getId()][$token];
172 } else {
173 if (strpos($token, '"') === 0) {
174 return false;
175 }
176 $operators = array_keys(self::getOperators());
178 $result = (bool) preg_match('#(\\' . implode("|\\", $operators) . '|' . implode('|', $functions) . ')#', $token);
179 self::$cache_math_tokens[$this->field->getId()][$token] = $result;
180
181 return $result;
182 }
183 }
184
185
193 protected function calculateFunctions($token)
194 {
195 if (isset(self::$cache_math_function_tokens[$this->field->getId()][$token])) {
196 $result = self::$cache_math_function_tokens[$this->field->getId()][$token];
197 if ($result === false) {
198 return $token;
199 }
200 } else {
201 $pattern = '#';
202 foreach (self::getFunctions() as $function) {
203 $pattern .= "($function)\\(([^)]*)\\)|";
204 }
205 if (!preg_match_all(rtrim($pattern, '|') . '#', $token, $result)) {
206 // No functions found inside token, just return token again
207 self::$cache_math_function_tokens[$this->field->getId()][$token] = false;
208
209 return $token;
210 }
211 }
212 // Function found inside token, calculate!
213 foreach ($result[0] as $k => $to_replace) {
214 $function_args = $this->getFunctionArgs($k, $result);
215 $function = $function_args['function'];
216 $args = $this->substituteFieldValues($function_args['args']);
217 $token = str_replace($to_replace, $this->calculateFunction($function, $args), $token);
218 }
219
220 return $token;
221 }
222
223
232 protected function getFunctionArgs($index, array $data)
233 {
234 $return = array(
235 'function' => '',
236 'args' => array(),
237 );
238 for ($i = 1; $i < count($data); $i++) {
239 $_data = $data[$i];
240 if ($_data[$index]) {
241 $function = $_data[$index];
242 $args = explode(';', $data[$i + 1][$index]);
243 $return['function'] = $function;
244 $return['args'] = $args;
245 break;
246 }
247 }
248
249 return $return;
250 }
251
252
260 protected function substituteFieldValues(array $tokens)
261 {
262 $replaced = array();
263 foreach ($tokens as $token) {
264 if (strpos($token, '[[') === 0) {
265 $replaced[] = $this->substituteFieldValue($token);
266 } else {
267 $replaced[] = $token;
268 }
269 }
270
271 return $replaced;
272 }
273
274
283 protected function substituteFieldValue($placeholder)
284 {
285 if (isset(self::$cache_fields[$placeholder])) {
286 $field = self::$cache_fields[$placeholder];
287 } else {
288 $table = ilDclCache::getTableCache($this->record->getTableId()); // TODO May need caching per table in future
289 $field_title = preg_replace('#^\[\[(.*)\]\]#', "$1", $placeholder);
290 $field = $table->getFieldByTitle($field_title);
291 if ($field === null) {
292 // Workaround for standardfields - title my be ID
293 $field = $table->getField($field_title);
294 if ($field === null) {
295 global $DIC;
296 $lng = $DIC['lng'];
300 $lng->loadLanguageModule('dcl');
301 // throw new ilException("Field with title '$field_title' not found");
302 throw new ilException(sprintf($lng->txt('dcl_err_formula_field_not_found'), $field_title));
303 }
304 }
305 self::$cache_fields[$placeholder] = $field;
306 }
307
308 return $this->record->getRecordFieldHTML($field->getId());
309 }
310
311
320 protected function parseMath(array $tokens)
321 {
323 $precedence = 0;
324 $stack = new ilDclStack();
325 $precedences = new ilDclStack();
326 $in_bracket = false;
327 foreach ($tokens as $token) {
328 if (empty($token) or is_null($token)) {
329 $token = 0;
330 }
331 if (is_numeric($token) or $token === '(') {
332 $stack->push($token);
333 if ($token === '(') {
334 $in_bracket = true;
335 }
336 } elseif (in_array($token, array_keys($operators))) {
337 $new_precedence = $operators[$token]['precedence'];
338 if ($new_precedence > $precedence || $in_bracket) {
339 // Precedence of operator is higher, push operator on stack
340 $stack->push($token);
341 $precedences->push($new_precedence);
342 $precedence = $new_precedence;
343 } else {
344 // Precedence is equal or lower, calculate result on stack
345 while ($new_precedence <= $precedence && $stack->count() > 1) {
346 $right = (float) $stack->pop();
347 $operator = $stack->pop();
348 $left = (float) $stack->pop();
349 $result = $this->calculate($operator, $left, $right);
350 $stack->push($result);
351 $precedence = $precedences->pop();
352 }
353 $stack->push($token);
354 $precedence = $new_precedence;
355 $precedences->push($new_precedence);
356 }
357 } elseif ($token === ')') {
358 // Need to calculate stack back to opening bracket
359 $_tokens = array();
360 $elem = $stack->pop();
361 while ($elem !== '(' && !$stack->isEmpty()) {
362 $_tokens[] = $elem;
363 $elem = $stack->pop();
364 }
365 // Get result within brackets recursive and push to stack
366 $stack->push($this->parseMath(array_reverse($_tokens)));
367 $in_bracket = false;
368 } else {
369 throw new ilException("Unrecognized token '$token'");
370 }
371 // $stack->debug();
372 }
373 // If one element is left on stack, we are done. Otherwise calculate
374 if ($stack->count() == 1) {
375 $result = $stack->pop();
376
377 return (ctype_digit((string) $result)) ? $result : number_format($result, self::N_DECIMALS, '.', "'");
378 } else {
379 while ($stack->count() >= 2) {
380 $right = $stack->pop();
381 $operator = $stack->pop();
382 $left = $stack->count() ? $stack->pop() : 0;
383 $stack->push($this->calculate($operator, $left, $right));
384 }
385 $result = $stack->pop();
386
387 return $result;
388 }
389 }
390
391
401 protected function calculateFunction($function, array $args = array())
402 {
403 switch ($function) {
404 case 'AVERAGE':
405 $count = count($args);
406
407 return ($count) ? array_sum($args) / $count : 0;
408 case 'SUM':
409 return array_sum($args);
410 case 'MIN':
411 return min($args);
412 case 'MAX':
413 return max($args);
414 default:
415 throw new ilException("Unrecognized function '$function'");
416 }
417 }
418
419
428 protected function calculate($operator, $left, $right)
429 {
430 switch ($operator) {
431 case '+':
432 $result = $left + $right;
433 break;
434 case '-':
435 $result = $left - $right;
436 break;
437 case '*':
438 $result = $left * $right;
439 break;
440 case '/':
441 $result = ($right == 0) ? 0 : $left / $right;
442 break;
443 case '^':
444 $result = pow($left, $right);
445 break;
446 default:
447 throw new ilException("Unrecognized operator '$operator'");
448 }
449
450 return $result;
451 }
452}
sprintf('%.4f', $callTime)
$result
$function
Definition: cas.php:28
An exception for terminatinating execution or to throw for unit testing.
Class ilDclBaseFieldModel.
Class ilDclBaseRecordModel.
static getTableCache($table_id=0)
Class ilDclExpressionParser.
substituteFieldValues(array $tokens)
Given an array of tokens, replace each token that is a placeholder (e.g.
getFunctionArgs($index, array $data)
Helper method to return the function and its arguments from a preg_replace_all $result array.
calculateFunctions($token)
Execute any math functions inside a token.
parseMath(array $tokens)
Parse a math expression.
parse()
Parse expression and return result.
isMathToken($token)
Check if a given token is a math expression.
calculateFunction($function, array $args=array())
Calculate a function with its arguments.
__construct($expression, ilDclBaseRecordModel $record, ilDclBaseFieldModel $field)
calculate($operator, $left, $right)
Class ilDclStack.
static getMathTokens($math_expression)
Generate tokens for a math expression.
static getTokens($expression)
Split expression by & (ignore escaped &-symbols with backslash)
Base class for ILIAS Exception handling.
$i
Definition: disco.tpl.php:19
$index
Definition: metadata.php:60
global $lng
Definition: privfeed.php:17
if(empty($password)) $table
Definition: pwgen.php:24
global $DIC
Definition: saml.php:7