331 lines
10 KiB
PHP
331 lines
10 KiB
PHP
<?php
|
|
/* vim: set expandtab tabstop=4 shiftwidth=4: */
|
|
//
|
|
// Copyright (c) 2004-2005 Laurent Bedubourg
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
// Authors: Laurent Bedubourg <lbedubourg@motion-twin.com>
|
|
// Moritz Bechler <mbechler@eenterphace.org>
|
|
//
|
|
|
|
require_once PHPTAL_DIR.'PHPTAL/TalesRegistry.php';
|
|
|
|
class PHPTAL_TalesInternal implements PHPTAL_Tales {
|
|
|
|
//
|
|
// This function registers all internal expression modifiers
|
|
//
|
|
static public function registerInternalTales() {
|
|
|
|
static $registered = false;
|
|
|
|
if($registered) {
|
|
return;
|
|
}
|
|
|
|
$registry = PHPTAL_TalesRegistry::getInstance();
|
|
|
|
$registry->registerPrefix('not', array(__CLASS__, 'not'));
|
|
$registry->registerPrefix('path', array(__CLASS__, 'path'));
|
|
$registry->registerPrefix('string', array(__CLASS__, 'string'));
|
|
$registry->registerPrefix('php', array(__CLASS__, 'php'));
|
|
$registry->registerPrefix('exists', array(__CLASS__, 'exists'));
|
|
$registry->registerPrefix('number', array(__CLASS__, 'number'));
|
|
$registry->registerPrefix('true', array(__CLASS__, 'true'));
|
|
|
|
$registered = true;
|
|
}
|
|
|
|
static public function true($src, $nothrow)
|
|
{
|
|
return sprintf('phptal_true($ctx, %s)', self::string(trim($src), $nothrow));
|
|
}
|
|
|
|
//
|
|
// not:
|
|
//
|
|
// not: Expression
|
|
//
|
|
// evaluate the expression string (recursively) as a full expression,
|
|
// and returns the boolean negation of its value
|
|
//
|
|
// return boolean based on the following rules:
|
|
//
|
|
// 1. integer 0 is false
|
|
// 2. integer > 0 is true
|
|
// 3. an empty string or other sequence is false
|
|
// 4. a non-empty string or other sequence is true
|
|
// 5. a non-value (e.g. void, None, Nil, NULL, etc) is false
|
|
// 6. all other values are implementation-dependent.
|
|
//
|
|
// Examples:
|
|
//
|
|
// not: exists: foo/bar/baz
|
|
// not: php: object.hasChildren()
|
|
// not: string:${foo}
|
|
// not: foo/bar/booleancomparable
|
|
//
|
|
static public function not($expression, $nothrow)
|
|
{
|
|
return '!(' . phptal_tales($expression, $nothrow) . ')';
|
|
}
|
|
|
|
|
|
//
|
|
// path:
|
|
//
|
|
// PathExpr ::= Path [ '|' Path ]*
|
|
// Path ::= variable [ '/' URL_Segment ]*
|
|
// variable ::= Name
|
|
//
|
|
// Examples:
|
|
//
|
|
// path: username
|
|
// path: user/name
|
|
// path: object/method/10/method/member
|
|
// path: object/${dynamicmembername}/method
|
|
// path: maybethis | path: maybethat | path: default
|
|
//
|
|
// PHPTAL:
|
|
//
|
|
// 'default' may lead to some 'difficult' attributes implementation
|
|
//
|
|
// For example, the tal:content will have to insert php code like:
|
|
//
|
|
// if (isset($ctx->maybethis)) {
|
|
// echo $ctx->maybethis;
|
|
// }
|
|
// else if (isset($ctx->maybethat) {
|
|
// echo $ctx->maybethat;
|
|
// }
|
|
// else {
|
|
// // process default tag content
|
|
// }
|
|
//
|
|
// @returns string or array
|
|
//
|
|
static public function path($expression, $nothrow=false)
|
|
{
|
|
$expression = trim($expression);
|
|
if ($expression == 'default') return PHPTAL_TALES_DEFAULT_KEYWORD;
|
|
if ($expression == 'nothing') return PHPTAL_TALES_NOTHING_KEYWORD;
|
|
if ($expression == '') return PHPTAL_TALES_NOTHING_KEYWORD;
|
|
|
|
// split OR expressions terminated by a string
|
|
if (preg_match('/^(.*?)\s*\|\s*?(string:.*)$/sm', $expression, $m)){
|
|
list(, $expression, $string) = $m;
|
|
}
|
|
// split OR expressions terminated by a 'fast' string
|
|
else if (preg_match('/^(.*?)\s*\|\s*\'((?:[^\'\\\\]|\\\\.)*)\'\s*$/sm', $expression, $m)){
|
|
list(, $expression, $string) = $m;
|
|
$string = 'string:'.stripslashes($string);
|
|
}
|
|
|
|
// split OR expressions
|
|
$exps = preg_split('/\s*\|\s*/sm', $expression);
|
|
|
|
// if (many expressions) or (expressions or terminating string) found then
|
|
// generate the array of sub expressions and return it.
|
|
if (count($exps) > 1 || isset($string)) {
|
|
$result = array();
|
|
foreach ($exps as $exp) {
|
|
$result[] = phptal_tales(trim($exp), true);
|
|
}
|
|
if (isset($string)){
|
|
$result[] = phptal_tales($string, true);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
// only one expression to process
|
|
|
|
// first evaluate ${foo} inside the expression and threat the expression
|
|
// as if it was a string to interpolate
|
|
$expression = self::string($expression);
|
|
$expression = substr($expression, 1, -1);
|
|
|
|
$pos = strpos($expression, '/');
|
|
// if no sub part for this expression, just optimize the generated code
|
|
// and access the $ctx->var
|
|
if ($pos === false) {
|
|
if (!self::checkExpressionPart($expression)) throw new PHPTAL_Exception("Invalid TALES path: '$expression', expected variable name");
|
|
return '$ctx->'.$expression;
|
|
}
|
|
|
|
// otherwise we have to call phptal_path() to resolve the path at runtime
|
|
// extract the first part of the expression (it will be the phptal_path()
|
|
// $base and pass the remaining of the path to phptal_path()
|
|
$next = substr($expression, 0, $pos);
|
|
$expression = substr($expression, $pos+1);
|
|
|
|
if (!self::checkExpressionPart($next)) throw new PHPTAL_Exception("Invalid TALES path: '$next/$expression', expected '$next' to be variable name");
|
|
|
|
// return php code invoking phptal_path($next, $expression, $notrhow)
|
|
return 'phptal_path($ctx->'.$next.', \''.$expression.'\''.($nothrow ? ', true' : '').')';
|
|
}
|
|
|
|
private static function checkExpressionPart($expression)
|
|
{
|
|
return preg_match('/^(\$?[a-z_][a-z0-9_]*|{.*})$/i',$expression);
|
|
}
|
|
|
|
//
|
|
// string:
|
|
//
|
|
// string_expression ::= ( plain_string | [ varsub ] )*
|
|
// varsub ::= ( '$' Path ) | ( '${' Path '}' )
|
|
// plain_string ::= ( '$$' | non_dollar )*
|
|
// non_dollar ::= any character except '$'
|
|
//
|
|
// Examples:
|
|
//
|
|
// string:my string
|
|
// string:hello, $username how are you
|
|
// string:hello, ${user/name}
|
|
// string:you have $$130 in your bank account
|
|
//
|
|
static public function string($expression, $nothrow=false)
|
|
{
|
|
// This is a simple parser which evaluates ${foo} inside
|
|
// 'string:foo ${foo} bar' expressions, it returns the php code which will
|
|
// print the string with correct interpollations.
|
|
// Nothing special there :)
|
|
|
|
$inPath = false;
|
|
$inAccoladePath = false;
|
|
$lastWasDollar = false;
|
|
$result = '';
|
|
$len = strlen($expression);
|
|
for ($i=0; $i<$len; $i++) {
|
|
$c = $expression[$i];
|
|
switch ($c) {
|
|
case '$':
|
|
if ($lastWasDollar) {
|
|
$lastWasDollar = false;
|
|
}
|
|
else {
|
|
$lastWasDollar = true;
|
|
$c = '';
|
|
}
|
|
break;
|
|
|
|
case '\\':
|
|
$c = '\\\\';
|
|
break;
|
|
|
|
case '\'':
|
|
$c = '\\\'';
|
|
break;
|
|
|
|
case '{':
|
|
if ($lastWasDollar) {
|
|
$lastWasDollar = false;
|
|
$inAccoladePath = true;
|
|
$subPath = '';
|
|
$c = '';
|
|
}
|
|
break;
|
|
|
|
case '}':
|
|
if ($inAccoladePath) {
|
|
$inAccoladePath = false;
|
|
$subEval = self::path($subPath);
|
|
if (is_array($subEval)) {
|
|
$err = 'cannot use | operator in evaluated expressions';
|
|
throw new PHPTAL_Exception($err);
|
|
}
|
|
$result .= "'." . $subEval . ".'";
|
|
$subPath = '';
|
|
$lastWasDollar = false;
|
|
$c = '';
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if ($lastWasDollar) {
|
|
$lastWasDollar = false;
|
|
$inPath = true;
|
|
$subPath = $c;
|
|
$c = '';
|
|
}
|
|
else if ($inAccoladePath) {
|
|
$subPath .= $c;
|
|
$c = '';
|
|
}
|
|
else if ($inPath) {
|
|
$t = strtolower($c);
|
|
if (($t >= 'a' && $t <= 'z') || ($t >= '0' && $t <= '9') || ($t == '_')){
|
|
$subPath .= $c;
|
|
$c = '';
|
|
}
|
|
else {
|
|
$inPath = false;
|
|
$subEval = self::path($subPath);
|
|
if (is_array($subEval)) {
|
|
$err = 'cannot use | operator in evaluated expressions';
|
|
throw new PHPTAL_Exception($err);
|
|
}
|
|
$result .= "'." . $subEval . ".'";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
$result .= $c;
|
|
}
|
|
if ($inPath){
|
|
$subEval = self::path($subPath);
|
|
if (is_array($subEval)){
|
|
$err = 'cannot use | operator in evaluated expressions';
|
|
throw new PHPTAL_Exception($err);
|
|
}
|
|
$result .= "'." . $subEval . ".'";
|
|
}
|
|
return '\''.$result.'\'';
|
|
}
|
|
|
|
/**
|
|
* php: modifier.
|
|
*
|
|
* Transform the expression into a regular PHP expression.
|
|
*/
|
|
static public function php($src)
|
|
{
|
|
require_once PHPTAL_DIR.'PHPTAL/Php/Transformer.php';
|
|
return PHPTAL_Php_Transformer::transform($src, '$ctx->');
|
|
}
|
|
|
|
/**
|
|
* exists: modifier.
|
|
*
|
|
* Returns the code required to invoke phptal_exists() on specified path.
|
|
*/
|
|
static public function exists($src, $nothrow)
|
|
{
|
|
return sprintf('phptal_exists($ctx, %s)', self::string(trim($src), $nothrow));
|
|
}
|
|
|
|
/**
|
|
* number: modifier.
|
|
*
|
|
* Returns the number as is.
|
|
*/
|
|
static public function number($src, $nothrow)
|
|
{
|
|
return trim($src);
|
|
}
|
|
}
|
|
|
|
?>
|