Merge pull request #164 from mAAdhaTTah/bugfix/mustache-context-switch

Fix context switching for mustache blocks
This commit is contained in:
Behrooz Shabani 2016-12-27 10:15:36 +01:00 committed by GitHub
commit 38c743f79b
3 changed files with 69 additions and 35 deletions

View File

@ -73,7 +73,7 @@ class Context
protected $key = array(); protected $key = array();
/** /**
* Special variables stack for sections. * Special variables stack for sections.
* *
* @var array Each stack element can * @var array Each stack element can
* contain elements with "@index", "@key", "@first" and "@last" keys. * contain elements with "@index", "@key", "@first" and "@last" keys.
@ -186,7 +186,7 @@ class Context
* @param boolean $strict strict search? if not found then throw exception * @param boolean $strict strict search? if not found then throw exception
* *
* @throws \InvalidArgumentException in strict mode and variable not found * @throws \InvalidArgumentException in strict mode and variable not found
* @throws \RuntimeException if supplied argument is a malformed quoted string * @throws \RuntimeException if supplied argument is a malformed quoted string
* @throws \InvalidArgumentException if variable name is invalid * @throws \InvalidArgumentException if variable name is invalid
* @return mixed * @return mixed
*/ */
@ -252,12 +252,17 @@ class Context
} }
} else { } else {
$chunks = $this->_splitVariableName($variableName); $chunks = $this->_splitVariableName($variableName);
foreach ($chunks as $chunk) { do {
if (is_string($current) and $current == '') { $current = current($this->stack);
return $current; foreach ($chunks as $chunk) {
if (is_string($current) and $current == '') {
return $current;
}
$current = $this->_findVariableInContext($current, $chunk, $strict);
} }
$current = $this->_findVariableInContext($current, $chunk, $strict); prev($this->stack);
}
} while ($current === null && current($this->stack) !== false);
} }
return $current; return $current;
} }
@ -316,18 +321,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 = "(?:[^" $name_pattern = "(?:[^"
. $bad_chars . $bad_chars
. "\s]+)|(?:\[[^" . "\s]+)|(?:\[[^"
. $bad_seg_chars . $bad_seg_chars
. "]+\])"; . "]+\])";
$check_pattern = "/^((" $check_pattern = "/^(("
. $name_pattern . $name_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)) {

View File

@ -24,6 +24,7 @@
namespace Handlebars; namespace Handlebars;
use Handlebars\Arguments; use Handlebars\Arguments;
use Traversable;
/** /**
* Handlebars base template * Handlebars base template
@ -447,35 +448,29 @@ class Template
); );
} }
$buffer = ''; $buffer = '';
if (is_array($sectionVar) || $sectionVar instanceof \Traversable) { if ($this->_checkIterable($sectionVar)) {
$isList = is_array($sectionVar) &&
(array_keys($sectionVar) === range(0, count($sectionVar) - 1));
$index = 0; $index = 0;
$lastIndex = $isList ? (count($sectionVar) - 1) : false; $lastIndex = (count($sectionVar) - 1);
foreach ($sectionVar as $key => $d) { foreach ($sectionVar as $key => $d) {
$specialVariables = array( $context->pushSpecialVariables(
'@index' => $index, array(
'@first' => ($index === 0), '@index' => $index,
'@last' => ($index === $lastIndex), '@first' => ($index === 0),
'@last' => ($index === $lastIndex),
'@key' => $key
)
); );
if (!$isList) {
$specialVariables['@key'] = $key;
}
$context->pushSpecialVariables($specialVariables);
$context->push($d); $context->push($d);
$buffer .= $this->render($context); $buffer .= $this->render($context);
$context->pop(); $context->pop();
$context->popSpecialVariables(); $context->popSpecialVariables();
$index++; $index++;
} }
} elseif (is_object($sectionVar)) { } elseif ($sectionVar) {
//Act like with //Act like with
$context->push($sectionVar); $context->push($sectionVar);
$buffer = $this->render($context); $buffer = $this->render($context);
$context->pop(); $context->pop();
} elseif ($sectionVar) {
$buffer = $this->render($context);
} }
return $buffer; return $buffer;
@ -712,4 +707,31 @@ class Template
return $args->getPositionalArguments(); return $args->getPositionalArguments();
} }
/**
* Tests whether a value should be iterated over (e.g. in a section context).
*
* @param mixed $value Value to check if iterable.
*
* @return bool True if the value is 'iterable'
*
* @see https://github.com/bobthecow/mustache.php/blob/18a2adc/src/Mustache/Template.php#L85-L113
*/
private function _checkIterable($value)
{
switch (gettype($value)) {
case 'object':
return $value instanceof Traversable;
case 'array':
$i = 0;
foreach ($value as $k => $v) {
if ($k !== $i++) {
return false;
}
}
return true;
default:
return false;
}
}
} }

View File

@ -689,12 +689,18 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('', $engine->render('{{^x}}yes{{/x}}', array('x' => true))); $this->assertEquals('', $engine->render('{{^x}}yes{{/x}}', array('x' => true)));
$this->assertEquals('1234', $engine->render('{{#x}}{{this}}{{/x}}', array('x' => array(1, 2, 3, 4)))); $this->assertEquals('1234', $engine->render('{{#x}}{{this}}{{/x}}', array('x' => array(1, 2, 3, 4))));
$this->assertEquals('012', $engine->render('{{#x}}{{@index}}{{/x}}', array('x' => array('a', 'b', 'c')))); $this->assertEquals('012', $engine->render('{{#x}}{{@index}}{{/x}}', array('x' => array('a', 'b', 'c'))));
$this->assertEquals('abc', $engine->render('{{#x}}{{@key}}{{/x}}', array('x' => array('a' => 1, 'b' => 2, 'c' => 3)))); $this->assertEquals('123', $engine->render('{{#x}}{{a}}{{b}}{{c}}{{/x}}', array('x' => array('a' => 1, 'b' => 2, 'c' => 3))));
$this->assertEquals('the_only_key', $engine->render('{{#x}}{{@key}}{{/x}}', array('x' => array('the_only_key' => 1)))); $this->assertEquals('1', $engine->render('{{#x}}{{the_only_key}}{{/x}}', array('x' => array('the_only_key' => 1))));
$std = new stdClass(); $std = new stdClass();
$std->value = 1; $std->value = 1;
$std->other = 4;
$this->assertEquals('1', $engine->render('{{#x}}{{value}}{{/x}}', array('x' => $std))); $this->assertEquals('1', $engine->render('{{#x}}{{value}}{{/x}}', array('x' => $std)));
$this->assertEquals('1', $engine->render('{{{x}}}', array('x' => 1))); $this->assertEquals('1', $engine->render('{{{x}}}', array('x' => 1)));
$this->assertEquals('1 2', $engine->render('{{#x}}{{value}} {{parent}}{{/x}}', array('x' => $std, 'parent' => 2)));
$y = new stdClass();
$y->value = 2;
$this->assertEquals('2 1 3 4', $engine->render('{{#x}}{{#y}}{{value}} {{x.value}} {{from_root}} {{other}}{{/y}}{{/x}}', array('x' => $std, 'y' => $y, 'from_root' => 3)));
} }
/** /**
@ -962,6 +968,7 @@ EOM;
$this->setExpectedException('RuntimeException'); $this->setExpectedException('RuntimeException');
$engine->render('{{>foo-again}}', array()); $engine->render('{{>foo-again}}', array());
} }
/** /**