diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index 563316e..de43fcd 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -127,7 +127,11 @@ class Mustache_Parser throw new Mustache_Exception_SyntaxException($msg, $token); } - if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) { + $sameName = $token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]; + $tokenDynamic = isset($token[Mustache_Tokenizer::DYNAMIC]) && $token[Mustache_Tokenizer::DYNAMIC]; + $parentDynamic = isset($parent[Mustache_Tokenizer::DYNAMIC]) && $parent[Mustache_Tokenizer::DYNAMIC]; + + if ($sameName || ($tokenDynamic !== $parentDynamic)) { $msg = sprintf( 'Nesting error: %s (on line %d) vs. %s (on line %d)', $parent[Mustache_Tokenizer::NAME], @@ -330,6 +334,7 @@ class Mustache_Parser switch ($token[Mustache_Tokenizer::TYPE]) { case Mustache_Tokenizer::T_PARTIAL: case Mustache_Tokenizer::T_PARENT: + case Mustache_Tokenizer::T_END_SECTION: return; } diff --git a/test/Mustache/Test/Functional/DynamicPartialsTest.php b/test/Mustache/Test/Functional/DynamicPartialsTest.php new file mode 100644 index 0000000..9887132 --- /dev/null +++ b/test/Mustache/Test/Functional/DynamicPartialsTest.php @@ -0,0 +1,114 @@ +mustache = new Mustache_Engine(array( + 'pragmas' => array(Mustache_Engine::PRAGMA_DYNAMIC_NAMES), + )); + } + + public function getInvalidDynamicNamesExamples() + { + return array( + array('{{> **foo}}'), + array('{{> *foo.*bar}}'), + array('{{> foo.*bar}}'), + array('{{ *foo }}'), + array('{{{ *foo }}}'), + array('{{& *foo }}'), + array('{{# *foo }}{{/ *foo }}'), + array('{{^ *foo }}{{/ *foo }}'), + array('{{% FILTERS}}{{> *foo | *bar}}'), + array('{{% FILTERS}}{{> foo | *bar}}'), + array('{{% BLOCKS }}{{$ *foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getInvalidDynamicNamesExamples + * @expectedException Mustache_Exception_SyntaxException + * @expectedExceptionMessage Invalid dynamic name: + */ + public function testInvalidDynamicNamesExamples($template) + { + $this->mustache->render($template); + } + + + public function getValidDynamicNamesExamples() + { + // technically not all dynamic names, but also not invalid + return array( + array('{{>* foo }}'), + array('{{>* foo.bar.baz }}'), + array('{{=* *=}}'), + array('{{! *foo }}'), + array('{{! foo.*bar }}'), + array('{{% FILTERS }}{{! foo | *bar }}'), + array('{{% BLOCKS }}{{< *foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getValidDynamicNamesExamples + */ + public function testLegalInheritanceExamples($template) + { + $this->assertSame('', $this->mustache->render($template)); + } + + public function getDynamicNameParseErrors() + { + return array( + array('{{# foo }}{{/ *foo }}'), + array('{{^ foo }}{{/ *foo }}'), + array('{{% BLOCKS }}{{< foo }}{{/ *foo }}'), + array('{{% BLOCKS }}{{$ foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getDynamicNameParseErrors + * @expectedException Mustache_Exception_SyntaxException + * @expectedExceptionMessage Nesting error: + */ + public function testDynamicNameParseErrors($template) + { + $this->mustache->render($template); + } + + + public function testDynamicBlocks() + { + $tpl = '{{% BLOCKS }}{{< *partial }}{{$ bar }}{{ value }}{{/ bar }}{{/ *partial }}'; + + $this->mustache->setPartials(array( + 'foobarbaz' => '{{% BLOCKS }}{{$ foo }}foo{{/ foo }}{{$ bar }}bar{{/ bar }}{{$ baz }}baz{{/ baz }}', + 'qux' => 'qux', + )); + + $result = $this->mustache->render($tpl, array( + 'partial' => 'foobarbaz', + 'value' => 'BAR', + )); + + $this->assertSame($result, 'fooBARbaz'); + } +}