Fix template nesting

This commit is contained in:
bzick 2013-02-27 20:55:08 +04:00
parent c071c98d51
commit a88c434824
6 changed files with 84 additions and 58 deletions

View File

@ -20,6 +20,7 @@ class Aspect {
const CHECK_MTIME = 0x80; const CHECK_MTIME = 0x80;
const FORCE_COMPILE = 0xF0; const FORCE_COMPILE = 0xF0;
const DISABLE_CACHE = 0x1F0;
const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose'; const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser'; const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser';

View File

@ -404,7 +404,7 @@ class Compiler {
} else { } else {
$body = $tpl->_extends->_body; $body = $tpl->_extends->_body;
} }
if(empty($tpl->_dynamic)) { /*if(empty($tpl->_dynamic)) {
do { do {
$t->_blocks = &$tpl->_blocks; $t->_blocks = &$tpl->_blocks;
$t->compile(); $t->compile();
@ -423,7 +423,7 @@ class Compiler {
$t->compile(); $t->compile();
$tpl->addDepend($t); $tpl->addDepend($t);
$body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); ?>'.$t->_body; $body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); ?>'.$t->_body;
} }*/
} else { // dynamic extends } else { // dynamic extends
$body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->b = &$tpl->b; $parent->display((array)$tpl); unset($tpl->b, $parent->b); ?>'; $body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->b = &$tpl->b; $parent->display((array)$tpl); unset($tpl->b, $parent->b); ?>';
} }
@ -450,7 +450,7 @@ class Compiler {
*/ */
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) { public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
$p = $scope->tpl->parseFirstArg($tokens, $name); $p = $scope->tpl->parseFirstArg($tokens, $name);
$scope["name"] = false; $scope["name"] = $name;
$scope["cname"] = $p; $scope["cname"] = $p;
} }
@ -466,53 +466,51 @@ class Compiler {
if($scope["name"]) { // is scalar name if($scope["name"]) { // is scalar name
if(!isset($tpl->blocks[ $scope["name"] ])) { // is block still doesn't preset if(!isset($tpl->blocks[ $scope["name"] ])) { // is block still doesn't preset
if($tpl->_compatible) { // is compatible mode if($tpl->_compatible) { // is compatible mode
$scope->replace( $scope->replaceContent(
'if(empty($tpl->blocks['.$scope["cname"].'])) { '. '<?php if(empty($tpl->blocks['.$scope["cname"].'])) { '.
'$tpl->b['.$scope["cname"].'] = function($tpl) {'. '$tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL.
$scope->getContent(). $scope->getContent().
"};". "};".
"}\n" "<?php } ?>".PHP_EOL
); );
} else { } else {
$tpl->blocks[ $scope["name"] ] = $scope->getContent(); $tpl->blocks[ $scope["name"] ] = $scope->getContent();
$scope->replace( $scope->replaceContent(
'$tpl->b['.$scope["cname"].'] = function($tpl) {'. '<?php $tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL.
$scope->getContent(). $scope->getContent().
"};\n" "<?php }; ?php".PHP_EOL
); );
} }
} }
} else { // dynamic name } else { // dynamic name
$tpl->_compatible = true; // go to compatible mode $tpl->_compatible = true; // enable compatible mode
$scope->replace( $scope->replaceContent(
'if(empty($tpl->b['.$scope["cname"].'])) { '. '<?php if(empty($tpl->b['.$scope["cname"].'])) { '.
'$tpl->b['.$scope["cname"].'] = function($tpl) {'. '$tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL.
$scope->getContent(). $scope->getContent().
"};". "};".
"}\n" "<?php } ?>".PHP_EOL
); );
} }
} else { // is parent } else { // is parent
if(isset($tpl->blocks[ $scope["name"] ])) { // has block if(isset($tpl->blocks[ $scope["name"] ])) { // has block
if($tpl->_compatible) { if($tpl->_compatible) { // compatible mode enabled
$scope->tpl->replace( $scope->replaceContent(
'<?php if(isset($tpl->blocks['.$scope["cname"].'])) { echo $tpl->blocks['.$scope["cname"].']->__invoke($tpl); } else {?>'. '<?php if(isset($tpl->b['.$scope["cname"].'])) { echo $tpl->b['.$scope["cname"].']->__invoke($tpl); } else {?>'.PHP_EOL.
$tpl->blocks[ $scope["body"] ]. $tpl->blocks[ $scope["body"] ].
'}' '<?php } ?>'.PHP_EOL
); );
} else { } else {
$tpl->replace($tpl->blocks[ $scope["name"] ]); $scope->replaceContent($tpl->blocks[ $scope["name"] ]);
} }
} else { } elseif(isset($tpl->_extended) && $tpl->_compatible || empty($tpl->_extended)) {
if(isset($tpl->_extended)) { $scope->replaceContent(
$scope->tpl->replace( '<?php if(isset($tpl->b['.$scope["cname"].'])) { echo $tpl->b['.$scope["cname"].']->__invoke($tpl); } else {?>'.PHP_EOL.
'<?php if(isset($tpl->blocks['.$scope["cname"].'])) { echo $tpl->blocks['.$scope["cname"].']->__invoke($tpl); } else {?>'.
$scope->getContent(). $scope->getContent().
'}' '<?php } ?>'.PHP_EOL
); );
} }
} }
}
return ''; return '';
} }

