Improve templates

This commit is contained in:
bzick
2013-02-20 18:03:53 +04:00
parent 0266f219b7
commit 9ba3ee68f8
5 changed files with 86 additions and 145 deletions

View File

@ -137,12 +137,12 @@ class Template extends Render {
// $frag = ltrim($frag); // $frag = ltrim($frag);
//} //}
$this->_body .= str_replace("<?", '<?php echo "<?"; ?>', $frag); $frag = str_replace("<?", '<?php echo "<?"; ?>'."\n", $frag);
$this->_body .= $this->_tag($tag, $this->_trim); // dispatching tags $this->_body .= $this->_tag($tag, $frag); // dispatching tags
// duplicate new line http://docs.php.net/manual/en/language.basic-syntax.instruction-separation.php // duplicate new line http://docs.php.net/manual/en/language.basic-syntax.instruction-separation.php
/*if(isset($this->_src[ $end+1 ])) { if(substr($this->_body, -2) === "?>" && isset($this->_src[ $end+1 ])) {
$c = $this->_src[ $end+1 ]; $c = $this->_src[ $end+1 ];
if($c === "\n") { if($c === "\n") {
$this->_body .= "\n"; $this->_body .= "\n";
@ -153,7 +153,7 @@ class Template extends Render {
$this->_body .= "\r"; $this->_body .= "\r";
} }
} }
}*/ }
//if($this->_trim) { // if current tag has trim flag //if($this->_trim) { // if current tag has trim flag
// $frag = rtrim($frag); // $frag = rtrim($frag);
//} //}
@ -262,20 +262,21 @@ class Template extends Render {
/** /**
* Internal tags router * Internal tags router
* @param string $src * @param string $src
* @param bool $trim * @param string $frag
* @throws UnexpectedException * @throws UnexpectedException
* @throws CompileException * @throws CompileException
* @throws SecurityException * @throws SecurityException
* @return string * @return string
*/ */
private function _tag($src, &$trim = false) { private function _tag($src, &$frag) {
if($src[strlen($src) - 2] === "-") { if($src[strlen($src) - 2] === "-") {
$token = substr($src, 1, -2); $token = substr($src, 1, -2);
$trim = true; //$frag = ltrim($frag);
} else { } else {
$token = substr($src, 1, -1); $token = substr($src, 1, -1);
$trim = false;
} }
$this->_body .= $frag;
$token = trim($token); $token = trim($token);
if($this->_ignore) { if($this->_ignore) {
if($token === '/ignore') { if($token === '/ignore') {
@ -298,10 +299,14 @@ class Template extends Render {
$code = $this->_end($tokens); $code = $this->_end($tokens);
break; break;
default: default:
$code = $this->_parseAct($tokens); if($tokens->current() === "ignore") {
break; $this->_ignore = true;
$tokens->next();
$code = '';
} else {
$code = $this->_parseAct($tokens);
}
} }
if($tokens->key()) { // if tokenizer still have tokens if($tokens->key()) { // if tokenizer still have tokens
throw new UnexpectedException($tokens); throw new UnexpectedException($tokens);
} }
@ -344,6 +349,7 @@ class Template extends Render {
* *
* @static * @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @throws \LogicException
* @throws TokenizeException * @throws TokenizeException
* @return string * @return string
*/ */
@ -355,11 +361,6 @@ class Template extends Render {
return 'echo '.$this->parseExp($tokens).';'; return 'echo '.$this->parseExp($tokens).';';
} }
if($action === "ignore") {
$this->_ignore = true;
$tokens->next();
return '';
}
if($tokens->isNext("(")) { if($tokens->isNext("(")) {
return "echo ".$this->parseExp($tokens).";"; return "echo ".$this->parseExp($tokens).";";
} }
@ -368,7 +369,7 @@ class Template extends Render {
$tokens->next(); $tokens->next();
switch($act["type"]) { switch($act["type"]) {
case Aspect::BLOCK_COMPILER: case Aspect::BLOCK_COMPILER:
$scope = new Scope($action, $this, $this->_line, $act); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack));
array_push($this->_stack, $scope); array_push($this->_stack, $scope);
return $scope->open($tokens); return $scope->open($tokens);
case Aspect::INLINE_COMPILER: case Aspect::INLINE_COMPILER:
@ -376,10 +377,12 @@ class Template extends Render {
case Aspect::INLINE_FUNCTION: case Aspect::INLINE_FUNCTION:
return call_user_func($act["parser"], $act["function"], $tokens, $this); return call_user_func($act["parser"], $act["function"], $tokens, $this);
case Aspect::BLOCK_FUNCTION: case Aspect::BLOCK_FUNCTION:
$scope = new Scope($action, $this, $this->_line, $act); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack));
$scope->setFuncName($act["function"]); $scope->setFuncName($act["function"]);
array_push($this->_stack, $scope); array_push($this->_stack, $scope);
return $scope->open($tokens); return $scope->open($tokens);
default:
throw new \LogicException("Unknown function type");
} }
} }
@ -520,6 +523,7 @@ class Template extends Render {
* @param int $deny * @param int $deny
* @param bool $pure_var * @param bool $pure_var
* @throws \LogicException * @throws \LogicException
* @throws UnexpectedException
* @return string * @return string
*/ */
public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) { public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
@ -707,6 +711,10 @@ class Template extends Render {
} }
} }
/**
* @param string $after
* @return bool|string
*/
private function _getMoreSubstr($after) { private function _getMoreSubstr($after) {
$end = strpos($this->_src, $after, $this->_pos); $end = strpos($this->_src, $after, $this->_pos);
$end = strpos($this->_src, "}", $end); $end = strpos($this->_src, "}", $end);
@ -874,13 +882,25 @@ class Template extends Render {
throw new TokenizeException("Unexpected token '".$tokens->current()."' in argument list"); throw new TokenizeException("Unexpected token '".$tokens->current()."' in argument list");
} }
/**
* Parse first unnamed argument
*
* @param Tokenizer $tokens
* @param string $static
* @return mixed|string
*/
public function parseFirstArg(Tokenizer $tokens, &$static) { public function parseFirstArg(Tokenizer $tokens, &$static) {
if($tokens->is(T_CONSTANT_ENCAPSED_STRING)) { if($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
$str = $tokens->getAndNext(); if($tokens->isNext('|')) {
$static = stripslashes(substr($str, 1, -1)); return $this->parseExp($tokens, true);
return $str; } else {
$str = $tokens->getAndNext();
$static = stripslashes(substr($str, 1, -1));
return $str;
}
} elseif($tokens->is(Tokenizer::MACRO_STRING)) { } elseif($tokens->is(Tokenizer::MACRO_STRING)) {
return $static = $tokens->getAndNext(); $static = $tokens->getAndNext();
return '"'.addslashes($static).'"';
} else { } else {
return $this->parseExp($tokens, true); return $this->parseExp($tokens, true);
} }
@ -925,8 +945,6 @@ class Template extends Render {
} }
} }
class CompileException extends \ErrorException {} class CompileException extends \ErrorException {}
class SecurityException extends CompileException {} class SecurityException extends CompileException {}
class ImproperUseException extends \LogicException {} class ImproperUseException extends \LogicException {}

