diff --git a/src/Aspect/Template.php b/src/Aspect/Template.php index baaf156..c455967 100644 --- a/src/Aspect/Template.php +++ b/src/Aspect/Template.php @@ -137,12 +137,12 @@ class Template extends Render { // $frag = ltrim($frag); //} - $this->_body .= str_replace("', $frag); + $frag = str_replace("'."\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 {} \ No newline at end of file diff --git a/src/Aspect/Tokenizer.php b/src/Aspect/Tokenizer.php index da0f335..84c0620 100644 --- a/src/Aspect/Tokenizer.php +++ b/src/Aspect/Tokenizer.php @@ -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 { diff --git a/tests/TestCase.php b/tests/TestCase.php index 3895cff..e496cb8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -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 diff --git a/tests/cases/Aspect/TemplateTest.php b/tests/cases/Aspect/TemplateTest.php index 3a97f65..2d9d43e 100644 --- a/tests/cases/Aspect/TemplateTest.php +++ b/tests/cases/Aspect/TemplateTest.php @@ -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 Welcome, Master (flame@dev.null) template'; $result4 = 'Include Welcome, Flame (flame@dev.null) 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; - } } diff --git a/tests/cases/Aspect/TokenizerTest.php b/tests/cases/Aspect/TokenizerTest.php index 9f2e3e8..aa0473d 100644 --- a/tests/cases/Aspect/TokenizerTest.php +++ b/tests/cases/Aspect/TokenizerTest.php @@ -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(); } }