- fix issue outlined by Emmanuel Engelhart <emmanuel@engelhart.org> on wikitech-l
[mediawiki.git] / PHPTAL-NP-0.7.0 / libs / PHPTAL / ExpressionFunctions.php
blobc13a71c8cc2074d8cdfd0bb54073a47f87c5cc56
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 //
4 // Copyright (c) 2003 Laurent Bedubourg
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 //
20 // Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
21 //
23 /**
24 * @var $_phptal_es_namespaces
25 * @type array
27 * This array contains phptales namespaces, it means php:, string:, exists:,
28 * not: and path: at this time.
30 global $_phptal_es_namespaces;
31 $_phptal_es_namespaces = array('not', 'php', 'string', 'exists', 'path', 'not-exists');
33 /**
34 * @var $_phptal_es_keywords
35 * @type array
37 * List of reserved phptales keywords.
39 global $_phptal_es_keywords;
40 $_phptal_es_keywords = array('nothing', 'default', 'structure');
42 global $_phptal_es_php_types;
43 $_phptal_es_php_types = array('true', 'false', 'null');
45 /**
46 * string: expression type
48 function PHPTAL_ES_string(&$exp, $value)
50 $value = PHPTAL_ES_path_in_string($value);
51 // $value = str_replace('$$', '\$\$', $value);
52 $value = '"' . $value . '"';
53 $value = preg_replace('/^\" ?\"\. /', '', $value);
54 $value = preg_replace('/\.\"\"$/', '', $value);
55 return $value;
58 /**
59 * not: expression type
61 function PHPTAL_ES_not(&$exp, $value)
63 return "! " . PHPTAL_ES_path($exp, $value);
66 /**
67 * not-exists: expression type
69 function PHPTAL_ES_not_exists(&$exp, $value)
71 return "! " . PHPTAL_ES_exists($exp, $value);
74 /**
75 * path: expression type
77 * @todo default -- existing text inside tag
78 * options -- ?? keyword arguments passed to template ??
79 * repeat -- repeat variables
80 * attrs -- initial values of tag attributes
81 * CONTEXTS -- list of standard names (above list) in case one of these
82 * names was hidden by a local variable.
84 function PHPTAL_ES_path(&$exp, $value)
86 $value = trim($value);
87 // special case : 'string' authorized
88 if ($value[0] == "'") { return PHPTAL_ES_path_in_string($value,"'"); }
89 // number case
90 if (preg_match('/^[0-9\.]*$/', $value)) { return $value; }
91 // php constants are accessed though @ keyword
92 if (preg_match('/^@[_a-z][0-9a-z_]*$/i', $value)) { return substr($value, 1); }
93 if ($value == "nothing") { return 'null'; }
94 if ($value == "default") { return '$__default__'; }
95 $value = PHPTAL_ES_path_in_string($value);
96 return '$__ctx__->get("'. $value .'")';
100 function PHPTAL_ES_path_toString(&$exp, $value)
102 $value = trim($value);
103 if ($value == "nothing") { return 'null'; }
104 if ($value == "default") { return '$__default__'; }
105 if (preg_match('/^@[_a-z][0-9a-z_]*$/i', $value)) { return substr($value, 1); }
106 return '$__ctx__->getToString("'. $value .'")';
111 * exists: expression type
113 function PHPTAL_ES_exists(&$exp, $value)
115 return '$__ctx__->has("'. trim($value). '")';
119 * php: expression type
121 function PHPTAL_ES_php(&$exp, $str)
123 $parser = new PHPTAL_ES_PHP_Parser($exp, $str);
124 return $parser->evaluate();
129 // utility functions (internal)
132 function PHPTAL_ES_path_in_string($arg, $c='"')
134 list($match,$repl) = _PHPTAL_context_accessed($arg);
135 for ($i=0; $i<count($match); $i++) {
136 $null = "";
137 $repl[$i] = $c . ". " . PHPTAL_ES_path_toString($null, $repl[$i]) . " ." . $c;
138 $pos = strpos($arg, $match[$i]);
139 $arg = substr($arg, 0, $pos)
140 . $repl[$i]
141 . substr($arg, $pos + strlen($match[$i]));
143 if ($c == '"') {
144 $arg = str_replace('$$','\$', $arg);
145 } else {
146 $arg = str_replace('$$', '$', $arg);
148 return $arg;
151 function _PHPTAL_context_accessed($str)
153 if (preg_match_all('/((?<!\$)\$\{?([@_a-zA-z0-9.\/\-]+)\}?)/', $str, $match)) {
154 return array_slice($match, 1);
156 return array(array(),array());
160 // php: expression parser
163 class PHPTAL_ES_PHP_Parser
165 var $_exp;
166 var $_gen;
167 var $_str;
168 var $_aliases = array();
169 var $_code = "";
170 var $_last_was_array = false;
172 function PHPTAL_ES_PHP_Parser(&$expression, $str)
174 $this->_exp =& $expression;
175 $this->_gen =& $expression->_gen;
176 $this->_str = $str;
179 function evaluate()
181 $value = $this->_str;
182 $strings = array();
184 // extract strings and replace context calls
185 if (preg_match_all('/(\'.*?(?<!\\\)\')/sm', $value, $m)) {
186 list(,$m) = $m;
187 foreach ($m as $str) {
188 $s_rplc = PHPTAL_ES_path_in_string($str, "'");
189 $s_rplc = preg_replace('/^\' ?\'\. /', '', $s_rplc);
190 $s_rplc = preg_replace('/\.\'\'$/', '', $s_rplc);
191 $value = str_replace($str, '#_STRING_'. count($strings) . '_#', $value);
192 $strings[] = $s_rplc;
196 list($match, $replc) = _PHPTAL_context_accessed($value);
197 $contexts = array();
198 foreach ($match as $m) {
199 $i = count($contexts);
200 $contexts[] = $replc[$i];
201 $value = str_replace($m, '#_CONTEXT_'. $i . '_#', $value);
204 // replace or, and, lt, gt, etc...
205 $value = $this->_php_test_modifiers($value);
206 $value = $this->_php_vars($value);
208 // restore strings
209 $i = 0;
210 foreach ($strings as $str) {
211 $value = str_replace('#_STRING_'. $i . '_#', $str, $value);
212 $i++;
215 $i = 0;
216 foreach ($contexts as $c) {
217 $value = str_replace('#_CONTEXT_' . $i . '_#', PHPTAL_ES_path($this->_exp, $c), $value);
218 $i++;
221 // experimental, compile php: content
223 require_once PT_IP . "/Types/Code.php";
225 $code = new Code();
226 $code->setCode($value .";");
227 $err = $code->compile();
228 if (PEAR::isError($err)) {
229 return $this->_raiseCompileError($value, $err);
232 return $value;
235 function _php_test_modifiers($exp)
237 $exp = preg_replace('/\bnot\b/i', ' !', $exp);
238 $exp = preg_replace('/\bne\b/i', ' != ', $exp);
239 $exp = preg_replace('/\band\b/i', ' && ', $exp);
240 $exp = preg_replace('/\bor\b/i', ' || ', $exp);
241 $exp = preg_replace('/\blt\b/i', ' < ', $exp);
242 $exp = preg_replace('/\bgt\b/i', ' > ', $exp);
243 $exp = preg_replace('/\bge\b/i', ' >= ', $exp);
244 $exp = preg_replace('/\ble\b/i', ' <= ', $exp);
245 $exp = preg_replace('/\beq\b/i', ' == ', $exp);
246 return $exp;
249 function _php_vars($arg)
251 $arg = preg_replace('/\s*\/\s*/',' / ', $arg);
252 $arg = preg_replace("/\s*\(\s*/","(", $arg );
253 $arg = preg_replace('/\s*\)\s*/',') ', $arg );
254 $arg = preg_replace('/\s*\[\s*/','[', $arg );
255 $arg = preg_replace('/\s*\]\s*/',']', $arg );
256 $arg = preg_replace('/\s*,\s*/',' , ', $arg );
258 $result = "";
259 $path = false;
260 $last_path = false;
261 $last_was_array = false;
263 $i = 0;
264 while ($i < strlen($arg)) {
265 $c = $arg[$i];
266 if (preg_match('/[a-z_]/i', $c)) {
267 $path .= $c;
268 } else if (preg_match('/[0-9]/', $c) && $path) {
269 $path .= $c;
270 } else if (preg_match('/[\/\.]/', $c) && $path) {
271 $last_path = $path;
272 $path .= $c;
273 } else if (preg_match('/[\/\.]/', $c) && $this->_last_was_array) {
274 $result .= '->';
275 } else if ($c == '(') {
276 if ($last_path) {
277 $result .= $this->pathRequest($last_path);
278 $result .= '->';
279 $path = substr($path, strlen($last_path) +1);
280 $last_path = false;
282 $result .= $path . '(';
283 $this->_last_was_array = false;
284 $path = false;
285 } else if ($c == '#') {
286 if ($path) {
287 $result .= $this->pathRequest($path);
288 $path = false;
289 $last_path = false;
291 $next = strpos($arg, '#', $i+1);
292 $result .= substr($arg, $i, $next - $i +1);
293 $i = $next;
294 } else if ($c == ':') {
295 if ($arg[$i+1] != ':') { // error
297 $i++;
298 $result .= $path;
299 $result .= '::';
300 $path = false;
301 $last_path = false;
303 } else if ($c == '@') {
304 // access to defined variable, append to result
305 // up to non word character
306 $i++; // skip @ character
307 do {
308 $c = $arg[$i];
309 $result .= $c;
310 $i++;
311 } while ($i < strlen($arg) && preg_match('/[a-z_0-9]/i', $arg[$i]));
312 $i--; // reset last character
314 } else {
315 if ($path) {
316 $result .= $this->pathRequest($path);
317 $path = false;
319 $result .= $c;
320 if ($c == ']') {
321 $last_path = false;
322 $path = false;
323 $this->_last_was_array = true;
324 } else {
325 $this->_last_was_array = false;
328 $i++;
331 // don't forget the last bit
332 if (isset($path) && $path) {
333 $result .= $this->pathRequest($path);
336 return $result;
339 function pathRequest($path)
341 global $_phptal_es_php_types;
343 if (!preg_match('/[a-z]/i', $path)){
344 return $path;
346 if ($this->_last_was_array) {
347 return str_replace('.', '->', $path);
349 if (in_array($path, $_phptal_es_php_types)) {
350 return $path;
353 $concatenate = false;
354 $path = str_replace('.','/', $path);
355 if (substr($path,-1) == '/') {
356 $path = substr($path, 0, -1);
357 $concatenate = true;
360 if (array_key_exists($path, $this->_aliases)) {
361 $res = $this->_aliases[$path];
362 } else {
363 $res = $this->_gen->newTemporaryVar();
364 $this->_aliases[$path] = $res;
365 $this->_gen->doContextGet($res, $path);
368 if ($concatenate) {
369 $res .= ' .';
371 return $res;
374 function _raiseCompileError($code, $err)
376 $str = sprintf("Expression 'php:' compilation error in %s:%d".endl,
377 $this->_exp->_tag->_parser->_file,
378 $this->_exp->_tag->line);
379 return PEAR::raiseError($str);