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);
102  $math_tokens = ilDclTokenizer::getMathTokens($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());
179  $functions = self::getFunctions();
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  {
324  $operators = self::$operators;
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 }
Class ilDclBaseFieldModel.
Class ilDclExpressionParser.
parse()
Parse expression and return result.
$result
calculate($operator, $left, $right)
global $DIC
Definition: saml.php:7
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...
__construct($expression, ilDclBaseRecordModel $record, ilDclBaseFieldModel $field)
$index
Definition: metadata.php:60
static getTableCache($table_id=0)
Class ilDclStack.
parseMath(array $tokens)
Parse a math expression.
isMathToken($token)
Check if a given token is a math expression.
calculateFunctions($token)
Execute any math functions inside a token.
$lng
static getTokens($expression)
Split expression by & (ignore escaped &-symbols with backslash)
static getMathTokens($math_expression)
Generate tokens for a math expression.
Class ilDclBaseRecordModel.
$i
Definition: disco.tpl.php:19
if(empty($password)) $table
Definition: pwgen.php:24
calculateFunction($function, array $args=array())
Calculate a function with its arguments.
$data
Definition: bench.php:6