mirror of
https://github.com/Mibew/handlebars.php.git
synced 2024-11-15 16:54:12 +03:00
Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
86ebab9dba
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ composer.phar
|
|||||||
composer.lock
|
composer.lock
|
||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
|
GPATH
|
||||||
|
GRTAGS
|
||||||
|
GTAGS
|
||||||
|
89
src/Handlebars/ChildContext.php
Normal file
89
src/Handlebars/ChildContext.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Handlebars-php
|
||||||
|
* Base on mustache-php https://github.com/bobthecow/mustache.php
|
||||||
|
*
|
||||||
|
* PHP version 5.3
|
||||||
|
*
|
||||||
|
* @category Xamin
|
||||||
|
* @package Handlebars
|
||||||
|
* @author fzerorubigd <fzerorubigd@gmail.com>
|
||||||
|
* @author Behrooz Shabani <everplays@gmail.com>
|
||||||
|
* @author Chris Gray <chris.w.gray@gmail.com>
|
||||||
|
* @author Ulrik Lystbaek <ulrik@bettertaste.dk>
|
||||||
|
* @author Dmitriy Simushev <simushevds@gmail.com>
|
||||||
|
* @author Christian Blanquera <cblanquera@openovate.com>
|
||||||
|
* @copyright 2010-2012 (c) Justin Hileman
|
||||||
|
* @copyright 2012 (c) ParsPooyesh Co
|
||||||
|
* @copyright 2013 (c) Behrooz Shabani
|
||||||
|
* @copyright 2013 (c) f0ruD A
|
||||||
|
* @license MIT <http://opensource.org/licenses/MIT>
|
||||||
|
* @version GIT: $Id$
|
||||||
|
* @link http://xamin.ir
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Handlebars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handlebars context
|
||||||
|
* Context for a template
|
||||||
|
*
|
||||||
|
* @category Xamin
|
||||||
|
* @package Handlebars
|
||||||
|
* @author fzerorubigd <fzerorubigd@gmail.com>
|
||||||
|
* @author Behrooz Shabani <everplays@gmail.com>
|
||||||
|
* @copyright 2010-2012 (c) Justin Hileman
|
||||||
|
* @copyright 2012 (c) ParsPooyesh Co
|
||||||
|
* @license MIT <http://opensource.org/licenses/MIT>
|
||||||
|
* @version Release: @package_version@
|
||||||
|
* @link http://xamin.ir
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ChildContext extends Context
|
||||||
|
{
|
||||||
|
protected $parentContext = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a parent context in which
|
||||||
|
* we will case for the ../ in get()
|
||||||
|
*
|
||||||
|
* @param Context $parent parent context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setParent(Context $parent)
|
||||||
|
{
|
||||||
|
$this->parentContext = $parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a available from current context
|
||||||
|
* Supported types :
|
||||||
|
* variable , ../variable , variable.variable , variable.[variable] , .
|
||||||
|
*
|
||||||
|
* @param string $variableName variable name to get from current context
|
||||||
|
* @param boolean $strict strict search? if not found then throw exception
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException in strict mode and variable not found
|
||||||
|
* @throws \RuntimeException if supplied argument is a malformed quoted string
|
||||||
|
* @throws \InvalidArgumentException if variable name is invalid
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get($variableName, $strict = false)
|
||||||
|
{
|
||||||
|
//if the variable name starts with a ../
|
||||||
|
//and we have a parent
|
||||||
|
if (strpos($variableName, '../') === 0
|
||||||
|
&& $this->parentContext instanceof Context
|
||||||
|
) {
|
||||||
|
//just remove the first ../
|
||||||
|
$variableName = substr($variableName, 3);
|
||||||
|
|
||||||
|
//and let the parent context handle the rest
|
||||||
|
return $this->parentContext->get($variableName, $strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
//otherwise, it's business as usual
|
||||||
|
return parent::get($variableName, $strict);
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,6 @@ namespace Handlebars;
|
|||||||
|
|
||||||
class Context
|
class Context
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of charcters that cannot be used in identifiers.
|
* List of charcters that cannot be used in identifiers.
|
||||||
*/
|
*/
|
||||||
@ -53,22 +52,30 @@ class Context
|
|||||||
const NOT_VALID_SEGMENT_NAME_CHARS = "]";
|
const NOT_VALID_SEGMENT_NAME_CHARS = "]";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Context stack
|
||||||
|
*
|
||||||
* @var array stack for context only top stack is available
|
* @var array stack for context only top stack is available
|
||||||
*/
|
*/
|
||||||
protected $stack = array();
|
protected $stack = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Section stack index
|
||||||
|
*
|
||||||
* @var array index stack for sections
|
* @var array index stack for sections
|
||||||
*/
|
*/
|
||||||
protected $index = array();
|
protected $index = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Object stack keys
|
||||||
|
*
|
||||||
* @var array key stack for objects
|
* @var array key stack for objects
|
||||||
*/
|
*/
|
||||||
protected $key = array();
|
protected $key = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Special variables stack for sections. Each stack element can
|
* Special variables stack for sections.
|
||||||
|
*
|
||||||
|
* @var array Each stack element can
|
||||||
* contain elements with "@index", "@key", "@first" and "@last" keys.
|
* contain elements with "@index", "@key", "@first" and "@last" keys.
|
||||||
*/
|
*/
|
||||||
protected $specialVariables = array();
|
protected $specialVariables = array();
|
||||||
@ -309,8 +316,18 @@ class Context
|
|||||||
$bad_chars = preg_quote(self::NOT_VALID_NAME_CHARS, '/');
|
$bad_chars = preg_quote(self::NOT_VALID_NAME_CHARS, '/');
|
||||||
$bad_seg_chars = preg_quote(self::NOT_VALID_SEGMENT_NAME_CHARS, '/');
|
$bad_seg_chars = preg_quote(self::NOT_VALID_SEGMENT_NAME_CHARS, '/');
|
||||||
|
|
||||||
$name_pattern = "(?:[^" . $bad_chars . "\s]+)|(?:\[[^" . $bad_seg_chars . "]+\])";
|
$name_pattern = "(?:[^"
|
||||||
$check_pattern = "/^((" . $name_pattern . ")\.)*(" . $name_pattern . ")\.?$/";
|
. $bad_chars
|
||||||
|
. "\s]+)|(?:\[[^"
|
||||||
|
. $bad_seg_chars
|
||||||
|
. "]+\])";
|
||||||
|
|
||||||
|
$check_pattern = "/^(("
|
||||||
|
. $name_pattern
|
||||||
|
. ")\.)*("
|
||||||
|
. $name_pattern
|
||||||
|
. ")\.?$/";
|
||||||
|
|
||||||
$get_pattern = "/(?:" . $name_pattern . ")/";
|
$get_pattern = "/(?:" . $name_pattern . ")/";
|
||||||
|
|
||||||
if (!preg_match($check_pattern, $variableName)) {
|
if (!preg_match($check_pattern, $variableName)) {
|
||||||
|
@ -41,7 +41,7 @@ class Handlebars
|
|||||||
const VERSION = '1.1.0';
|
const VERSION = '1.1.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* factory method
|
* Factory method
|
||||||
*
|
*
|
||||||
* @param array $options see __construct's options parameter
|
* @param array $options see __construct's options parameter
|
||||||
*
|
*
|
||||||
@ -57,31 +57,43 @@ class Handlebars
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current tokenizer instance
|
||||||
|
*
|
||||||
* @var Tokenizer
|
* @var Tokenizer
|
||||||
*/
|
*/
|
||||||
private $_tokenizer;
|
private $_tokenizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current parser instance
|
||||||
|
*
|
||||||
* @var Parser
|
* @var Parser
|
||||||
*/
|
*/
|
||||||
private $_parser;
|
private $_parser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current helper list
|
||||||
|
*
|
||||||
* @var Helpers
|
* @var Helpers
|
||||||
*/
|
*/
|
||||||
private $_helpers;
|
private $_helpers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current loader instance
|
||||||
|
*
|
||||||
* @var Loader
|
* @var Loader
|
||||||
*/
|
*/
|
||||||
private $_loader;
|
private $_loader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current partial loader instance
|
||||||
|
*
|
||||||
* @var Loader
|
* @var Loader
|
||||||
*/
|
*/
|
||||||
private $_partialsLoader;
|
private $_partialsLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Current cache instance
|
||||||
|
*
|
||||||
* @var Cache
|
* @var Cache
|
||||||
*/
|
*/
|
||||||
private $_cache;
|
private $_cache;
|
||||||
@ -104,6 +116,8 @@ class Handlebars
|
|||||||
private $_escape = 'htmlspecialchars';
|
private $_escape = 'htmlspecialchars';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Parameters for the escpae method above
|
||||||
|
*
|
||||||
* @var array parametes to pass to escape function
|
* @var array parametes to pass to escape function
|
||||||
*/
|
*/
|
||||||
private $_escapeArgs = array(
|
private $_escapeArgs = array(
|
||||||
@ -259,6 +273,110 @@ class Handlebars
|
|||||||
return $this->getHelpers()->has($name);
|
return $this->getHelpers()->has($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new helper.
|
||||||
|
*
|
||||||
|
* @param string $name helper name
|
||||||
|
* @param mixed $helper helper callable
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function registerHelper($name, $helper)
|
||||||
|
{
|
||||||
|
$callback = function ($template, $context, $arg) use ($helper) {
|
||||||
|
$args = $template->parseArguments($arg);
|
||||||
|
$named = $template->parseNamedArguments($arg);
|
||||||
|
|
||||||
|
foreach ($args as $i => $arg) {
|
||||||
|
//if it's literally string
|
||||||
|
if ($arg instanceof BaseString) {
|
||||||
|
//we have no problems here
|
||||||
|
$args[$i] = (string) $arg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//not sure what to do if it's not a string or StringWrapper
|
||||||
|
if (!is_string($arg)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//it's a variable and we need to figure out the value of it
|
||||||
|
$args[$i] = $context->get($arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//push the options
|
||||||
|
$args[] = array(
|
||||||
|
//special fields
|
||||||
|
'data' => array(
|
||||||
|
'index' => $context->get('@index'),
|
||||||
|
'key' => $context->get('@key'),
|
||||||
|
'first' => $context->get('@first'),
|
||||||
|
'last' => $context->get('@last')),
|
||||||
|
// Named arguments
|
||||||
|
'hash' => $named,
|
||||||
|
// A renderer for block helper
|
||||||
|
'fn' => function ($inContext = null) use ($context, $template) {
|
||||||
|
$defined = !!$inContext;
|
||||||
|
|
||||||
|
if (!$defined) {
|
||||||
|
$inContext = $context;
|
||||||
|
$inContext->push($inContext->last());
|
||||||
|
} else if (!$inContext instanceof Context) {
|
||||||
|
$inContext = new ChildContext($inContext);
|
||||||
|
$inContext->setParent($context);
|
||||||
|
}
|
||||||
|
|
||||||
|
$template->setStopToken('else');
|
||||||
|
$buffer = $template->render($inContext);
|
||||||
|
$template->setStopToken(false);
|
||||||
|
//what if it's a loop ?
|
||||||
|
$template->rewind();
|
||||||
|
//What's the point of this again?
|
||||||
|
//I mean in this context (literally)
|
||||||
|
//$template->discard($inContext);
|
||||||
|
|
||||||
|
if ($defined) {
|
||||||
|
$inContext->pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
},
|
||||||
|
|
||||||
|
// A render for the else block
|
||||||
|
'inverse' => function ($inContext = null) use ($context, $template) {
|
||||||
|
$defined = !!$inContext;
|
||||||
|
|
||||||
|
if (!$defined) {
|
||||||
|
$inContext = $context;
|
||||||
|
$inContext->push($inContext->last());
|
||||||
|
} else if (!$inContext instanceof Context) {
|
||||||
|
$inContext = new ChildContext($inContext);
|
||||||
|
$inContext->setParent($context);
|
||||||
|
}
|
||||||
|
|
||||||
|
$template->setStopToken('else');
|
||||||
|
$template->discard($inContext);
|
||||||
|
$template->setStopToken(false);
|
||||||
|
$buffer = $template->render($inContext);
|
||||||
|
|
||||||
|
if ($defined) {
|
||||||
|
$inContext->pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
},
|
||||||
|
|
||||||
|
// The current context.
|
||||||
|
'context' => $context,
|
||||||
|
// The current template
|
||||||
|
'template' => $template);
|
||||||
|
|
||||||
|
return call_user_func_array($helper, $args);
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->addHelper($name, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a helper by name.
|
* Remove a helper by name.
|
||||||
*
|
*
|
||||||
@ -284,7 +402,7 @@ class Handlebars
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get current loader
|
* Get current loader
|
||||||
*
|
*
|
||||||
* @return Loader
|
* @return Loader
|
||||||
*/
|
*/
|
||||||
@ -310,7 +428,7 @@ class Handlebars
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get current partials loader
|
* Get current partials loader
|
||||||
*
|
*
|
||||||
* @return Loader
|
* @return Loader
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +33,7 @@ interface Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@ -43,7 +43,7 @@ class BindAttrHelper implements Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@ -45,14 +45,15 @@ class EachHelper implements Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function execute(Template $template, Context $context, $args, $source)
|
public function execute(Template $template, Context $context, $args, $source)
|
||||||
{
|
{
|
||||||
$tmp = $context->get($args);
|
$positionalArgs = $args->getPositionalArguments();
|
||||||
|
$tmp = $context->get($positionalArgs[0]);
|
||||||
$buffer = '';
|
$buffer = '';
|
||||||
|
|
||||||
if (!$tmp) {
|
if (!$tmp) {
|
||||||
|
@ -43,7 +43,7 @@ class IfHelper implements Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@ -43,7 +43,7 @@ class UnlessHelper implements Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@ -43,14 +43,15 @@ class WithHelper implements Helper
|
|||||||
*
|
*
|
||||||
* @param \Handlebars\Template $template The template instance
|
* @param \Handlebars\Template $template The template instance
|
||||||
* @param \Handlebars\Context $context The current context
|
* @param \Handlebars\Context $context The current context
|
||||||
* @param array $args The arguments passed the the helper
|
* @param \Handlebars\Arguments $args The arguments passed the the helper
|
||||||
* @param string $source The source
|
* @param string $source The source
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function execute(Template $template, Context $context, $args, $source)
|
public function execute(Template $template, Context $context, $args, $source)
|
||||||
{
|
{
|
||||||
$context->with($args);
|
$positionalArgs = $args->getPositionalArguments();
|
||||||
|
$context->with($positionalArgs[0]);
|
||||||
$buffer = $template->render($context);
|
$buffer = $template->render($context);
|
||||||
$context->pop();
|
$context->pop();
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ namespace Handlebars;
|
|||||||
/**
|
/**
|
||||||
* Handlebars helpers
|
* Handlebars helpers
|
||||||
*
|
*
|
||||||
* a collection of helper function. normally a function like
|
* A collection of helper function. normally a function like
|
||||||
* function ($sender, $name, $arguments) $arguments is unscaped arguments and
|
* function ($sender, $name, $arguments) $arguments is unscaped arguments and
|
||||||
* is a string, not array
|
* is a string, not array
|
||||||
*
|
*
|
||||||
@ -35,10 +35,11 @@ namespace Handlebars;
|
|||||||
* @version Release: @package_version@
|
* @version Release: @package_version@
|
||||||
* @link http://xamin.ir
|
* @link http://xamin.ir
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Helpers
|
class Helpers
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* Raw helper array
|
||||||
|
*
|
||||||
* @var array array of helpers
|
* @var array array of helpers
|
||||||
*/
|
*/
|
||||||
protected $helpers = array();
|
protected $helpers = array();
|
||||||
@ -147,13 +148,20 @@ class Helpers
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$parsedArgs = new Arguments($args);
|
||||||
if ($this->helpers[$name] instanceof Helper) {
|
if ($this->helpers[$name] instanceof Helper) {
|
||||||
return $this->helpers[$name]->execute(
|
return $this->helpers[$name]->execute(
|
||||||
$template, $context, $args, $source
|
$template, $context, $parsedArgs, $source
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return call_user_func($this->helpers[$name], $template, $context, $args, $source);
|
return call_user_func(
|
||||||
|
$this->helpers[$name],
|
||||||
|
$template,
|
||||||
|
$context,
|
||||||
|
$parsedArgs,
|
||||||
|
$source
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,7 +61,6 @@ class Parser
|
|||||||
* @throws \LogicException when nesting errors or mismatched section tags
|
* @throws \LogicException when nesting errors or mismatched section tags
|
||||||
* are encountered.
|
* are encountered.
|
||||||
* @return array Token parse tree
|
* @return array Token parse tree
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private function _buildTree(\ArrayIterator $tokens)
|
private function _buildTree(\ArrayIterator $tokens)
|
||||||
{
|
{
|
||||||
@ -90,14 +89,20 @@ class Parser
|
|||||||
&& isset($result[Tokenizer::NAME])
|
&& isset($result[Tokenizer::NAME])
|
||||||
&& $result[Tokenizer::NAME] == $token[Tokenizer::NAME]
|
&& $result[Tokenizer::NAME] == $token[Tokenizer::NAME]
|
||||||
) {
|
) {
|
||||||
if (isset($result[Tokenizer::TRIM_RIGHT]) && $result[Tokenizer::TRIM_RIGHT]) {
|
if (isset($result[Tokenizer::TRIM_RIGHT])
|
||||||
// If the start node has trim right, then its equal with the first item in the loop with
|
&& $result[Tokenizer::TRIM_RIGHT]
|
||||||
|
) {
|
||||||
|
// If the start node has trim right, then its equal
|
||||||
|
//with the first item in the loop with
|
||||||
// Trim left
|
// Trim left
|
||||||
$newNodes[0][Tokenizer::TRIM_LEFT] = true;
|
$newNodes[0][Tokenizer::TRIM_LEFT] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($token[Tokenizer::TRIM_RIGHT]) && $token[Tokenizer::TRIM_RIGHT]) {
|
if (isset($token[Tokenizer::TRIM_RIGHT])
|
||||||
//OK, if we have trim right here, we should pass it to the upper level.
|
&& $token[Tokenizer::TRIM_RIGHT]
|
||||||
|
) {
|
||||||
|
//OK, if we have trim right here, we should
|
||||||
|
//pass it to the upper level.
|
||||||
$result[Tokenizer::TRIM_RIGHT] = true;
|
$result[Tokenizer::TRIM_RIGHT] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@ use Handlebars\Arguments;
|
|||||||
class Template
|
class Template
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* Handlebars instance
|
||||||
|
*
|
||||||
* @var Handlebars
|
* @var Handlebars
|
||||||
*/
|
*/
|
||||||
protected $handlebars;
|
protected $handlebars;
|
||||||
@ -58,6 +60,8 @@ class Template
|
|||||||
protected $source = '';
|
protected $source = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Run stack
|
||||||
|
*
|
||||||
* @var array Run stack
|
* @var array Run stack
|
||||||
*/
|
*/
|
||||||
private $_stack = array();
|
private $_stack = array();
|
||||||
@ -108,13 +112,12 @@ class Template
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set stop token for render and discard method
|
* Set stop token for render and discard method
|
||||||
*
|
*
|
||||||
* @param string $token token to set as stop token or false to remove
|
* @param string $token token to set as stop token or false to remove
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function setStopToken($token)
|
public function setStopToken($token)
|
||||||
{
|
{
|
||||||
$topStack = array_pop($this->_stack);
|
$topStack = array_pop($this->_stack);
|
||||||
@ -123,11 +126,10 @@ class Template
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get current stop token
|
* Get current stop token
|
||||||
*
|
*
|
||||||
* @return string|bool
|
* @return string|bool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function getStopToken()
|
public function getStopToken()
|
||||||
{
|
{
|
||||||
$topStack = end($this->_stack);
|
$topStack = end($this->_stack);
|
||||||
@ -175,26 +177,35 @@ class Template
|
|||||||
) {
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) {
|
if (isset($current[Tokenizer::TRIM_LEFT])
|
||||||
|
&& $current[Tokenizer::TRIM_LEFT]
|
||||||
|
) {
|
||||||
$buffer = rtrim($buffer);
|
$buffer = rtrim($buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp = $this->_renderInternal($current, $context);
|
$tmp = $this->_renderInternal($current, $context);
|
||||||
|
|
||||||
if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) {
|
if (isset($current[Tokenizer::TRIM_LEFT])
|
||||||
|
&& $current[Tokenizer::TRIM_LEFT]
|
||||||
|
) {
|
||||||
$tmp = rtrim($tmp);
|
$tmp = rtrim($tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rTrim || (isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT])) {
|
if ($rTrim
|
||||||
|
|| (isset($current[Tokenizer::TRIM_RIGHT])
|
||||||
|
&& $current[Tokenizer::TRIM_RIGHT])
|
||||||
|
) {
|
||||||
$tmp = ltrim($tmp);
|
$tmp = ltrim($tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
$buffer .= $tmp;
|
$buffer .= $tmp;
|
||||||
// Some time, there is more than one string token (first is empty),
|
// Some time, there is more than
|
||||||
|
//one string token (first is empty),
|
||||||
//so we need to trim all of them in one shot
|
//so we need to trim all of them in one shot
|
||||||
|
|
||||||
$rTrim = (empty($tmp) && $rTrim) ||
|
$rTrim = (empty($tmp) && $rTrim) ||
|
||||||
isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT];
|
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.
|
||||||
@ -331,7 +342,9 @@ class Template
|
|||||||
}
|
}
|
||||||
|
|
||||||
// subexpression parsing loop
|
// subexpression parsing loop
|
||||||
$subexprs = array(); // will contain all subexpressions inside outermost brackets
|
// will contain all subexpressions
|
||||||
|
// inside outermost brackets
|
||||||
|
$subexprs = array();
|
||||||
$insideOf = array( 'single' => false, 'double' => false );
|
$insideOf = array( 'single' => false, 'double' => false );
|
||||||
$lvl = 0;
|
$lvl = 0;
|
||||||
$cur_start = 0;
|
$cur_start = 0;
|
||||||
@ -353,7 +366,11 @@ class Template
|
|||||||
if ($cur == ')' && ! $insideOf['single'] && ! $insideOf['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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -372,14 +389,30 @@ class Template
|
|||||||
Tokenizer::INDEX => $current[Tokenizer::INDEX],
|
Tokenizer::INDEX => $current[Tokenizer::INDEX],
|
||||||
Tokenizer::ARGS => implode(" ", array_slice($cmd, 1))
|
Tokenizer::ARGS => implode(" ", array_slice($cmd, 1))
|
||||||
);
|
);
|
||||||
|
|
||||||
// resolve the node recursively
|
// resolve the node recursively
|
||||||
$resolved = addcslashes($this->_handlebarsStyleSection($context, $section_node), '"');
|
$resolved = $this->_handlebarsStyleSection(
|
||||||
|
$context,
|
||||||
|
$section_node
|
||||||
|
);
|
||||||
|
|
||||||
|
$resolved = addcslashes($resolved, '"');
|
||||||
// replace original subexpression with result
|
// replace original subexpression with result
|
||||||
$current[Tokenizer::ARGS] = str_replace('('.$expr.')', '"' . $resolved . '"', $current[Tokenizer::ARGS]);
|
$current[Tokenizer::ARGS] = str_replace(
|
||||||
|
'('.$expr.')',
|
||||||
|
'"' . $resolved . '"',
|
||||||
|
$current[Tokenizer::ARGS]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$return = $helpers->call($sectionName, $this, $context, $current[Tokenizer::ARGS], $source);
|
$return = $helpers->call(
|
||||||
|
$sectionName,
|
||||||
|
$this,
|
||||||
|
$context,
|
||||||
|
$current[Tokenizer::ARGS],
|
||||||
|
$source
|
||||||
|
);
|
||||||
|
|
||||||
if ($return instanceof StringWrapper) {
|
if ($return instanceof StringWrapper) {
|
||||||
return $this->handlebars->loadString($return)->render($context);
|
return $this->handlebars->loadString($return)->render($context);
|
||||||
@ -564,9 +597,9 @@ class Template
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get replacing value of a tag
|
* Get replacing value of a tag
|
||||||
*
|
*
|
||||||
* will process the tag as section, if a helper with the same name could be
|
* Will process the tag as section, if a helper with the same name could be
|
||||||
* found, so {{helper arg}} can be used instead of {{#helper arg}}.
|
* found, so {{helper arg}} can be used instead of {{#helper arg}}.
|
||||||
*
|
*
|
||||||
* @param Context $context current context
|
* @param Context $context current context
|
||||||
|
@ -454,6 +454,201 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase
|
|||||||
$engine->getHelpers()->call('invalid', $engine->loadTemplate(''), new \Handlebars\Context(), '', '');
|
$engine->getHelpers()->call('invalid', $engine->loadTemplate(''), new \Handlebars\Context(), '', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRegisterHelper()
|
||||||
|
{
|
||||||
|
$loader = new \Handlebars\Loader\StringLoader();
|
||||||
|
$engine = new \Handlebars\Handlebars(array('loader' => $loader));
|
||||||
|
//date_default_timezone_set('GMT');
|
||||||
|
|
||||||
|
//FIRST UP: some awesome helpers!!
|
||||||
|
|
||||||
|
//translations
|
||||||
|
$translations = array(
|
||||||
|
'hello' => 'bonjour',
|
||||||
|
'my name is %s' => 'mon nom est %s',
|
||||||
|
'how are your %s kids and %s' => 'comment sont les enfants de votre %s et %s'
|
||||||
|
);
|
||||||
|
|
||||||
|
//i18n
|
||||||
|
$engine->registerHelper('_', function($key) use ($translations) {
|
||||||
|
$args = func_get_args();
|
||||||
|
$key = array_shift($args);
|
||||||
|
$options = array_pop($args);
|
||||||
|
|
||||||
|
//make sure it's a string
|
||||||
|
$key = (string) $key;
|
||||||
|
|
||||||
|
//by default the translation is the key
|
||||||
|
$translation = $key;
|
||||||
|
|
||||||
|
//if there is a translation
|
||||||
|
if(isset($translations[$key])) {
|
||||||
|
//translate it
|
||||||
|
$translation = $translations[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
//if there are more arguments
|
||||||
|
if(!empty($args)) {
|
||||||
|
//it means the translations was
|
||||||
|
//something like 'Hello %s'
|
||||||
|
return vsprintf($translation, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
//just return what we got
|
||||||
|
return $translation;
|
||||||
|
});
|
||||||
|
|
||||||
|
//create a better if helper
|
||||||
|
$engine->registerHelper('when', function($value1, $operator, $value2, $options) {
|
||||||
|
$valid = false;
|
||||||
|
//the amazing reverse switch!
|
||||||
|
switch (true) {
|
||||||
|
case $operator == 'eq' && $value1 == $value2:
|
||||||
|
case $operator == '==' && $value1 == $value2:
|
||||||
|
case $operator == 'req' && $value1 === $value2:
|
||||||
|
case $operator == '===' && $value1 === $value2:
|
||||||
|
case $operator == 'neq' && $value1 != $value2:
|
||||||
|
case $operator == '!=' && $value1 != $value2:
|
||||||
|
case $operator == 'rneq' && $value1 !== $value2:
|
||||||
|
case $operator == '!==' && $value1 !== $value2:
|
||||||
|
case $operator == 'lt' && $value1 < $value2:
|
||||||
|
case $operator == '<' && $value1 < $value2:
|
||||||
|
case $operator == 'lte' && $value1 <= $value2:
|
||||||
|
case $operator == '<=' && $value1 <= $value2:
|
||||||
|
case $operator == 'gt' && $value1 > $value2:
|
||||||
|
case $operator == '>' && $value1 > $value2:
|
||||||
|
case $operator == 'gte' && $value1 >= $value2:
|
||||||
|
case $operator == '>=' && $value1 >= $value2:
|
||||||
|
case $operator == 'and' && $value1 && $value2:
|
||||||
|
case $operator == '&&' && ($value1 && $value2):
|
||||||
|
case $operator == 'or' && ($value1 || $value2):
|
||||||
|
case $operator == '||' && ($value1 || $value2):
|
||||||
|
$valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($valid) {
|
||||||
|
return $options['fn']();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options['inverse']();
|
||||||
|
});
|
||||||
|
|
||||||
|
//a loop helper
|
||||||
|
$engine->registerHelper('loop', function($object, $options) {
|
||||||
|
//expected for subtemplates of this block to use
|
||||||
|
// {{value.profile_name}} vs {{profile_name}}
|
||||||
|
// {{key}} vs {{@index}}
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$buffer = array();
|
||||||
|
$total = count($object);
|
||||||
|
|
||||||
|
//loop through the object
|
||||||
|
foreach($object as $key => $value) {
|
||||||
|
//call the sub template and
|
||||||
|
//add it to the buffer
|
||||||
|
$buffer[] = $options['fn'](array(
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $value,
|
||||||
|
'last' => ++$i === $total
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', $buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
//array in
|
||||||
|
$engine->registerHelper('in', function(array $array, $key, $options) {
|
||||||
|
if(in_array($key, $array)) {
|
||||||
|
return $options['fn']();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options['inverse']();
|
||||||
|
});
|
||||||
|
|
||||||
|
//converts date formats to other formats
|
||||||
|
$engine->registerHelper('date', function($time, $format, $options) {
|
||||||
|
return date($format, strtotime($time));
|
||||||
|
});
|
||||||
|
|
||||||
|
//nesting helpers, these don't really help anyone :)
|
||||||
|
$engine->registerHelper('nested1', function($test1, $test2, $options) {
|
||||||
|
return $options['fn'](array(
|
||||||
|
'test4' => $test1,
|
||||||
|
'test5' => 'This is Test 5'
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
$engine->registerHelper('nested2', function($options) {
|
||||||
|
return $options['fn'](array('test6' => 'This is Test 6'));
|
||||||
|
});
|
||||||
|
|
||||||
|
//NEXT UP: some practical case studies
|
||||||
|
|
||||||
|
//case 1 - i18n
|
||||||
|
$variable1 = array();
|
||||||
|
$template1 = "{{_ 'hello'}}, {{_ 'my name is %s' 'Foo'}}! {{_ 'how are your %s kids and %s' 6 'dog'}}?";
|
||||||
|
$expected1 = 'bonjour, mon nom est Foo! comment sont les enfants de votre 6 et dog?';
|
||||||
|
|
||||||
|
//case 2 - when
|
||||||
|
$variable2 = array('gender' => 'female');
|
||||||
|
$template2 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}";
|
||||||
|
$expected2 = 'Hello maam';
|
||||||
|
|
||||||
|
//case 3 - when else
|
||||||
|
$variable3 = array('gender' => 'male');
|
||||||
|
$template3 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}";
|
||||||
|
$expected3 = 'Hello sir';
|
||||||
|
|
||||||
|
//case 4 - loop
|
||||||
|
$variable4 = array(
|
||||||
|
'rows' => array(
|
||||||
|
array(
|
||||||
|
'profile_name' => 'Jane Doe',
|
||||||
|
'profile_created' => '2014-04-04 00:00:00'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'profile_name' => 'John Doe',
|
||||||
|
'profile_created' => '2015-01-21 00:00:00'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$template4 = "{{#loop rows}}<li>{{value.profile_name}} - {{date value.profile_created 'M d'}}</li>{{/loop}}";
|
||||||
|
$expected4 = '<li>Jane Doe - Apr 04</li><li>John Doe - Jan 21</li>';
|
||||||
|
|
||||||
|
//case 5 - array in
|
||||||
|
$variable5 = $variable4;
|
||||||
|
$variable5['me'] = 'Jack Doe';
|
||||||
|
$variable5['admins'] = array('Jane Doe', 'John Doe');
|
||||||
|
$template5 = "{{#in admins me}}<ul>".$template4."</ul>{{else}}No Access{{/in}}";
|
||||||
|
$expected5 = 'No Access';
|
||||||
|
|
||||||
|
//case 6 - array in else
|
||||||
|
$variable6 = $variable5;
|
||||||
|
$variable6['me'] = 'Jane Doe';
|
||||||
|
$template6 = $template5;
|
||||||
|
$expected6 = '<ul><li>Jane Doe - Apr 04</li><li>John Doe - Jan 21</li></ul>';
|
||||||
|
|
||||||
|
//case 7 - nested templates and parent-grand variables
|
||||||
|
$variable7 = array('test' => 'Hello World');
|
||||||
|
$template7 = '{{#nested1 test "test2"}} '
|
||||||
|
.'In 1: {{test4}} {{#nested1 ../test \'test3\'}} '
|
||||||
|
.'In 2: {{test5}}{{#nested2}} '
|
||||||
|
.'In 3: {{test6}} {{../../../test}}{{/nested2}}{{/nested1}}{{/nested1}}';
|
||||||
|
$expected7 = ' In 1: Hello World In 2: This is Test 5 In 3: This is Test 6 Hello World';
|
||||||
|
|
||||||
|
//LAST UP: the actual testing
|
||||||
|
|
||||||
|
$this->assertEquals($expected1, $engine->render($template1, $variable1));
|
||||||
|
$this->assertEquals($expected2, $engine->render($template2, $variable2));
|
||||||
|
$this->assertEquals($expected3, $engine->render($template3, $variable3));
|
||||||
|
$this->assertEquals($expected4, $engine->render($template4, $variable4));
|
||||||
|
$this->assertEquals($expected5, $engine->render($template5, $variable5));
|
||||||
|
$this->assertEquals($expected6, $engine->render($template6, $variable6));
|
||||||
|
$this->assertEquals($expected7, $engine->render($template7, $variable7));
|
||||||
|
}
|
||||||
|
|
||||||
public function testInvalidHelper()
|
public function testInvalidHelper()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('RuntimeException');
|
$this->setExpectedException('RuntimeException');
|
||||||
|
Loading…
Reference in New Issue
Block a user