mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
commit
56dfcfc71f
@ -18,7 +18,7 @@
|
||||
var data = { "time": obj.ts };
|
||||
```
|
||||
|
||||
Так же для игнорирования синтаксиса Fenom можно использовать модификатор `:ignore` для любого блочного тега.
|
||||
Так же для игнорирования синтаксиса Fenom можно использовать опцию `:ignore` для любого блочного тега.
|
||||
```smarty
|
||||
{if:ignore $cdn.yandex}
|
||||
var item = {cdn: "//yandex.st/"};
|
||||
|
@ -6,7 +6,9 @@ require_once __DIR__.'/../tests/tools.php';
|
||||
\Fenom::registerAutoload();
|
||||
|
||||
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled');
|
||||
$fenom->setOptions(Fenom::AUTO_RELOAD);
|
||||
var_dump($fenom->compileCode('{set $z = "A"~~"B"}')->getBody());
|
||||
//$fenom->display("blocks/second.tpl", []);
|
||||
$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_COMPILE);
|
||||
$fenom->addAccessorSmart('g', 'App::$q->get', Fenom::ACCESSOR_CALL);
|
||||
var_dump($fenom->compileCode('{$.g("env")}')->getBody());
|
||||
//var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode());
|
||||
//var_dump($fenom->display("bug158/main.tpl", []));
|
||||
// $fenom->getTemplate("problem.tpl");
|
3
sandbox/templates/bug158/main.tpl
Normal file
3
sandbox/templates/bug158/main.tpl
Normal file
@ -0,0 +1,3 @@
|
||||
{* Отображаемый шаблон *}
|
||||
{import [test] from "bug158/test.tpl" as test}
|
||||
{test.test}
|
7
sandbox/templates/bug158/test.tpl
Normal file
7
sandbox/templates/bug158/test.tpl
Normal file
@ -0,0 +1,7 @@
|
||||
{* template:test.tpl *}
|
||||
{macro test($break = false)}
|
||||
Test macro recursive
|
||||
{if $break?}
|
||||
{macro.test break = true}
|
||||
{/if}
|
||||
{/macro}
|
@ -18,7 +18,8 @@ use Fenom\Template;
|
||||
*/
|
||||
class Fenom
|
||||
{
|
||||
const VERSION = '2.6';
|
||||
const VERSION = '2.7';
|
||||
const REV = 1;
|
||||
/* Actions */
|
||||
const INLINE_COMPILER = 1;
|
||||
const BLOCK_COMPILER = 5;
|
||||
@ -53,6 +54,12 @@ class Fenom
|
||||
|
||||
const MAX_MACRO_RECURSIVE = 32;
|
||||
|
||||
const ACCESSOR_CUSTOM = null;
|
||||
const ACCESSOR_VAR = 'Fenom\Accessor::parserVar';
|
||||
const ACCESSOR_CALL = 'Fenom\Accessor::parserCall';
|
||||
|
||||
public static $charset = "UTF-8";
|
||||
|
||||
/**
|
||||
* @var int[] of possible options, as associative array
|
||||
* @see setOptions
|
||||
@ -805,6 +812,21 @@ class Fenom
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add global accessor ($.)
|
||||
* @param string $name
|
||||
* @param callable|string $accessor
|
||||
* @param string $parser
|
||||
* @return Fenom
|
||||
*/
|
||||
public function addAccessorSmart($name, $accessor, $parser) {
|
||||
$this->_accessors[$name] = array(
|
||||
"accessor" => $accessor,
|
||||
"parser" => $parser
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove accessor
|
||||
* @param string $name
|
||||
|
@ -28,10 +28,21 @@ class Accessor {
|
||||
'env' => '$_ENV'
|
||||
);
|
||||
|
||||
public static function parserVar($var, Tokenizer $tokens, Template $tpl, &$is_var) {
|
||||
$is_var = true;
|
||||
return $tpl->parseVariable($tokens, $var);
|
||||
}
|
||||
|
||||
|
||||
public static function parserCall($call, Tokenizer $tokens, Template $tpl) {
|
||||
return $call.$tpl->parseArgs($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for global variables
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @return string
|
||||
*/
|
||||
public static function getVar(Tokenizer $tokens, Template $tpl)
|
||||
{
|
||||
@ -47,6 +58,7 @@ class Accessor {
|
||||
/**
|
||||
* Accessor for template information
|
||||
* @param Tokenizer $tokens
|
||||
* @return string
|
||||
*/
|
||||
public static function tpl(Tokenizer $tokens)
|
||||
{
|
||||
|
@ -741,7 +741,16 @@ class Compiler
|
||||
*/
|
||||
public static function setOpen(Tokenizer $tokens, Tag $scope)
|
||||
{
|
||||
$var = $scope->tpl->parseVariable($tokens);
|
||||
if($tokens->is(T_VARIABLE)) {
|
||||
$var = $scope->tpl->parseVariable($tokens);
|
||||
} elseif($tokens->is('$')) {
|
||||
$var = $scope->tpl->parseAccessor($tokens, $is_var);
|
||||
if(!$is_var) {
|
||||
throw new InvalidUsageException("Accessor is not writable");
|
||||
}
|
||||
} else {
|
||||
throw new InvalidUsageException("{set} and {add} accept only variable");
|
||||
}
|
||||
$before = $after = "";
|
||||
if($scope->name == 'add') {
|
||||
$before = "if(!isset($var)) {\n";
|
||||
|
@ -58,13 +58,13 @@ class Modifier
|
||||
* @param string $charset
|
||||
* @return string
|
||||
*/
|
||||
public static function escape($text, $type = 'html', $charset = 'UTF-8')
|
||||
public static function escape($text, $type = 'html', $charset = null)
|
||||
{
|
||||
switch (strtolower($type)) {
|
||||
case "url":
|
||||
return urlencode($text);
|
||||
case "html";
|
||||
return htmlspecialchars($text, ENT_COMPAT, $charset);
|
||||
return htmlspecialchars($text, ENT_COMPAT, $charset ? $charset : \Fenom::$charset);
|
||||
case "js":
|
||||
return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256
|
||||
default:
|
||||
|
@ -428,9 +428,9 @@ class Template extends Render
|
||||
{
|
||||
if ($this->macros) {
|
||||
$macros = array();
|
||||
foreach ($this->macros as $m) {
|
||||
foreach ($this->macros as $name => $m) {
|
||||
if ($m["recursive"]) {
|
||||
$macros[] = "\t\t'" . $m["name"] . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}";
|
||||
$macros[] = "\t\t'" . $name . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}";
|
||||
}
|
||||
}
|
||||
return "array(\n" . implode(",\n", $macros) . ")";
|
||||
@ -490,7 +490,7 @@ class Template extends Render
|
||||
$escape = $this->_options & Fenom::AUTO_ESCAPE;
|
||||
}
|
||||
if ($escape) {
|
||||
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
|
||||
return "echo htmlspecialchars($data, ENT_COMPAT, ".var_export(Fenom::$charset, true).");";
|
||||
} else {
|
||||
return "echo $data;";
|
||||
}
|
||||
@ -782,86 +782,107 @@ class Template extends Render
|
||||
} else {
|
||||
$unary = "";
|
||||
}
|
||||
if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
|
||||
$code = $unary . $this->parseScalar($tokens, true);
|
||||
} elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING, '"', T_ENCAPSED_AND_WHITESPACE)) {
|
||||
if ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
}
|
||||
$code = $this->parseScalar($tokens, true);
|
||||
} elseif ($tokens->is(T_VARIABLE)) {
|
||||
$code = $this->parseVariable($tokens);
|
||||
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
|
||||
if ($this->_options & Fenom::DENY_METHODS) {
|
||||
throw new \LogicException("Forbidden to call methods");
|
||||
switch($tokens->key()) {
|
||||
case T_LNUMBER:
|
||||
case T_DNUMBER:
|
||||
$code = $unary . $this->parseScalar($tokens, true);
|
||||
break;
|
||||
case T_CONSTANT_ENCAPSED_STRING:
|
||||
case '"':
|
||||
case T_ENCAPSED_AND_WHITESPACE:
|
||||
if ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
}
|
||||
$code = $unary . $this->parseChain($tokens, $code);
|
||||
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$code = $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)';
|
||||
} else {
|
||||
$code = $unary . $code . $tokens->getAndNext();
|
||||
}
|
||||
} else {
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$code = $unary . '(isset(' . $code . ') ? ' . $code . ' : null)';
|
||||
} else {
|
||||
$is_var = true;
|
||||
$code = $this->parseScalar($tokens, true);
|
||||
break;
|
||||
case '$':
|
||||
$code = $this->parseAccessor($tokens, $is_var);
|
||||
if(!$is_var) {
|
||||
$code = $unary . $code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif ($tokens->is('$')) {
|
||||
$is_var = false;
|
||||
$code = $unary . $this->parseAccessor($tokens);
|
||||
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$var = $this->parseVariable($tokens);
|
||||
$code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
|
||||
} else {
|
||||
$code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
|
||||
}
|
||||
} elseif ($tokens->is("(")) {
|
||||
$tokens->next();
|
||||
$code = $unary . "(" . $this->parseExpr($tokens) . ")";
|
||||
$tokens->need(")")->next();
|
||||
} elseif ($tokens->is(T_STRING)) {
|
||||
if ($tokens->isSpecialVal()) {
|
||||
$code = $unary . $tokens->getAndNext();
|
||||
} elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) {
|
||||
$func = $this->_fenom->getModifier($modifier = $tokens->current(), $this);
|
||||
if (!$func) {
|
||||
throw new \Exception("Function " . $tokens->getAndNext() . " not found");
|
||||
case T_VARIABLE:
|
||||
if(!isset($code)) {
|
||||
$code = $this->parseVariable($tokens);
|
||||
}
|
||||
if (!is_string($func)) { // dynamic modifier
|
||||
$call = 'call_user_func_array($tpl->getStorage()->getModifier("' . $modifier . '"), array'.$this->parseArgs($tokens->next()).')'; // @todo optimize
|
||||
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
|
||||
if ($this->_options & Fenom::DENY_METHODS) {
|
||||
throw new \LogicException("Forbidden to call methods");
|
||||
}
|
||||
$code = $unary . $this->parseChain($tokens, $code);
|
||||
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$code = $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)';
|
||||
} else {
|
||||
$code = $unary . $code . $tokens->getAndNext();
|
||||
}
|
||||
} else {
|
||||
$call = $func . $this->parseArgs($tokens->next());
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$code = $unary . '(isset(' . $code . ') ? ' . $code . ' : null)';
|
||||
} else {
|
||||
$is_var = true;
|
||||
$code = $unary . $code;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case T_DEC:
|
||||
case T_INC:
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
$var = $this->parseVariable($tokens);
|
||||
$code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
|
||||
} else {
|
||||
$code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
$tokens->next();
|
||||
$code = $unary . "(" . $this->parseExpr($tokens) . ")";
|
||||
$tokens->need(")")->next();
|
||||
break;
|
||||
case T_STRING:
|
||||
if ($tokens->isSpecialVal()) {
|
||||
$code = $unary . $tokens->getAndNext();
|
||||
} elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) {
|
||||
$func = $this->_fenom->getModifier($modifier = $tokens->current(), $this);
|
||||
if (!$func) {
|
||||
throw new \Exception("Function " . $tokens->getAndNext() . " not found");
|
||||
}
|
||||
if (!is_string($func)) { // dynamic modifier
|
||||
$call = 'call_user_func_array($tpl->getStorage()->getModifier("' . $modifier . '"), array'.$this->parseArgs($tokens->next()).')'; // @todo optimize
|
||||
} else {
|
||||
$call = $func . $this->parseArgs($tokens->next());
|
||||
}
|
||||
$code = $unary . $this->parseChain($tokens, $call);
|
||||
} elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
|
||||
$method = $this->parseStatic($tokens);
|
||||
$args = $this->parseArgs($tokens);
|
||||
$code = $unary . $this->parseChain($tokens, $method . $args);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case T_ISSET:
|
||||
case T_EMPTY:
|
||||
$func = $tokens->getAndNext();
|
||||
if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
|
||||
$code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")";
|
||||
$tokens->need(')')->next();
|
||||
} else {
|
||||
throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables");
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
if ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
}
|
||||
$code = $this->parseArray($tokens);
|
||||
break;
|
||||
default:
|
||||
if ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$code = $unary . $this->parseChain($tokens, $call);
|
||||
} elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
|
||||
$method = $this->parseStatic($tokens);
|
||||
$args = $this->parseArgs($tokens);
|
||||
$code = $unary . $this->parseChain($tokens, $method . $args);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} elseif ($tokens->is(T_ISSET, T_EMPTY)) {
|
||||
$func = $tokens->getAndNext();
|
||||
if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
|
||||
$code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")";
|
||||
$tokens->need(')')->next();
|
||||
} else {
|
||||
throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables");
|
||||
}
|
||||
} elseif ($tokens->is('[')) {
|
||||
if ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
}
|
||||
$code = $this->parseArray($tokens);
|
||||
} elseif ($unary) {
|
||||
throw new UnexpectedTokenException($tokens->back());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (($allows & self::TERM_MODS) && $tokens->is('|')) {
|
||||
$code = $this->parseModifier($tokens, $code);
|
||||
@ -958,14 +979,20 @@ class Template extends Render
|
||||
/**
|
||||
* Parse accessor
|
||||
* @param Tokenizer $tokens
|
||||
* @param bool $is_var
|
||||
* @return string
|
||||
*/
|
||||
public function parseAccessor(Tokenizer $tokens)
|
||||
public function parseAccessor(Tokenizer $tokens, &$is_var = false)
|
||||
{
|
||||
$accessor = $tokens->need('$')->next()->need('.')->next()->current();
|
||||
$callback = $this->getStorage()->getAccessor($accessor);
|
||||
if($callback) {
|
||||
return call_user_func($callback, $tokens->next(), $this);
|
||||
$parser = $this->getStorage()->getAccessor($accessor);
|
||||
$is_var = false;
|
||||
if($parser) {
|
||||
if(is_string($parser)) {
|
||||
return call_user_func_array($parser, array($tokens->next(), $this, &$is_var));
|
||||
} else {
|
||||
return call_user_func_array($parser['parser'], array($parser['accessor'], $tokens->next(), $this, &$is_var));
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException("Unknown accessor '$accessor'");
|
||||
}
|
||||
@ -1342,6 +1369,7 @@ class Template extends Render
|
||||
{
|
||||
$recursive = false;
|
||||
$macro = false;
|
||||
|
||||
if (isset($this->macros[$name])) {
|
||||
$macro = $this->macros[$name];
|
||||
$recursive = $macro['recursive'];
|
||||
@ -1414,7 +1442,6 @@ class Template extends Render
|
||||
* (1 + 2.3, 'string', $var, [2,4])
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @param bool $as_string
|
||||
* @return string
|
||||
*/
|
||||
public function parseArgs(Tokenizer $tokens)
|
||||
|
@ -77,34 +77,12 @@ class MacrosTest extends TestCase
|
||||
$this->tpl(
|
||||
"macro_recursive_import.tpl",
|
||||
'
|
||||
{import "macro_recursive.tpl" as math}
|
||||
{import [factorial] from "macro_recursive.tpl" as math}
|
||||
|
||||
{math.factorial num=10}'
|
||||
);
|
||||
}
|
||||
|
||||
public function _testSandbox()
|
||||
{
|
||||
try {
|
||||
// $this->fenom->compile("macro_recursive.tpl")->display([]);
|
||||
// $this->fenom->flush();
|
||||
// var_dump($this->fenom->fetch("macro_recursive.tpl", []));
|
||||
var_dump(
|
||||
$this->fenom->compileCode(
|
||||
'{macro factorial(num)}
|
||||
{if $num}
|
||||
{$num} {macro.factorial num=$num-1} {$num}
|
||||
{/if}
|
||||
{/macro}'
|
||||
)->getBody()
|
||||
);
|
||||
// var_dump($this->fenom->display("macro_recursive_import.tpl", array()));
|
||||
} catch (\Exception $e) {
|
||||
var_dump($e->getMessage() . ": " . $e->getTraceAsString());
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @group macros
|
||||
@ -162,6 +140,7 @@ class MacrosTest extends TestCase
|
||||
$this->assertSame("10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10", Modifier::strip($tpl->fetch(array()), true));
|
||||
}
|
||||
|
||||
|
||||
public function testImportRecursive()
|
||||
{
|
||||
$this->fenom->compile('macro_recursive_import.tpl');
|
||||
|
@ -10,23 +10,25 @@ class SandboxTest extends TestCase {
|
||||
*/
|
||||
public function test()
|
||||
{
|
||||
|
||||
return;
|
||||
$this->fenom->setOptions(\Fenom::FORCE_VERIFY);
|
||||
$this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR);
|
||||
// $this->assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]);
|
||||
// $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) {
|
||||
// return '<?php ' . $tag->cutContent();
|
||||
// });
|
||||
// $this->tpl('welcome.tpl', '{$a}');
|
||||
// var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody());
|
||||
// try {
|
||||
// var_dump($this->fenom->compileCode('{foreach $a as $k => $v} {/foreach}')->getBody());
|
||||
// } catch (\Exception $e) {
|
||||
// print_r($e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
// while ($e->getPrevious()) {
|
||||
// $e = $e->getPrevious();
|
||||
// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
|
||||
// }
|
||||
// }
|
||||
// exit;
|
||||
try {
|
||||
var_dump($this->fenom->compileCode('{set $.q.ddqd->d() = 333}')->getBody());
|
||||
} catch (\Exception $e) {
|
||||
print_r($e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
while ($e->getPrevious()) {
|
||||
$e = $e->getPrevious();
|
||||
print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user