// Moritz Bechler // 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); } } ?>