View File

@ -6,7 +6,6 @@ namespace Aspect;
*/ */
class Scope extends \ArrayObject { class Scope extends \ArrayObject {
public $id = 0;
public $line = 0; public $line = 0;
public $name; public $name;
public $level = 0; public $level = 0;
@ -16,22 +15,27 @@ class Scope extends \ArrayObject {
public $tpl; public $tpl;
public $is_compiler = true; public $is_compiler = true;
private $_action; private $_action;
private static $count = 0; private $_body;
private $_offset;
/** /**
* Creating cope
*
* @param string $name * @param string $name
* @param Template $tpl * @param Template $tpl
* @param int $line * @param int $line
* @param array $action * @param array $action
* @param int $level * @param int $level
* @param $body
*/ */
public function __construct($name, $tpl, $line, $action, $level) { public function __construct($name, $tpl, $line, $action, $level, &$body) {
$this->id = ++self::$count;
$this->line = $line; $this->line = $line;
$this->name = $name; $this->name = $name;
$this->tpl = $tpl; $this->tpl = $tpl;
$this->_action = $action; $this->_action = $action;
$this->level = $level; $this->level = $level;
$this->_body = &$body;
$this->_offset = strlen($body);
} }
/** /**
@ -50,7 +54,7 @@ class Scope extends \ArrayObject {
* @return mixed * @return mixed
*/ */
public function open($tokenizer) { public function open($tokenizer) {
return call_user_func($this->_action["open"], $tokenizer, $this)." /*#{$this->id}#*/"; return call_user_func($this->_action["open"], $tokenizer, $this);
} }
/** /**
@ -99,26 +103,28 @@ class Scope extends \ArrayObject {
* @return string * @return string
*/ */
public function getContent() { public function getContent() {
if($pos = strpos($this->tpl->_body, "/*#{$this->id}#*/")) { return substr($this->_body, $this->_offset);
$begin = strpos($this->tpl->_body, "?>", $pos);
return substr($this->tpl->_body, $begin + 2);
} else {
throw new \LogicException("Trying get content of non-block scope");
}
} }
/** /**
* Cut scope content
*
* @return string * @return string
* @throws \LogicException * @throws \LogicException
*/ */
public function cutContent() { public function cutContent() {
if($pos = strpos($this->tpl->_body, "/*#{$this->id}#*/")) { $content = substr($this->_body, $this->_offset + 1);
$begin = strpos($this->tpl->_body, "?>", $pos); $this->_body = substr($this->_body, 0, $this->_offset);
$content = substr($this->tpl->_body, $begin + 2);
$this->tpl->_body = substr($this->tpl->_body, 0, $begin + 1);
return $content; return $content;
} else {
throw new \LogicException("Trying cut content of non-block scope");
} }
/**
* Replace scope content
*
* @param $new_content
*/
public function replaceContent($new_content) {
$this->cutContent();
$this->_body .= $new_content;
} }
} }

View File

@ -156,18 +156,18 @@ class Template extends Render {
$this->_line += substr_count($this->_src, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code) $this->_line += substr_count($this->_src, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
$pos = $this->_pos = $end + 1; // move search-pointer to end of the tag $pos = $this->_pos = $end + 1; // move search-pointer to end of the tag
if($tag[strlen($tag) - 2] === "-") { if($tag[strlen($tag) - 2] === "-") { // check right trim flag
$_tag = substr($tag, 1, -2); $_tag = substr($tag, 1, -2);
$_frag = rtrim($frag); $_frag = rtrim($frag);
} else { } else {
$_tag = substr($tag, 1, -1); $_tag = substr($tag, 1, -1);
$_frag = $frag; $_frag = $frag;
} }
if($this->_ignore) { if($this->_ignore) { // check ignore scope
if($_tag === '/ignore') { if($_tag === '/ignore') {
$this->_ignore = false; $this->_ignore = false;
$this->_appendText($_frag); $this->_appendText($_frag);
} else { } else { // still ignore
$frag .= $tag; $frag .= $tag;
continue; continue;
} }
@ -195,6 +195,7 @@ class Template extends Render {
call_user_func_array($cb, array(&$this->_body, $this)); call_user_func_array($cb, array(&$this->_body, $this));
} }
} }
$this->_body = str_replace(array('?>'.PHP_EOL.'<?php ', '?><?php'), array(PHP_EOL, ' '), $this->_body);
} }
/** /**
@ -206,24 +207,43 @@ class Template extends Render {
$this->_body .= str_replace("<?", '<?php echo "<?"; ?>'.PHP_EOL, $text); $this->_body .= str_replace("<?", '<?php echo "<?"; ?>'.PHP_EOL, $text);
} }
public static function escapeCode($code) {
$c = "";
foreach(token_get_all($code) as $token) {
if(is_string($token)) {
$c .= $token;
} elseif($token[0] == T_CLOSE_TAG) {
$c .= $token[1].PHP_EOL;
} else {
$c .= $token[1];
}
}
return $c;
}
/** /**
* Append PHP code to template body * Append PHP code to template body
* *
* @param string $code * @param string $code
*/ */
private function _appendCode($code) { private function _appendCode($code) {
$this->_body .= $code; if(!$code) {
return;
} else {
$this->_body .= self::escapeCode($code);
}
} }
/** /**
* @param callable[] $cb * @param callable[] $cb
*/ */
public function addPostCompile(array $cb) { public function addPostCompile($cb) {
$this->_post[] = $cb; $this->_post[] = $cb;
} }
/** /**
* Return PHP code of template * Return PHP code of template
*
* @return string * @return string
*/ */
public function getBody() { public function getBody() {
@ -232,6 +252,7 @@ class Template extends Render {
/** /**
* Return PHP code for saving to file * Return PHP code for saving to file
*
* @return string * @return string
*/ */
public function getTemplateCode() { public function getTemplateCode() {
@ -398,7 +419,7 @@ class Template extends Render {
if($act = $this->_aspect->getFunction($action)) { // call some function if($act = $this->_aspect->getFunction($action)) { // call some function
switch($act["type"]) { switch($act["type"]) {
case Aspect::BLOCK_COMPILER: case Aspect::BLOCK_COMPILER:
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack)); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
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:
@ -406,7 +427,7 @@ 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, count($this->_stack)); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
$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);

View File

@ -30,7 +30,7 @@ class ExtendsTemplateTest extends TestCase {
* @param $vars * @param $vars
* @param $result * @param $result
*/ */
public function _testStaticExtends($name, $code, $vars, $result) { public function testStaticExtends($name, $code, $vars, $result) {
static $i = 0; static $i = 0;
$vars["iteration"] = $i++; $vars["iteration"] = $i++;
$this->execTpl($name, $code, $vars, $result); $this->execTpl($name, $code, $vars, $result);

View File

@ -17,16 +17,16 @@ class ScopeTest extends TestCase {
} }
public function testBlock() { public function testBlock() {
$scope = new Scope($this->aspect, new Template($this->aspect), 1, array( /*$scope = new Scope($this->aspect, new Template($this->aspect), 1, array(
"open" => array($this, "openTag"), "open" => array($this, "openTag"),
"close" => array($this, "closeTag") "close" => array($this, "closeTag")
), 0); ), 0);
$tokenizer = new Tokenizer("1+1"); $tokenizer = new Tokenizer("1+1");
$this->assertSame("open-tag /*#{$scope->id}#*/", $scope->open($tokenizer)); $this->assertSame("open-tag /*#{$scope->id}#* /", $scope->open($tokenizer));
$this->assertSame("close-tag", $scope->close($tokenizer)); $this->assertSame("close-tag", $scope->close($tokenizer));
$content = " some ?> content\n\nwith /*#9999999#*/ many\n\tlines"; $content = " some ?> content\n\nwith /*#9999999#* / many\n\tlines";
$scope->tpl->_body = "start <?php ".$scope->open($tokenizer)." ?>".$content; $scope->tpl->_body = "start <?php ".$scope->open($tokenizer)." ?>".$content;
$this->assertSame($content, $scope->getContent()); $this->assertSame($content, $scope->getContent());*/
} }
} }