mirror of
https://github.com/Mibew/handlebars.php.git
synced 2025-04-08 08:20:10 +03:00
Should return null instead of an empty string when context is not found. Handlebars.js returns undefined, which makes it possible to distict an empty string and not existing. (should not affect existing use, because null is handeled the same way as empty string)
316 lines
9.0 KiB
PHP
316 lines
9.0 KiB
PHP
<?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>
|
|
* @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 Context
|
|
{
|
|
|
|
/**
|
|
* List of charcters that cannot be used in identifiers.
|
|
*/
|
|
const NOT_VALID_NAME_CHARS = '!"#%&\'()*+,./;<=>@[\\]^`{|}~';
|
|
|
|
/**
|
|
* List of characters that cannot be used in identifiers in segment-literal
|
|
* notation.
|
|
*/
|
|
const NOT_VALID_SEGMENT_NAME_CHARS = "]";
|
|
|
|
/**
|
|
* @var array stack for context only top stack is available
|
|
*/
|
|
protected $stack = array();
|
|
|
|
/**
|
|
* @var array index stack for sections
|
|
*/
|
|
protected $index = array();
|
|
|
|
/**
|
|
* @var array key stack for objects
|
|
*/
|
|
protected $key = array();
|
|
|
|
/**
|
|
* @var array Special variables stack for sections. Each stack element can
|
|
* contain elements with "@index", "@key", "@first" and "@last" keys.
|
|
*/
|
|
protected $specialVariables = array();
|
|
|
|
/**
|
|
* Mustache rendering Context constructor.
|
|
*
|
|
* @param mixed $context Default rendering context (default: null)
|
|
*/
|
|
public function __construct($context = null)
|
|
{
|
|
if ($context !== null) {
|
|
$this->stack = array($context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push a new Context frame onto the stack.
|
|
*
|
|
* @param mixed $value Object or array to use for context
|
|
*
|
|
* @return void
|
|
*/
|
|
public function push($value)
|
|
{
|
|
array_push($this->stack, $value);
|
|
}
|
|
|
|
/**
|
|
* Push an array of special variables to stack.
|
|
*
|
|
* @param array $variables An associative array of special variables.
|
|
*
|
|
* @return void
|
|
*
|
|
* @see \Handlebars\Context::$specialVariables
|
|
*/
|
|
public function pushSpecialVariables($variables)
|
|
{
|
|
array_push($this->specialVariables, $variables);
|
|
}
|
|
|
|
/**
|
|
* Pop the last Context frame from the stack.
|
|
*
|
|
* @return mixed Last Context frame (object or array)
|
|
*/
|
|
public function pop()
|
|
{
|
|
return array_pop($this->stack);
|
|
}
|
|
|
|
/**
|
|
* Pop the last special variables set from the stack.
|
|
*
|
|
* @return array Associative array of special variables.
|
|
*
|
|
* @see \Handlebars\Context::$specialVariables
|
|
*/
|
|
public function popSpecialVariables()
|
|
{
|
|
return array_pop($this->specialVariables);
|
|
}
|
|
|
|
/**
|
|
* Get the last Context frame.
|
|
*
|
|
* @return mixed Last Context frame (object or array)
|
|
*/
|
|
public function last()
|
|
{
|
|
return end($this->stack);
|
|
}
|
|
|
|
/**
|
|
* Get the last special variables set from the stack.
|
|
*
|
|
* @return array Associative array of special variables.
|
|
*
|
|
* @see \Handlebars\Context::$specialVariables
|
|
*/
|
|
public function lastSpecialVariables()
|
|
{
|
|
return end($this->specialVariables);
|
|
}
|
|
|
|
/**
|
|
* Change the current context to one of current context members
|
|
*
|
|
* @param string $variableName name of variable or a callable on current context
|
|
*
|
|
* @return mixed actual value
|
|
*/
|
|
public function with($variableName)
|
|
{
|
|
$value = $this->get($variableName);
|
|
$this->push($value);
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* 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 ($variableName instanceof \Handlebars\String) {
|
|
return (string)$variableName;
|
|
}
|
|
$variableName = trim($variableName);
|
|
$level = 0;
|
|
while (substr($variableName, 0, 3) == '../') {
|
|
$variableName = trim(substr($variableName, 3));
|
|
$level++;
|
|
}
|
|
if (count($this->stack) < $level) {
|
|
if ($strict) {
|
|
throw new \InvalidArgumentException(
|
|
'can not find variable in context'
|
|
);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
end($this->stack);
|
|
while ($level) {
|
|
prev($this->stack);
|
|
$level--;
|
|
}
|
|
$current = current($this->stack);
|
|
if (!$variableName) {
|
|
if ($strict) {
|
|
throw new \InvalidArgumentException(
|
|
'can not find variable in context'
|
|
);
|
|
}
|
|
return '';
|
|
} elseif ($variableName == '.' || $variableName == 'this') {
|
|
return $current;
|
|
} elseif ($variableName[0] == '@') {
|
|
$specialVariables = $this->lastSpecialVariables();
|
|
if (isset($specialVariables[$variableName])) {
|
|
return $specialVariables[$variableName];
|
|
} elseif ($strict) {
|
|
throw new \InvalidArgumentException(
|
|
'can not find variable in context'
|
|
);
|
|
} else {
|
|
return '';
|
|
}
|
|
} else {
|
|
$chunks = $this->_splitVariableName($variableName);
|
|
foreach ($chunks as $chunk) {
|
|
if (is_string($current) and $current == '') {
|
|
return $current;
|
|
}
|
|
$current = $this->_findVariableInContext($current, $chunk, $strict);
|
|
}
|
|
}
|
|
return $current;
|
|
}
|
|
|
|
/**
|
|
* Check if $variable->$inside is available
|
|
*
|
|
* @param mixed $variable variable to check
|
|
* @param string $inside property/method to check
|
|
* @param boolean $strict strict search? if not found then throw exception
|
|
*
|
|
* @throws \InvalidArgumentException in strict mode and variable not found
|
|
* @return boolean true if exist
|
|
*/
|
|
private function _findVariableInContext($variable, $inside, $strict = false)
|
|
{
|
|
$value = null;
|
|
if (($inside !== '0' && empty($inside)) || ($inside == 'this')) {
|
|
return $variable;
|
|
} elseif (is_array($variable)) {
|
|
if (isset($variable[$inside]) || array_key_exists($inside, $variable)) {
|
|
return $variable[$inside];
|
|
} elseif ($inside == "length") {
|
|
return count($variable);
|
|
}
|
|
} elseif (is_object($variable)) {
|
|
if (isset($variable->$inside)) {
|
|
return $variable->$inside;
|
|
} elseif (is_callable(array($variable, $inside))) {
|
|
return call_user_func(array($variable, $inside));
|
|
}
|
|
}
|
|
|
|
if ($strict) {
|
|
throw new \InvalidArgumentException('can not find variable in context');
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Splits variable name to chunks.
|
|
*
|
|
* @param string $variableName Fully qualified name of a variable.
|
|
*
|
|
* @throws \InvalidArgumentException if variable name is invalid.
|
|
* @return array
|
|
*/
|
|
private function _splitVariableName($variableName)
|
|
{
|
|
$bad_chars = preg_quote(self::NOT_VALID_NAME_CHARS, '/');
|
|
$bad_seg_chars = preg_quote(self::NOT_VALID_SEGMENT_NAME_CHARS, '/');
|
|
|
|
$name_pattern = "(?:[^" . $bad_chars . "\s]+)|(?:\[[^" . $bad_seg_chars . "]+\])";
|
|
$check_pattern = "/^((" . $name_pattern . ")\.)*(" . $name_pattern . ")\.?$/";
|
|
$get_pattern = "/(?:" . $name_pattern . ")/";
|
|
|
|
if (!preg_match($check_pattern, $variableName)) {
|
|
throw new \InvalidArgumentException('variable name is invalid');
|
|
}
|
|
|
|
preg_match_all($get_pattern, $variableName, $matches);
|
|
|
|
$chunks = array();
|
|
foreach ($matches[0] as $chunk) {
|
|
// Remove wrapper braces if needed
|
|
if ($chunk[0] == '[') {
|
|
$chunk = substr($chunk, 1, -1);
|
|
}
|
|
$chunks[] = $chunk;
|
|
}
|
|
|
|
return $chunks;
|
|
}
|
|
|
|
}
|