ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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());
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 = ilDataCollectionCache::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 $lng;
289 $lng->loadLanguageModule('dcl');
290// throw new ilException("Field with title '$field_title' not found");
291 throw new ilException(sprintf($lng->txt('dcl_err_formula_field_not_found'), $field_title));
292 }
293 }
294 self::$cache_fields[$placeholder] = $field;
295 }
296
297 return $this->record->getRecordFieldHTML($field->getId());
298 }
299
300
309 protected function parseMath(array $tokens) {
311 $precedence = 0;
312 $stack = new ilDclStack();
313 $precedences = new ilDclStack();
314 $in_bracket = false;
315 foreach ($tokens as $token) {
316 if (empty($token) OR is_null($token)) {
317 $token = 0;
318 }
319 if (is_numeric($token) OR $token === '(') {
320 $stack->push($token);
321 if ($token === '(') {
322 $in_bracket = true;
323 }
324 } elseif (in_array($token, array_keys($operators))) {
325 $new_precedence = $operators[$token]['precedence'];
326 if ($new_precedence > $precedence || $in_bracket) {
327 // Precedence of operator is higher, push operator on stack
328 $stack->push($token);
329 $precedences->push($new_precedence);
330 $precedence = $new_precedence;
331 } else {
332 // Precedence is equal or lower, calculate result on stack
333 while ($new_precedence <= $precedence && $stack->count() > 1) {
334 $right = (float)$stack->pop();
335 $operator = $stack->pop();
336 $left = (float)$stack->pop();
337 $result = $this->calculate($operator, $left, $right);
338 $stack->push($result);
339 $precedence = $precedences->pop();
340 }
341 $stack->push($token);
342 $precedence = $new_precedence;
343 $precedences->push($new_precedence);
344 }
345 } elseif ($token === ')') {
346 // Need to calculate stack back to opening bracket
347 $_tokens = array();
348 $elem = $stack->pop();
349 while ($elem !== '(' && !$stack->isEmpty()) {
350 $_tokens[] = $elem;
351 $elem = $stack->pop();
352 }
353 // Get result within brackets recursive and push to stack
354 $stack->push($this->parseMath(array_reverse($_tokens)));
355 $in_bracket = false;
356 } else {
357 throw new ilException("Unrecognized token '$token'");
358 }
359 // $stack->debug();
360 }
361 // If one element is left on stack, we are done. Otherwise calculate
362 if ($stack->count() == 1) {
363 $result = $stack->pop();
364
365 return (ctype_digit((string)$result)) ? $result : number_format($result, self::N_DECIMALS, '.', "'");
366 } else {
367
368 while ($stack->count() >= 2) {
369 $right = $stack->pop();
370 $operator = $stack->pop();
371 $left = $stack->count() ? $stack->pop() : 0;
372 $stack->push($this->calculate($operator, $left, $right));
373 }
374 $result = $stack->pop();
375
376 return $result;
377 }
378 }
379
380
390 protected function calculateFunction($function, array $args = array()) {
391 switch ($function) {
392 case 'AVERAGE':
393 $count = count($args);
394
395 return ($count) ? array_sum($args) / $count : 0;
396 case 'SUM':
397 return array_sum($args);
398 case 'MIN':
399 return min($args);
400 case 'MAX':
401 return max($args);
402 default:
403 throw new ilException("Unrecognized function '$function'");
404 }
405 }
406
407
416 protected function calculate($operator, $left, $right) {
417 switch ($operator) {
418 case '+':
419 $result = $left + $right;
420 break;
421 case '-':
422 $result = $left - $right;
423 break;
424 case '*':
425 $result = $left * $right;
426 break;
427 case '/':
428 $result = ($right == 0) ? 0 : $left / $right;
429 break;
430 case '^':
431 $result = pow($left, $right);
432 break;
433 default:
434 throw new ilException("Unrecognized operator '$operator'");
435 }
436
437 return $result;
438 }
439}
440
441
442?>
$result
Class ilDataCollectionField.
Class ilDataCollectionRecord.
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.
__construct($expression, ilDataCollectionRecord $record, ilDataCollectionField $field)
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.
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.
$data
global $lng
Definition: privfeed.php:40