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());
177  $functions = self::getFunctions();
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  {
322  $operators = self::$operators;
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 }
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.
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
$function
Definition: cas.php:28
$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.