View File

@ -48,7 +48,7 @@ class Tokenizer {
*/ */
const MACRO_INCDEC = 1005; const MACRO_INCDEC = 1005;
/** /**
* Boolean operations: &&, ||, or, xor * Boolean operations: &&, ||, or, xor, and
*/ */
const MACRO_BOOLEAN = 1006; const MACRO_BOOLEAN = 1006;
/** /**
@ -98,7 +98,7 @@ class Tokenizer {
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1 "+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
), ),
self::MACRO_BOOLEAN => array( self::MACRO_BOOLEAN => array(
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1 \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_LOGICAL_AND => 1
), ),
self::MACRO_MATH => array( self::MACRO_MATH => array(
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1 "+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
@ -171,22 +171,6 @@ class Tokenizer {
$this->_last_no = $this->tokens[$this->_max][3]; $this->_last_no = $this->tokens[$this->_max][3];
} }
/**
* Set the filter callback. Token may be changed by reference or skipped if callback return false.
*
* @param $callback
*/
public function filter(\Closure $callback) {
$tokens = array();
foreach($this->tokens as $token) {
if($callback($token) !== false) {
$tokens[] = $token;
}
}
$this->tokens = $tokens;
$this->_max = count($this->tokens) - 1;
}
/** /**
* Return the current element * Return the current element
* *
@ -270,20 +254,6 @@ class Tokenizer {
return $this->current(); return $this->current();
} }
/**
* Concatenate tokens from the current one to one of the specified and returns the string.
* @param string|int $token
* @param ...
* @return string
*/
public function getStringUntil($token/*, $token2 */) {
$str = '';
while($this->valid() && !$this->_valid(func_get_args(), $this->curr[0])) {
$str .= $this->curr[1].$this->curr[2];
$this->next();
}
return $str;
}
/** /**
* Return substring. This method doesn't move pointer. * Return substring. This method doesn't move pointer.
@ -313,11 +283,10 @@ class Tokenizer {
if($this->curr) { if($this->curr) {
$cur = $this->curr[1]; $cur = $this->curr[1];
$this->next(); $this->next();
return $cur;
} else { } else {
throw new UnexpectedException($this, func_get_args()); throw new UnexpectedException($this, func_get_args());
} }
return $cur;
} }
/** /**
@ -396,7 +365,6 @@ class Tokenizer {
/** /**
* Return the key of the current element * Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure. * @return mixed scalar on success, or null on failure.
*/ */
public function key() { public function key() {
@ -405,7 +373,6 @@ class Tokenizer {
/** /**
* Checks if current position is valid * Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated. * @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure. * Returns true on success or false on failure.
*/ */
@ -413,13 +380,6 @@ class Tokenizer {
return (bool)$this->curr; return (bool)$this->curr;
} }
/**
* Rewind the Iterator to the first element. Disabled.
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind() {}
/** /**
* Get token name * Get token name
* @static * @static
@ -438,18 +398,6 @@ class Tokenizer {
} }
} }
/**
* Return whitespace of current token
* @return null
*/
public function getWhiteSpace() {
if($this->curr) {
return $this->curr[2];
} else {
return null;
}
}
/** /**
* Skip specific token or throw an exception * Skip specific token or throw an exception
* *
@ -470,19 +418,6 @@ class Tokenizer {
} }
} }
/**
* Skip specific token or do nothing
*
* @param int|string $token1
* @return Tokenizer
*/
public function skipIf($token1/*, $token2, ...*/) {
if($this->_valid(func_get_args(), $this->curr[0])) {
$this->next();
}
return $this;
}
/** /**
* Check current token's type * Check current token's type
* *
@ -498,16 +433,6 @@ class Tokenizer {
} }
} }
/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* The return value is cast to an integer.
*/
public function count() {
return $this->_max;
}
/** /**
* Get tokens near current token * Get tokens near current token
* @param int $before count tokens before current token * @param int $before count tokens before current token
@ -636,8 +561,6 @@ class UnexpectedException extends TokenizeException {
} }
if(!$tokens->curr) { if(!$tokens->curr) {
$this->message = "Unexpected end of ".($where?:"expression")."$expect"; $this->message = "Unexpected end of ".($where?:"expression")."$expect";
} elseif($tokens->curr[1] === "\n") {
$this->message = "Unexpected new line$expect";
} elseif($tokens->curr[0] === T_WHITESPACE) { } elseif($tokens->curr[0] === T_WHITESPACE) {
$this->message = "Unexpected whitespace$expect"; $this->message = "Unexpected whitespace$expect";
} else { } else {

View File

@ -17,6 +17,18 @@ class TestCase extends \PHPUnit_Framework_TestCase {
$this->aspect = Aspect::factory(ASPECT_RESOURCES.'/template', ASPECT_RESOURCES.'/compile'); $this->aspect = Aspect::factory(ASPECT_RESOURCES.'/template', ASPECT_RESOURCES.'/compile');
} }
public static function setUpBeforeClass() {
if(!file_exists(ASPECT_RESOURCES.'/template')) {
mkdir(ASPECT_RESOURCES.'/template', 0777, true);
} else {
Misc::clean(ASPECT_RESOURCES.'/template/');
}
}
public function tpl($name, $code) {
file_put_contents(ASPECT_RESOURCES.'/template/'.$name, $code);
}
/** /**
* Compile and execute template * Compile and execute template
* *
@ -33,6 +45,15 @@ class TestCase extends \PHPUnit_Framework_TestCase {
$this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code"); $this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code");
} }
public function execTpl($name, $code, $vars, $result, $dump = false) {
$this->tpl($name, $code);
$tpl = $this->aspect->getTemplate($name);
if($dump) {
echo "\n========= DUMP BEGIN ===========\n".$code."\n--- to ---\n".$tpl->getBody()."\n========= DUMP END =============\n";
}
$this->assertSame(Modifier::strip($result, true), Modifier::strip($tpl->fetch($vars), true), "Test tpl $name");
}
/** /**
* Try to compile the invalid template * Try to compile the invalid template
* @param string $code source of the template * @param string $code source of the template

View File

@ -165,16 +165,16 @@ class TemplateTest extends TestCase {
array('Exp: {!$x} result', $b, 'Exp: result'), array('Exp: {!$x} result', $b, 'Exp: result'),
array('Exp: {!5} result', $b, 'Exp: result'), array('Exp: {!5} result', $b, 'Exp: result'),
array('Exp: {-1} result', $b, 'Exp: -1 result'), array('Exp: {-1} result', $b, 'Exp: -1 result'),
array('Exp: {$z = 5} {$z} result', $b, 'Exp: 5 5 result'), array('Exp: {$z = 5} {$z} result', $b, 'Exp: 5 5 result'),
array('Exp: {$k.i = "str"} {$k.i} result', $b, 'Exp: str str result'), array('Exp: {$k.i = "str"} {$k.i} result', $b, 'Exp: str str result'),
array('Exp: {($y*$x - (($x+$y) + $y/$x) ^ $y)/4} result', array('Exp: {($y*$x - (($x+$y) + $y/$x) ^ $y)/4} result',
$b, 'Exp: 53.75 result'), $b, 'Exp: 53.75 result'),
array('Exp: {$x+max($x, $y)} result', $b, 'Exp: 36 result'), array('Exp: {$x+max($x, $y)} result', $b, 'Exp: 36 result'),
array('Exp: {max(1,2)} result', $b, 'Exp: 2 result'), array('Exp: {max(1,2)} result', $b, 'Exp: 2 result'),
array('Exp: {round(sin(pi()), 8)} result', $b, 'Exp: 0 result'), array('Exp: {round(sin(pi()), 8)} result', $b, 'Exp: 0 result'),
array('Exp: {max($x, $y) + round(sin(pi()), 8) - min($x, $y) +3} result', array('Exp: {max($x, $y) + round(sin(pi()), 8) - min($x, $y) +3} result',
$b, 'Exp: 21 result'), $b, 'Exp: 21 result'),
); );
} }
@ -207,25 +207,26 @@ class TemplateTest extends TestCase {
$result3 = 'Include <b>Welcome, Master (flame@dev.null)</b> template'; $result3 = 'Include <b>Welcome, Master (flame@dev.null)</b> template';
$result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template'; $result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template';
return array( return array(
array('Include {include file="welcome.tpl"} template', $a, $result), array('Include {include "welcome.tpl"} template', $a, $result),
array('Include {include file=$tpl} template', $a, $result), array('Include {include $tpl} template', $a, $result),
array('Include {include file="$tpl"} template', $a, $result), array('Include {include "$tpl"} template', $a, $result),
array('Include {include file="{$tpl}"} template', $a, $result), array('Include {include "{$tpl}"} template', $a, $result),
array('Include {include file="$name.tpl"} template', $a, $result), array('Include {include "$name.tpl"} template', $a, $result),
array('Include {include file="{$name}.tpl"} template', $a, $result), array('Include {include "{$name}.tpl"} template', $a, $result),
array('Include {include file="{$pr_name|lower}.tpl"} template', $a, $result), array('Include {include "{$pr_name|lower}.tpl"} template', $a, $result),
array('Include {include file="wel{$fragment}.tpl"} template', $a, $result), array('Include {include "wel{$fragment}.tpl"} template', $a, $result),
array('Include {include file="wel{$pr_fragment|lower}.tpl"} template', $a, $result), array('Include {include "wel{$pr_fragment|lower}.tpl"} template', $a, $result),
array('Include {include file="welcome.tpl" username="Flame"} template', $a, $result2), array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2),
array('Include {include file="welcome.tpl" email="flame@dev.null"} template', $a, $result3), array('Include {include "welcome.tpl" email="flame@dev.null"} template', $a, $result3),
array('Include {include file="welcome.tpl" username="Flame" email="flame@dev.null"} template', array('Include {include "welcome.tpl" username="Flame" email="flame@dev.null"} template',
$a, $result4), $a, $result4),
); );
} }
public static function providerIncludeInvalid() { public static function providerIncludeInvalid() {
return array( return array(
array('Include {include} template', 'Aspect\CompileException', "The tag {include} requires 'file' parameter"), array('Include {include} template', 'Aspect\CompileException', "Unexpected end of expression"),
array('Include {include another="welcome.tpl"} template', 'Aspect\CompileException', "Unexpected token '=' in expression"),
); );
} }
@ -721,26 +722,5 @@ class TemplateTest extends TestCase {
public function testLayersInvalid($code, $exception, $message, $options = 0) { public function testLayersInvalid($code, $exception, $message, $options = 0) {
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/**
* @group extends
*/
public function _testExtends() {
echo(self::$aspect->getTemplate("parent.tpl")->getBody()); exit;
}
/**
* @group extends
*/
public function ___testExtends() {
echo(self::$aspect->getTemplate("child1.tpl")->getBody()); exit;
}
/**
* @group extends
*/
public function __testExtends() {
echo(self::$aspect->fetch("child1.tpl", array("a" => "value", "z" => ""))."\n"); exit;
}
} }

View File

@ -60,7 +60,6 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase {
$this->assertTrue($tokens->valid()); $this->assertTrue($tokens->valid());
$this->assertSame("3", $tokens->current()); $this->assertSame("3", $tokens->current());
$this->assertSame(T_LNUMBER, $tokens->key()); $this->assertSame(T_LNUMBER, $tokens->key());
$this->assertSame(" ", $tokens->getWhiteSpace());
$tokens->next(); $tokens->next();
} }
} }