ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
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  $this->expression = $expression;
70  $this->record = $record;
71  $this->field = $field;
72  }
73 
74 
83  public function parse() {
84  if (isset(self::$cache_tokens[$this->field->getId()])) {
85  $tokens = self::$cache_tokens[$this->field->getId()];
86  } else {
87  $tokens = ilDclTokenizer::getTokens($this->expression);
88  self::$cache_tokens[$this->field->getId()] = $tokens;
89  }
90  // ilUtil::sendInfo( "<pre>" . print_r($tokens, 1) . "</pre>");
91  $parsed = '';
92  foreach ($tokens as $token) {
93  if (empty($token)) {
94  continue;
95  }
96  if ($this->isMathToken($token)) {
97  $token = $this->calculateFunctions($token);
98  $math_tokens = ilDclTokenizer::getMathTokens($token);
99  $value = $this->parseMath($this->substituteFieldValues($math_tokens));
100 
101  $value = $this->formatScientific($value);
102 
103  $parsed .= $value;
104  } else {
105  // Token is a string, either a field placeholder [[Field name]] or a string starting with "
106  if (strpos($token, '"') === 0) {
107  $parsed .= strip_tags(trim($token, '"'));
108  } elseif (strpos($token, '[[') === 0) {
109  $parsed .= trim(strip_tags($this->substituteFieldValue($token)));
110  } else {
111  throw new ilException("Unrecognized string token: '$token'");
112  }
113  }
114  }
115 
116  return $parsed;
117  }
118 
119 
125  protected function formatScientific($value) {
126  if (abs($value) >= self::SCIENTIFIC_NOTATION_UPPER) {
127  return sprintf("%e", $value);
128  }
129  if (abs($value) <= self::SCIENTIFIC_NOTATION_LOWER && $value != 0 ) {
130  return sprintf("%e", $value);
131  }
132  if (is_float($value)) {
133  return $value;
134  }
135 
136  return $value;
137  }
138 
139 
143  public static function getOperators() {
144  return self::$operators;
145  }
146 
147 
151  public static function getFunctions() {
152  return self::$functions;
153  }
154 
155 
163  protected function isMathToken($token) {
164  if (isset(self::$cache_math_tokens[$this->field->getId()][$token])) {
165  return self::$cache_math_tokens[$this->field->getId()][$token];
166  } else {
167  if (strpos($token, '"') === 0) {
168  return false;
169  }
170  $operators = array_keys(self::getOperators());
171  $functions = self::getFunctions();
172  $result = (bool)preg_match('#(\\' . implode("|\\", $operators) . '|' . implode('|', $functions) . ')#', $token);
173  self::$cache_math_tokens[$this->field->getId()][$token] = $result;
174 
175  return $result;
176  }
177  }
178 
179 
187  protected function calculateFunctions($token) {
188  if (isset(self::$cache_math_function_tokens[$this->field->getId()][$token])) {
189  $result = self::$cache_math_function_tokens[$this->field->getId()][$token];
190  if ($result === false) {
191  return $token;
192  }
193  } else {
194  $pattern = '#';
195  foreach (self::getFunctions() as $function) {
196  $pattern .= "($function)\\(([^)]*)\\)|";
197  }
198  if (!preg_match_all(rtrim($pattern, '|') . '#', $token, $result)) {
199  // No functions found inside token, just return token again
200  self::$cache_math_function_tokens[$this->field->getId()][$token] = false;
201 
202  return $token;
203  }
204  }
205  // Function found inside token, calculate!
206  foreach ($result[0] as $k => $to_replace) {
207  $function_args = $this->getFunctionArgs($k, $result);
208  $function = $function_args['function'];
209  $args = $this->substituteFieldValues($function_args['args']);
210  $token = str_replace($to_replace, $this->calculateFunction($function, $args), $token);
211  }
212 
213  return $token;
214  }
215 
216 
225  protected function getFunctionArgs($index, array $data) {
226  $return = array(
227  'function' => '',
228  'args' => array(),
229  );
230  for ($i = 1; $i < count($data); $i ++) {
231  $_data = $data[$i];
232  if ($_data[$index]) {
233  $function = $_data[$index];
234  $args = explode(';', $data[$i + 1][$index]);
235  $return['function'] = $function;
236  $return['args'] = $args;
237  break;
238  }
239  }
240 
241  return $return;
242  }
243 
244 
252  protected function substituteFieldValues(array $tokens) {
253  $replaced = array();
254  foreach ($tokens as $token) {
255  if (strpos($token, '[[') === 0) {
256  $replaced[] = $this->substituteFieldValue($token);
257  } else {
258  $replaced[] = $token;
259  }
260  }
261 
262  return $replaced;
263  }
264 
265 
274  protected function substituteFieldValue($placeholder) {
275  if (isset(self::$cache_fields[$placeholder])) {
276  $field = self::$cache_fields[$placeholder];
277  } else {
278  $table = ilDclCache::getTableCache($this->record->getTableId()); // TODO May need caching per table in future
279  $field_title = preg_replace('#^\[\[(.*)\]\]#', "$1", $placeholder);
280  $field = $table->getFieldByTitle($field_title);
281  if ($field === NULL) {
282  // Workaround for standardfields - title my be ID
283  $field = $table->getField($field_title);
284  if ($field === NULL) {
285  global $DIC;
286  $lng = $DIC['lng'];
290  $lng->loadLanguageModule('dcl');
291 // throw new ilException("Field with title '$field_title' not found");
292  throw new ilException(sprintf($lng->txt('dcl_err_formula_field_not_found'), $field_title));
293  }
294  }
295  self::$cache_fields[$placeholder] = $field;
296  }
297 
298  return $this->record->getRecordFieldHTML($field->getId());
299  }
300 
301 
310  protected function parseMath(array $tokens) {
311  $operators = self::$operators;
312  $precedence = 0;
313  $stack = new ilDclStack();
314  $precedences = new ilDclStack();
315  $in_bracket = false;
316  foreach ($tokens as $token) {
317  if (empty($token) OR is_null($token)) {
318  $token = 0;
319  }
320  if (is_numeric($token) OR $token === '(') {
321  $stack->push($token);
322  if ($token === '(') {
323  $in_bracket = true;
324  }
325  } elseif (in_array($token, array_keys($operators))) {
326  $new_precedence = $operators[$token]['precedence'];
327  if ($new_precedence > $precedence || $in_bracket) {
328  // Precedence of operator is higher, push operator on stack
329  $stack->push($token);
330  $precedences->push($new_precedence);
331  $precedence = $new_precedence;
332  } else {
333  // Precedence is equal or lower, calculate result on stack
334  while ($new_precedence <= $precedence && $stack->count() > 1) {
335  $right = (float)$stack->pop();
336  $operator = $stack->pop();
337  $left = (float)$stack->pop();
338  $result = $this->calculate($operator, $left, $right);
339  $stack->push($result);
340  $precedence = $precedences->pop();
341  }
342  $stack->push($token);
343  $precedence = $new_precedence;
344  $precedences->push($new_precedence);
345  }
346  } elseif ($token === ')') {
347  // Need to calculate stack back to opening bracket
348  $_tokens = array();
349  $elem = $stack->pop();
350  while ($elem !== '(' && !$stack->isEmpty()) {
351  $_tokens[] = $elem;
352  $elem = $stack->pop();
353  }
354  // Get result within brackets recursive and push to stack
355  $stack->push($this->parseMath(array_reverse($_tokens)));
356  $in_bracket = false;
357  } else {
358  throw new ilException("Unrecognized token '$token'");
359  }
360  // $stack->debug();
361  }
362  // If one element is left on stack, we are done. Otherwise calculate
363  if ($stack->count() == 1) {
364  $result = $stack->pop();
365 
366  return (ctype_digit((string)$result)) ? $result : number_format($result, self::N_DECIMALS, '.', "'");
367  } else {
368 
369  while ($stack->count() >= 2) {
370  $right = $stack->pop();
371  $operator = $stack->pop();
372  $left = $stack->count() ? $stack->pop() : 0;
373  $stack->push($this->calculate($operator, $left, $right));
374  }
375  $result = $stack->pop();
376 
377  return $result;
378  }
379  }
380 
381 
391  protected function calculateFunction($function, array $args = array()) {
392  switch ($function) {
393  case 'AVERAGE':
394  $count = count($args);
395 
396  return ($count) ? array_sum($args) / $count : 0;
397  case 'SUM':
398  return array_sum($args);
399  case 'MIN':
400  return min($args);
401  case 'MAX':
402  return max($args);
403  default:
404  throw new ilException("Unrecognized function '$function'");
405  }
406  }
407 
408 
417  protected function calculate($operator, $left, $right) {
418  switch ($operator) {
419  case '+':
420  $result = $left + $right;
421  break;
422  case '-':
423  $result = $left - $right;
424  break;
425  case '*':
426  $result = $left * $right;
427  break;
428  case '/':
429  $result = ($right == 0) ? 0 : $left / $right;
430  break;
431  case '^':
432  $result = pow($left, $right);
433  break;
434  default:
435  throw new ilException("Unrecognized operator '$operator'");
436  }
437 
438  return $result;
439  }
440 }
441 
442 
443 ?>
Class ilDclBaseFieldModel.
Class ilDclExpressionParser.
parse()
Parse expression and return result.
Base class for ILIAS Exception handling.
$result
calculate($operator, $left, $right)
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)
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.
static getTokens($expression)
Split expression by & (ignore escaped &-symbols with backslash)
static getMathTokens($math_expression)
Generate tokens for a math expression.
Create styles array
The data for the language used.
Class ilDclBaseRecordModel.
global $lng
Definition: privfeed.php:17
global $DIC
calculateFunction($function, array $args=array())
Calculate a function with its arguments.