mirror of
https://github.com/Mibew/handlebars.php.git
synced 2025-03-24 01:07:07 +03:00
parent
cd8cec42a0
commit
a68318f4c5
@ -115,6 +115,7 @@ class Helpers
|
|||||||
* @param array $args The arguments passed the the helper
|
* @param array $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
* @return mixed The helper return value
|
* @return mixed The helper return value
|
||||||
*/
|
*/
|
||||||
public function call($name, Template $template, Context $context, $args, $source)
|
public function call($name, Template $template, Context $context, $args, $source)
|
||||||
|
@ -92,7 +92,7 @@ class Parser
|
|||||||
array_unshift($newNodes, $result);
|
array_unshift($newNodes, $result);
|
||||||
}
|
}
|
||||||
} while (true);
|
} while (true);
|
||||||
break;
|
// There is no break here, since we need the end token to handle the whitespace trim
|
||||||
default:
|
default:
|
||||||
array_push($stack, $token);
|
array_push($stack, $token);
|
||||||
}
|
}
|
||||||
@ -101,7 +101,6 @@ class Parser
|
|||||||
} while ($tokens->valid());
|
} while ($tokens->valid());
|
||||||
|
|
||||||
return $stack;
|
return $stack;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,7 @@ class Template
|
|||||||
list($index, $tree, $stop) = $topTree;
|
list($index, $tree, $stop) = $topTree;
|
||||||
|
|
||||||
$buffer = '';
|
$buffer = '';
|
||||||
|
$rTrim = false;
|
||||||
while (array_key_exists($index, $tree)) {
|
while (array_key_exists($index, $tree)) {
|
||||||
$current = $tree[$index];
|
$current = $tree[$index];
|
||||||
$index++;
|
$index++;
|
||||||
@ -155,44 +156,60 @@ class Template
|
|||||||
) {
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) {
|
||||||
|
$buffer = rtrim($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = '';
|
||||||
switch ($current[Tokenizer::TYPE]) {
|
switch ($current[Tokenizer::TYPE]) {
|
||||||
|
case Tokenizer::T_END_SECTION:
|
||||||
|
break; // Its here just for handling whitespace trim.
|
||||||
case Tokenizer::T_SECTION :
|
case Tokenizer::T_SECTION :
|
||||||
$newStack = isset($current[Tokenizer::NODES])
|
$newStack = isset($current[Tokenizer::NODES])
|
||||||
? $current[Tokenizer::NODES] : array();
|
? $current[Tokenizer::NODES] : array();
|
||||||
array_push($this->_stack, array(0, $newStack, false));
|
array_push($this->_stack, array(0, $newStack, false));
|
||||||
$buffer .= $this->_section($context, $current);
|
$tmp = $this->_section($context, $current);
|
||||||
array_pop($this->_stack);
|
array_pop($this->_stack);
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_INVERTED :
|
case Tokenizer::T_INVERTED :
|
||||||
$newStack = isset($current[Tokenizer::NODES]) ?
|
$newStack = isset($current[Tokenizer::NODES]) ?
|
||||||
$current[Tokenizer::NODES] : array();
|
$current[Tokenizer::NODES] : array();
|
||||||
array_push($this->_stack, array(0, $newStack, false));
|
array_push($this->_stack, array(0, $newStack, false));
|
||||||
$buffer .= $this->_inverted($context, $current);
|
$tmp = $this->_inverted($context, $current);
|
||||||
array_pop($this->_stack);
|
array_pop($this->_stack);
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_COMMENT :
|
case Tokenizer::T_COMMENT :
|
||||||
$buffer .= '';
|
$tmp = '';
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_PARTIAL:
|
case Tokenizer::T_PARTIAL:
|
||||||
case Tokenizer::T_PARTIAL_2:
|
case Tokenizer::T_PARTIAL_2:
|
||||||
$buffer .= $this->_partial($context, $current);
|
$tmp = $this->_partial($context, $current);
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_UNESCAPED:
|
case Tokenizer::T_UNESCAPED:
|
||||||
case Tokenizer::T_UNESCAPED_2:
|
case Tokenizer::T_UNESCAPED_2:
|
||||||
$buffer .= $this->_get($context, $current, false);
|
$tmp = $this->_get($context, $current, false);
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_ESCAPED:
|
case Tokenizer::T_ESCAPED:
|
||||||
|
$tmp = $this->_get($context, $current, true);
|
||||||
$buffer .= $this->_get($context, $current, true);
|
|
||||||
break;
|
break;
|
||||||
case Tokenizer::T_TEXT:
|
case Tokenizer::T_TEXT:
|
||||||
$buffer .= $current[Tokenizer::VALUE];
|
$tmp = $current[Tokenizer::VALUE];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new \RuntimeException(
|
throw new \RuntimeException(
|
||||||
'Invalid node type : ' . json_encode($current)
|
'Invalid node type : ' . json_encode($current)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if ($rTrim) {
|
||||||
|
$tmp = ltrim($tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $tmp;
|
||||||
|
// Some time, there is more than one string token (first is empty),
|
||||||
|
//so we need to trim all of them in one shot
|
||||||
|
|
||||||
|
$rTrim = (empty($tmp) && $rTrim) ||
|
||||||
|
isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT];
|
||||||
}
|
}
|
||||||
if ($stop) {
|
if ($stop) {
|
||||||
//Ok break here, the helper should be aware of this.
|
//Ok break here, the helper should be aware of this.
|
||||||
@ -273,25 +290,25 @@ class Template
|
|||||||
|
|
||||||
// subexpression parsing loop
|
// subexpression parsing loop
|
||||||
$subexprs = array(); // will contain all subexpressions inside outermost brackets
|
$subexprs = array(); // will contain all subexpressions inside outermost brackets
|
||||||
$inside_of = array( 'single' => false, 'double' => false );
|
$insideOf = array( 'single' => false, 'double' => false );
|
||||||
$lvl = 0;
|
$lvl = 0;
|
||||||
$cur_start = 0;
|
$cur_start = 0;
|
||||||
for ($i=0; $i < strlen($current[Tokenizer::ARGS]); $i++) {
|
for ($i=0; $i < strlen($current[Tokenizer::ARGS]); $i++) {
|
||||||
$cur = substr($current[Tokenizer::ARGS], $i, 1);
|
$cur = substr($current[Tokenizer::ARGS], $i, 1);
|
||||||
if ($cur == "'" ) {
|
if ($cur == "'" ) {
|
||||||
$inside_of['single'] = ! $inside_of['single'];
|
$insideOf['single'] = ! $insideOf['single'];
|
||||||
}
|
}
|
||||||
if ($cur == '"' ) {
|
if ($cur == '"' ) {
|
||||||
$inside_of['double'] = ! $inside_of['double'];
|
$insideOf['double'] = ! $insideOf['double'];
|
||||||
}
|
}
|
||||||
if ($cur == '(' && ! $inside_of['single'] && ! $inside_of['double']) {
|
if ($cur == '(' && ! $insideOf['single'] && ! $insideOf['double']) {
|
||||||
if ($lvl == 0) {
|
if ($lvl == 0) {
|
||||||
$cur_start = $i+1;
|
$cur_start = $i+1;
|
||||||
}
|
}
|
||||||
$lvl++;
|
$lvl++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($cur == ')' && ! $inside_of['single'] && ! $inside_of['double']) {
|
if ($cur == ')' && ! $insideOf['single'] && ! $insideOf['double']) {
|
||||||
$lvl--;
|
$lvl--;
|
||||||
if ($lvl == 0) {
|
if ($lvl == 0) {
|
||||||
$subexprs[] = substr($current[Tokenizer::ARGS], $cur_start, $i - $cur_start);
|
$subexprs[] = substr($current[Tokenizer::ARGS], $cur_start, $i - $cur_start);
|
||||||
|
@ -60,6 +60,7 @@ class Tokenizer
|
|||||||
const T_ESCAPE = "\\";
|
const T_ESCAPE = "\\";
|
||||||
const T_SINGLE_Q = "'";
|
const T_SINGLE_Q = "'";
|
||||||
const T_DOUBLE_Q = "\"";
|
const T_DOUBLE_Q = "\"";
|
||||||
|
const T_TRIM = "~";
|
||||||
|
|
||||||
// Valid token types
|
// Valid token types
|
||||||
private static $_tagTypes = array(
|
private static $_tagTypes = array(
|
||||||
@ -93,6 +94,8 @@ class Tokenizer
|
|||||||
const NODES = 'nodes';
|
const NODES = 'nodes';
|
||||||
const VALUE = 'value';
|
const VALUE = 'value';
|
||||||
const ARGS = 'args';
|
const ARGS = 'args';
|
||||||
|
const TRIM_LEFT = 'tleft';
|
||||||
|
const TRIM_RIGHT = 'rleft';
|
||||||
|
|
||||||
protected $state;
|
protected $state;
|
||||||
protected $tagType;
|
protected $tagType;
|
||||||
@ -103,6 +106,10 @@ class Tokenizer
|
|||||||
protected $lineStart;
|
protected $lineStart;
|
||||||
protected $otag;
|
protected $otag;
|
||||||
protected $ctag;
|
protected $ctag;
|
||||||
|
protected $escaped;
|
||||||
|
protected $escaping;
|
||||||
|
protected $trimLeft;
|
||||||
|
protected $trimRight;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan and tokenize template source.
|
* Scan and tokenize template source.
|
||||||
@ -132,10 +139,10 @@ class Tokenizer
|
|||||||
|
|
||||||
// To play nice with helpers' arguments quote and apostrophe marks
|
// To play nice with helpers' arguments quote and apostrophe marks
|
||||||
// should be additionally escaped only when they are not in a tag.
|
// should be additionally escaped only when they are not in a tag.
|
||||||
$quote_in_tag = $this->state != self::IN_TEXT
|
$quoteInTag = $this->state != self::IN_TEXT
|
||||||
&& ($text[$i] == self::T_SINGLE_Q || $text[$i] == self::T_DOUBLE_Q);
|
&& ($text[$i] == self::T_SINGLE_Q || $text[$i] == self::T_DOUBLE_Q);
|
||||||
|
|
||||||
if ($this->escaped && $text[$i] != self::T_UNESCAPED && !$quote_in_tag) {
|
if ($this->escaped && $text[$i] != self::T_UNESCAPED && !$quoteInTag) {
|
||||||
$this->buffer .= "\\";
|
$this->buffer .= "\\";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +152,10 @@ class Tokenizer
|
|||||||
$this->buffer .= "{{{";
|
$this->buffer .= "{{{";
|
||||||
$i += 2;
|
$i += 2;
|
||||||
continue;
|
continue;
|
||||||
|
} elseif ($this->tagChange($this->otag. self::T_TRIM, $text, $i) and !$this->escaped) {
|
||||||
|
$this->flushBuffer();
|
||||||
|
$this->state = self::IN_TAG_TYPE;
|
||||||
|
$this->trimLeft = true;
|
||||||
} elseif ($this->tagChange($this->otag, $text, $i) and !$this->escaped) {
|
} elseif ($this->tagChange($this->otag, $text, $i) and !$this->escaped) {
|
||||||
$i--;
|
$i--;
|
||||||
$this->flushBuffer();
|
$this->flushBuffer();
|
||||||
@ -184,6 +195,10 @@ class Tokenizer
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if ($this->tagChange(self::T_TRIM . $this->ctag, $text, $i)) {
|
||||||
|
$this->trimRight = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ($this->tagChange($this->ctag, $text, $i)) {
|
if ($this->tagChange($this->ctag, $text, $i)) {
|
||||||
// Sections (Helpers) can accept parameters
|
// Sections (Helpers) can accept parameters
|
||||||
// Same thing for Partials (little known fact)
|
// Same thing for Partials (little known fact)
|
||||||
@ -206,6 +221,8 @@ class Tokenizer
|
|||||||
self::INDEX => ($this->tagType == self::T_END_SECTION) ?
|
self::INDEX => ($this->tagType == self::T_END_SECTION) ?
|
||||||
$this->seenTag - strlen($this->otag) :
|
$this->seenTag - strlen($this->otag) :
|
||||||
$i + strlen($this->ctag),
|
$i + strlen($this->ctag),
|
||||||
|
self::TRIM_LEFT => $this->trimLeft,
|
||||||
|
self::TRIM_RIGHT => $this->trimRight
|
||||||
);
|
);
|
||||||
if (isset($args)) {
|
if (isset($args)) {
|
||||||
$t[self::ARGS] = $args;
|
$t[self::ARGS] = $args;
|
||||||
@ -214,6 +231,8 @@ class Tokenizer
|
|||||||
unset($t);
|
unset($t);
|
||||||
unset($args);
|
unset($args);
|
||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
|
$this->trimLeft = false;
|
||||||
|
$this->trimRight = false;
|
||||||
$i += strlen($this->ctag) - 1;
|
$i += strlen($this->ctag) - 1;
|
||||||
$this->state = self::IN_TEXT;
|
$this->state = self::IN_TEXT;
|
||||||
if ($this->tagType == self::T_UNESCAPED) {
|
if ($this->tagType == self::T_UNESCAPED) {
|
||||||
@ -262,6 +281,8 @@ class Tokenizer
|
|||||||
$this->lineStart = 0;
|
$this->lineStart = 0;
|
||||||
$this->otag = '{{';
|
$this->otag = '{{';
|
||||||
$this->ctag = '}}';
|
$this->ctag = '}}';
|
||||||
|
$this->trimLeft = false;
|
||||||
|
$this->trimRight = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -337,7 +358,7 @@ class Tokenizer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the current Mustache delimiters. Set new `otag` and `ctag` values.
|
* Change the current Handlebars delimiters. Set new `otag` and `ctag` values.
|
||||||
*
|
*
|
||||||
* @param string $text Mustache template source
|
* @param string $text Mustache template source
|
||||||
* @param int $index Current tokenizer index
|
* @param int $index Current tokenizer index
|
||||||
@ -364,7 +385,7 @@ class Tokenizer
|
|||||||
* Test whether it's time to change tags.
|
* Test whether it's time to change tags.
|
||||||
*
|
*
|
||||||
* @param string $tag Current tag name
|
* @param string $tag Current tag name
|
||||||
* @param string $text Mustache template source
|
* @param string $text Handlebars template source
|
||||||
* @param int $index Current tokenizer index
|
* @param int $index Current tokenizer index
|
||||||
*
|
*
|
||||||
* @return boolean True if this is a closing section tag
|
* @return boolean True if this is a closing section tag
|
||||||
|
@ -283,8 +283,32 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase
|
|||||||
'{{#if 0}}ok{{else}}fail{{/if}}',
|
'{{#if 0}}ok{{else}}fail{{/if}}',
|
||||||
array(),
|
array(),
|
||||||
'fail'
|
'fail'
|
||||||
)
|
),
|
||||||
|
array (
|
||||||
|
' {{~#if 1}}OK {{~else~}} NO {{~/if~}} END',
|
||||||
|
array(),
|
||||||
|
'OKEND'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'XX {{~#bindAttr data}} XX',
|
||||||
|
array(),
|
||||||
|
'XXdata XX'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'{{#each data}}{{#if @last}}the last is
|
||||||
|
{{~this}}{{/if}}{{/each}}',
|
||||||
|
array('data' => array('one', 'two', 'three')),
|
||||||
|
'the last isthree'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'{{#with data}}
|
||||||
|
|
||||||
|
{{~key~}}
|
||||||
|
|
||||||
|
{{/with}}',
|
||||||
|
array('data' => array('key' => 'result')),
|
||||||
|
'result'
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user