ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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
30 = array(
31 '+' => array('precedence' => 1),
32 '-' => array('precedence' => 1),
33 '*' => array('precedence' => 2),
34 '/' => array('precedence' => 2),
35 '^' => array('precedence' => 3),
36 );
40 protected static $cache_tokens = array();
44 protected static $cache_fields = array();
48 protected static $cache_math_tokens = array();
52 protected static $cache_math_function_tokens = array();
56 protected static $functions
57 = array(
58 'SUM',
59 'AVERAGE',
60 'MIN',
61 'MAX',
62 );
63
64
71 {
72 $this->expression = $expression;
73 $this->record = $record;
74 $this->field = $field;
75 }
76
77
86 public function parse()
87 {
88 if (isset(self::$cache_tokens[$this->field->getId()])) {
89 $tokens = self::$cache_tokens[$this->field->getId()];
90 } else {
91 $tokens = ilDclTokenizer::getTokens($this->expression);
92 self::$cache_tokens[$this->field->getId()] = $tokens;
93 }
94 // ilUtil::sendInfo( "<pre>" . print_r($tokens, 1) . "</pre>");
95 $parsed = '';
96 foreach ($tokens as $token) {
97 if (empty($token)) {
98 continue;
99 }
100 if ($this->isMathToken($token)) {
101 $token = $this->calculateFunctions($token);
103 $value = $this->parseMath($this->substituteFieldValues($math_tokens));
104
105 $value = $this->formatScientific($value);
106
107 $parsed .= $value;
108 } else {
109 // Token is a string, either a field placeholder [[Field name]] or a string starting with "
110 if (strpos($token, '"') === 0) {
111 $parsed .= strip_tags(trim($token, '"'));
112 } elseif (strpos($token, '[[') === 0) {
113 $parsed .= trim(strip_tags($this->substituteFieldValue($token)));
114 } else {
115 throw new ilException("Unrecognized string token: '$token'");
116 }
117 }
118 }
119
120 return $parsed;
121 }
122
123
129 protected function formatScientific($value)
130 {
131 if (abs($value) >= self::SCIENTIFIC_NOTATION_UPPER) {
132 return sprintf("%e", $value);
133 }
134 if (abs($value) <= self::SCIENTIFIC_NOTATION_LOWER && $value != 0) {
135 return sprintf("%e", $value);
136 }
137 if (is_float($value)) {
138 return $value;
139 }
140
141 return $value;
142 }
143
144
148 public static function getOperators()
149 {
150 return self::$operators;
151 }
152
153
157 public static function getFunctions()
158 {
159 return self::$functions;
160 }
161
162
170 protected function isMathToken($token)
171 {
172 if (isset(self::$cache_math_tokens[$this->field->getId()][$token])) {
173 return self::$cache_math_tokens[$this->field->getId()][$token];
174 } else {
175 if (strpos($token, '"') === 0) {
176 return false;
177 }
178 $operators = array_keys(self::getOperators());
180 $result = (bool) preg_match('#(\\' . implode("|\\", $operators) . '|' . implode('|', $functions) . ')#', $token);
181 self::$cache_math_tokens[$this->field->getId()][$token] = $result;
182
183 return $result;
184 }
185 }
186
187
195 protected function calculateFunctions($token)
196 {
197 if (isset(self::$cache_math_function_tokens[$this->field->getId()][$token])) {
198 $result = self::$cache_math_function_tokens[$this->field->getId()][$token];
199 if ($result === false) {
200 return $token;
201 }
202 } else {
203 $pattern = '#';
204 foreach (self::getFunctions() as $function) {
205 $pattern .= "($function)\\(([^)]*)\\)|";
206 }
207 if (!preg_match_all(rtrim($pattern, '|') . '#', $token, $result)) {
208 // No functions found inside token, just return token again
209 self::$cache_math_function_tokens[$this->field->getId()][$token] = false;
210
211 return $token;
212 }
213 }
214 // Function found inside token, calculate!
215 foreach ($result[0] as $k => $to_replace) {
216 $function_args = $this->getFunctionArgs($k, $result);
217 $function = $function_args['function'];
218 $args = $this->substituteFieldValues($function_args['args']);
219 $token = str_replace($to_replace, $this->calculateFunction($function, $args), $token);
220 }
221
222 return $token;
223 }
224
225
234 protected function getFunctionArgs($index, array $data)
235 {
236 $return = array(
237 'function' => '',
238 'args' => array(),
239 );
240 for ($i = 1; $i < count($data); $i++) {
241 $_data = $data[$i];
242 if ($_data[$index]) {
243 $function = $_data[$index];
244 $args = explode(';', $data[$i + 1][$index]);
245 $return['function'] = $function;
246 $return['args'] = $args;
247 break;
248 }
249 }
250
251 return $return;
252 }
253
254
262 protected function substituteFieldValues(array $tokens)
263 {
264 $replaced = array();
265 foreach ($tokens as $token) {
266 if (strpos($token, '[[') === 0) {
267 $replaced[] = $this->substituteFieldValue($token);
268 } else {
269 $replaced[] = $token;
270 }
271 }
272
273 return $replaced;
274 }
275
276
285 protected function substituteFieldValue($placeholder)
286 {
287 if (isset(self::$cache_fields[$placeholder])) {
288 $field = self::$cache_fields[$placeholder];
289 } else {
290 $table = ilDclCache::getTableCache($this->record->getTableId()); // TODO May need caching per table in future
291 $field_title = preg_replace('#^\[\[(.*)\]\]#', "$1", $placeholder);
292 $field = $table->getFieldByTitle($field_title);
293 if ($field === null) {
294 // Workaround for standardfields - title my be ID
295 $field = $table->getField($field_title);
296 if ($field === null) {
297 global $DIC;
298 $lng = $DIC['lng'];
302 $lng->loadLanguageModule('dcl');
303 // throw new ilException("Field with title '$field_title' not found");
304 throw new ilException(sprintf($lng->txt('dcl_err_formula_field_not_found'), $field_title));
305 }
306 }
307 self::$cache_fields[$placeholder] = $field;
308 }
309
310 return $this->record->getRecordFieldFormulaValue($field->getId());
311 }
312
313
322 protected function parseMath(array $tokens)
323 {
325 $precedence = 0;
326 $stack = new ilDclStack();
327 $precedences = new ilDclStack();
328 $in_bracket = false;
329 foreach ($tokens as $token) {
330 if (empty($token) or is_null($token)) {
331 $token = 0;
332 }
333 if (is_numeric($token) or $token === '(') {
334 $stack->push($token);
335 if ($token === '(') {
336 $in_bracket = true;
337 }
338 } elseif (in_array($token, array_keys($operators))) {
339 $new_precedence = $operators[$token]['precedence'];
340 if ($new_precedence > $precedence || $in_bracket) {
341 // Precedence of operator is higher, push operator on stack
342 $stack->push($token);
343 $precedences->push($new_precedence);
344 $precedence = $new_precedence;
345 } else {
346 // Precedence is equal or lower, calculate result on stack
347 while ($new_precedence <= $precedence && $stack->count() > 1) {
348 $right = (float) $stack->pop();
349 $operator = $stack->pop();
350 $left = (float) $stack->pop();
351 $result = $this->calculate($operator, $left, $right);
352 $stack->push($result);
353 $precedence = $precedences->pop();
354 }
355 $stack->push($token);
356 $precedence = $new_precedence;
357 $precedences->push($new_precedence);
358 }
359 } elseif ($token === ')') {
360 // Need to calculate stack back to opening bracket
361 $_tokens = array();
362 $elem = $stack->pop();
363 while ($elem !== '(' && !$stack->isEmpty()) {
364 $_tokens[] = $elem;
365 $elem = $stack->pop();
366 }
367 // Get result within brackets recursive and push to stack
368 $stack->push($this->parseMath(array_reverse($_tokens)));
369 $in_bracket = false;
370 } else {
371 throw new ilException("Unrecognized token '$token'");
372 }
373 // $stack->debug();
374 }
375 // If one element is left on stack, we are done. Otherwise calculate
376 if ($stack->count() == 1) {
377 $result = $stack->pop();
378
379 return (ctype_digit((string) $result)) ? $result : number_format($result, self::N_DECIMALS, '.', "'");
380 } else {
381 while ($stack->count() >= 2) {
382 $right = $stack->pop();
383 $operator = $stack->pop();
384 $left = $stack->count() ? $stack->pop() : 0;
385 $stack->push($this->calculate($operator, $left, $right));
386 }
387 $result = $stack->pop();
388
389 return $result;
390 }
391 }
392
393
403 protected function calculateFunction($function, array $args = array())
404 {
405 switch ($function) {
406 case 'AVERAGE':
407 $count = count($args);
408
409 return ($count) ? array_sum($args) / $count : 0;
410 case 'SUM':
411 return array_sum($args);
412 case 'MIN':
413 return min($args);
414 case 'MAX':
415 return max($args);
416 default:
417 throw new ilException("Unrecognized function '$function'");
418 }
419 }
420
421
430 protected function calculate($operator, $left, $right)
431 {
432 switch ($operator) {
433 case '+':
434 $result = $left + $right;
435 break;
436 case '-':
437 $result = $left - $right;
438 break;
439 case '*':
440 $result = $left * $right;
441 break;
442 case '/':
443 $result = ($right == 0) ? 0 : $left / $right;
444 break;
445 case '^':
446 $result = pow($left, $right);
447 break;
448 default:
449 throw new ilException("Unrecognized operator '$operator'");
450 }
451
452 return $result;
453 }
454}
$result
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
if(empty($password)) $table
Definition: pwgen.php:24
global $DIC
Definition: saml.php:7
$lng
$data
Definition: bench.php:6