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);
//}
$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
/*if(isset($this->_src[ $end+1 ])) {
if(substr($this->_body, -2) === "?>" && isset($this->_src[ $end+1 ])) {
$c = $this->_src[ $end+1 ];
if($c === "\n") {
$this->_body .= "\n";
@ -153,7 +153,7 @@ class Template extends Render {
$this->_body .= "\r";
}
}
}*/
}
//if($this->_trim) { // if current tag has trim flag
// $frag = rtrim($frag);
//}
@ -262,20 +262,21 @@ class Template extends Render {
/**
* Internal tags router
* @param string $src
* @param bool $trim
* @param string $frag
* @throws UnexpectedException
* @throws CompileException
* @throws SecurityException
* @return string
*/
private function _tag($src, &$trim = false) {
private function _tag($src, &$frag) {
if($src[strlen($src) - 2] === "-") {
$token = substr($src, 1, -2);
$trim = true;
//$frag = ltrim($frag);
} else {
$token = substr($src, 1, -1);
$trim = false;
}
$this->_body .= $frag;
$token = trim($token);
if($this->_ignore) {
if($token === '/ignore') {
@ -298,10 +299,14 @@ class Template extends Render {
$code = $this->_end($tokens);
break;
default:
$code = $this->_parseAct($tokens);
break;
if($tokens->current() === "ignore") {
$this->_ignore = true;
$tokens->next();
$code = '';
} else {
$code = $this->_parseAct($tokens);
}
}
if($tokens->key()) { // if tokenizer still have tokens
throw new UnexpectedException($tokens);
}
@ -344,6 +349,7 @@ class Template extends Render {
*
* @static
* @param Tokenizer $tokens
* @throws \LogicException
* @throws TokenizeException
* @return string
*/
@ -355,11 +361,6 @@ class Template extends Render {
return 'echo '.$this->parseExp($tokens).';';
}
if($action === "ignore") {
$this->_ignore = true;
$tokens->next();
return '';
}
if($tokens->isNext("(")) {
return "echo ".$this->parseExp($tokens).";";
}
@ -368,7 +369,7 @@ class Template extends Render {
$tokens->next();
switch($act["type"]) {
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);
return $scope->open($tokens);
case Aspect::INLINE_COMPILER:
@ -376,10 +377,12 @@ class Template extends Render {
case Aspect::INLINE_FUNCTION:
return call_user_func($act["parser"], $act["function"], $tokens, $this);
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"]);
array_push($this->_stack, $scope);
return $scope->open($tokens);
default:
throw new \LogicException("Unknown function type");
}
}
@ -520,6 +523,7 @@ class Template extends Render {
* @param int $deny
* @param bool $pure_var
* @throws \LogicException
* @throws UnexpectedException
* @return string
*/
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) {
$end = strpos($this->_src, $after, $this->_pos);
$end = strpos($this->_src, "}", $end);
@ -874,13 +882,25 @@ class Template extends Render {
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) {
if($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
$str = $tokens->getAndNext();
$static = stripslashes(substr($str, 1, -1));
return $str;
if($tokens->isNext('|')) {
return $this->parseExp($tokens, true);
} else {
$str = $tokens->getAndNext();
$static = stripslashes(substr($str, 1, -1));
return $str;
}
} elseif($tokens->is(Tokenizer::MACRO_STRING)) {
return $static = $tokens->getAndNext();
$static = $tokens->getAndNext();
return '"'.addslashes($static).'"';
} else {
return $this->parseExp($tokens, true);
}
@ -925,8 +945,6 @@ class Template extends Render {
}
}
class CompileException extends \ErrorException {}
class SecurityException extends CompileException {}
class ImproperUseException extends \LogicException {}

View File

@ -48,7 +48,7 @@ class Tokenizer {
*/
const MACRO_INCDEC = 1005;
/**
* Boolean operations: &&, ||, or, xor
* Boolean operations: &&, ||, or, xor, and
*/
const MACRO_BOOLEAN = 1006;
/**
@ -98,7 +98,7 @@ class Tokenizer {
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
),
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(
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
@ -171,22 +171,6 @@ class Tokenizer {
$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
*
@ -270,20 +254,6 @@ class Tokenizer {
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.
@ -313,11 +283,10 @@ class Tokenizer {
if($this->curr) {
$cur = $this->curr[1];
$this->next();
return $cur;
} else {
throw new UnexpectedException($this, func_get_args());
}
return $cur;
}
/**
@ -396,7 +365,6 @@ class Tokenizer {
/**
* 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.
*/
public function key() {
@ -405,7 +373,6 @@ class Tokenizer {
/**
* 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.
* Returns true on success or false on failure.
*/
@ -413,13 +380,6 @@ class Tokenizer {
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
* @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
*
@ -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
*
@ -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
* @param int $before count tokens before current token
@ -636,8 +561,6 @@ class UnexpectedException extends TokenizeException {
}
if(!$tokens->curr) {
$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) {
$this->message = "Unexpected whitespace$expect";
} else {

View File

@ -17,6 +17,18 @@ class TestCase extends \PHPUnit_Framework_TestCase {
$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
*
@ -33,6 +45,15 @@ class TestCase extends \PHPUnit_Framework_TestCase {
$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
* @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: {!5} result', $b, 'Exp: result'),
array('Exp: {-1} result', $b, 'Exp: -1 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: {$z = 5} {$z} result', $b, 'Exp: 5 5 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',
$b, 'Exp: 53.75 result'),
array('Exp: {$x+max($x, $y)} result', $b, 'Exp: 36 result'),
$b, 'Exp: 53.75 result'),
array('Exp: {$x+max($x, $y)} result', $b, 'Exp: 36 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',
$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';
$result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template';
return array(
array('Include {include file="welcome.tpl"} template', $a, $result),
array('Include {include file=$tpl} template', $a, $result),
array('Include {include file="$tpl"} template', $a, $result),
array('Include {include file="{$tpl}"} template', $a, $result),
array('Include {include file="$name.tpl"} template', $a, $result),
array('Include {include file="{$name}.tpl"} template', $a, $result),
array('Include {include file="{$pr_name|lower}.tpl"} template', $a, $result),
array('Include {include file="wel{$fragment}.tpl"} template', $a, $result),
array('Include {include file="wel{$pr_fragment|lower}.tpl"} template', $a, $result),
array('Include {include file="welcome.tpl" username="Flame"} template', $a, $result2),
array('Include {include file="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"} template', $a, $result),
array('Include {include $tpl} template', $a, $result),
array('Include {include "$tpl"} template', $a, $result),
array('Include {include "{$tpl}"} template', $a, $result),
array('Include {include "$name.tpl"} template', $a, $result),
array('Include {include "{$name}.tpl"} template', $a, $result),
array('Include {include "{$pr_name|lower}.tpl"} template', $a, $result),
array('Include {include "wel{$fragment}.tpl"} template', $a, $result),
array('Include {include "wel{$pr_fragment|lower}.tpl"} template', $a, $result),
array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2),
array('Include {include "welcome.tpl" email="flame@dev.null"} template', $a, $result3),
array('Include {include "welcome.tpl" username="Flame" email="flame@dev.null"} template',
$a, $result4),
);
}
public static function providerIncludeInvalid() {
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) {
$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->assertSame("3", $tokens->current());
$this->assertSame(T_LNUMBER, $tokens->key());
$this->assertSame(" ", $tokens->getWhiteSpace());
$tokens->next();
}
}