From d7a9844e93ec86337dc39ecab7ddb119c882178f Mon Sep 17 00:00:00 2001 From: majortom731 Date: Wed, 12 Mar 2014 16:59:54 +0100 Subject: [PATCH 1/9] Changed the internal 'if' helper such that it can test not only paths/variable names, but also numeric constants. This will be helpful for subexpressions later, when testing numeric subexpressions return values, eg: {{#if (eq 0 (mod @index 3))}} Also added a test for this. --- tests/Xamin/HandlebarsTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 0736591..6ad6644 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -248,6 +248,16 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase '{{#bindAttr data}}', array(), 'data' + ), + array( + '{{#if 1}}ok{{else}}fail{{/if}}', + array(), + 'ok' + ), + array( + '{{#if 0}}ok{{else}}fail{{/if}}', + array(), + 'fail' ) ); From 5c3ea257b71e7970430a87d876ec225471fc0680 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Wed, 12 Mar 2014 17:00:09 +0100 Subject: [PATCH 2/9] Changed the internal 'if' helper such that it can test not only paths/variable names, but also numeric constants. This will be helpful for subexpressions later, when testing numeric subexpressions return values, eg: {{#if (eq 0 (mod @index 3))}} Also added a test for this. --- src/Handlebars/Helpers.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Handlebars/Helpers.php b/src/Handlebars/Helpers.php index 625e785..3ff781e 100644 --- a/src/Handlebars/Helpers.php +++ b/src/Handlebars/Helpers.php @@ -88,7 +88,11 @@ class Helpers * @var $args array * @var $source string */ - $tmp = $context->get($args); + if( is_numeric($args) ) { + $tmp = $args; + } else { + $tmp = $context->get($args); + } if ($tmp) { $template->setStopToken('else'); From 3fc92fcdc569dd6276c4ab4ca66d30746ee8cc2e Mon Sep 17 00:00:00 2001 From: majortom731 Date: Wed, 12 Mar 2014 19:33:46 +0100 Subject: [PATCH 3/9] Added support for subexpressions in helper calls. Not sure if Template.php is the best place to handle subexpressions... --- src/Handlebars/Template.php | 150 +++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 35 deletions(-) diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index 0e8a777..0fd0a17 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -11,6 +11,7 @@ * @author Behrooz Shabani * @author Chris Gray * @author Dmitriy Simushev + * @author majortom731 * @copyright 2012 (c) ParsPooyesh Co * @copyright 2013 (c) Behrooz Shabani * @license MIT @@ -151,42 +152,42 @@ class Template break; } switch ($current[Tokenizer::TYPE]) { - case Tokenizer::T_SECTION : - $newStack = isset($current[Tokenizer::NODES]) - ? $current[Tokenizer::NODES] : array(); - array_push($this->_stack, array(0, $newStack, false)); - $buffer .= $this->_section($context, $current); - array_pop($this->_stack); - break; - case Tokenizer::T_INVERTED : - $newStack = isset($current[Tokenizer::NODES]) ? - $current[Tokenizer::NODES] : array(); - array_push($this->_stack, array(0, $newStack, false)); - $buffer .= $this->_inverted($context, $current); - array_pop($this->_stack); - break; - case Tokenizer::T_COMMENT : - $buffer .= ''; - break; - case Tokenizer::T_PARTIAL: - case Tokenizer::T_PARTIAL_2: - $buffer .= $this->_partial($context, $current); - break; - case Tokenizer::T_UNESCAPED: - case Tokenizer::T_UNESCAPED_2: - $buffer .= $this->_get($context, $current, false); - break; - case Tokenizer::T_ESCAPED: + case Tokenizer::T_SECTION : + $newStack = isset($current[Tokenizer::NODES]) + ? $current[Tokenizer::NODES] : array(); + array_push($this->_stack, array(0, $newStack, false)); + $buffer .= $this->_section($context, $current); + array_pop($this->_stack); + break; + case Tokenizer::T_INVERTED : + $newStack = isset($current[Tokenizer::NODES]) ? + $current[Tokenizer::NODES] : array(); + array_push($this->_stack, array(0, $newStack, false)); + $buffer .= $this->_inverted($context, $current); + array_pop($this->_stack); + break; + case Tokenizer::T_COMMENT : + $buffer .= ''; + break; + case Tokenizer::T_PARTIAL: + case Tokenizer::T_PARTIAL_2: + $buffer .= $this->_partial($context, $current); + break; + case Tokenizer::T_UNESCAPED: + case Tokenizer::T_UNESCAPED_2: + $buffer .= $this->_get($context, $current, false); + break; + case Tokenizer::T_ESCAPED: - $buffer .= $this->_get($context, $current, true); - break; - case Tokenizer::T_TEXT: - $buffer .= $current[Tokenizer::VALUE]; - break; - default: - throw new \RuntimeException( - 'Invalid node type : ' . json_encode($current) - ); + $buffer .= $this->_get($context, $current, true); + break; + case Tokenizer::T_TEXT: + $buffer .= $current[Tokenizer::VALUE]; + break; + default: + throw new \RuntimeException( + 'Invalid node type : ' . json_encode($current) + ); } } if ($stop) { @@ -272,6 +273,42 @@ class Template $source ); + // subexpression parsing loop + $subexprs = array(); // will contain all subexpressions inside outermost brackets + $lvl = 0; + $cur_start = 0; + for( $i=0; $i < strlen($params[2]); $i++ ) { + $cur = substr( $params[2], $i, 1 ); + if( $cur == '(' ) { + if( $lvl == 0 ) $cur_start = $i+1; + $lvl++; + } + if( $cur == ')' ) { + $lvl--; + if( $lvl == 0 ) $subexprs[] = substr( $params[2], $cur_start, $i - $cur_start); + } + } + + if( ! empty( $subexprs ) ) { + foreach( $subexprs as $expr ) { + $cmd = explode( " ", $expr ); + $name = trim( $cmd[0] ); + // construct artificial section node + $section_node = array( + Tokenizer::TYPE => Tokenizer::T_ESCAPED, + Tokenizer::NAME => $name, + Tokenizer::OTAG => $current[Tokenizer::OTAG], + Tokenizer::CTAG => $current[Tokenizer::CTAG], + Tokenizer::INDEX => $current[Tokenizer::INDEX], + Tokenizer::ARGS => implode( " ", array_slice( $cmd, 1 ) ) + ); + // resolve the node recursively + $resolved = $this->_handlebarsStyleSection( $context, $section_node ); + // replace original subexpression with result + $params[2] = str_replace( '('.$expr.')', $resolved, $params[2] ); + } + } + $return = call_user_func_array($helpers->$sectionName, $params); if ($return instanceof String) { return $this->handlebars->loadString($return)->render($context); @@ -280,6 +317,49 @@ class Template } } + /** + * Process handlebars subexpression + * + * @param Context $context current context + * @param array $current section node data + * + * @return mixed|string + */ + public function _handlebarsSubexpression(Context $context, $current) + { + $helpers = $this->handlebars->getHelpers(); + $sectionName = $current[Tokenizer::NAME]; + + if (isset($current[Tokenizer::END])) { + $source = substr( + $this->getSource(), + $current[Tokenizer::INDEX], + $current[Tokenizer::END] - $current[Tokenizer::INDEX] + ); + } else { + $source = ''; + } + $params = array( + $this, //First argument is this template + $context, //Second is current context + $current[Tokenizer::ARGS], //Arguments + $source + ); + +// echo "Context: ", "\n"; +// print_r( $params[1] ); + echo "arguments: ", $params[2], "\n"; + echo "src: ", $params[3], "\n"; + + $return = call_user_func_array($helpers->$sectionName, $params); + echo "result: ", $return, "\n\n"; + if ($return instanceof String) { + return $return; + } else { + return $return; + } + } + /** * Process Mustache section style * From 4bc443e834c9945de84fb42eb6ad756fe8cca5e3 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Wed, 12 Mar 2014 19:34:15 +0100 Subject: [PATCH 4/9] Added testHelperSubexpressions() --- tests/Xamin/HandlebarsTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 6ad6644..75dc2ec 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -774,4 +774,27 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase } + /** + * Helper subexpressions test + */ + public function testHelperSubexpressions() + { + $loader = new \Handlebars\Loader\StringLoader(); + $engine = new \Handlebars\Handlebars(array('loader' => $loader)); + $engine->addHelper('test', function ($template, $context, $arg) { + return $arg.'Test.'; + }); + + // assert that nested syntax is accepted and sub-helper is run + $this->assertEquals('Test.Test.', $engine->render('{{test (test)}}', array())); + + $engine->addHelper('add', function ($template, $context, $arg) { + $values = explode( " ", $arg ); + return $values[0] + $values[1]; + }); + + // assert that subexpression result is inserted correctly as argument to top level helper + $this->assertEquals('42', $engine->render('{{add 21 (add 10 (add 5 6))}}', array())); + } + } \ No newline at end of file From 41722af86ded2af648996770f1663cd49d1bef81 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Wed, 12 Mar 2014 19:39:05 +0100 Subject: [PATCH 5/9] Added testHelperSubexpressions() --- src/Handlebars/Template.php | 43 ------------------------------------- 1 file changed, 43 deletions(-) diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index 0fd0a17..1352b15 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -317,49 +317,6 @@ class Template } } - /** - * Process handlebars subexpression - * - * @param Context $context current context - * @param array $current section node data - * - * @return mixed|string - */ - public function _handlebarsSubexpression(Context $context, $current) - { - $helpers = $this->handlebars->getHelpers(); - $sectionName = $current[Tokenizer::NAME]; - - if (isset($current[Tokenizer::END])) { - $source = substr( - $this->getSource(), - $current[Tokenizer::INDEX], - $current[Tokenizer::END] - $current[Tokenizer::INDEX] - ); - } else { - $source = ''; - } - $params = array( - $this, //First argument is this template - $context, //Second is current context - $current[Tokenizer::ARGS], //Arguments - $source - ); - -// echo "Context: ", "\n"; -// print_r( $params[1] ); - echo "arguments: ", $params[2], "\n"; - echo "src: ", $params[3], "\n"; - - $return = call_user_func_array($helpers->$sectionName, $params); - echo "result: ", $return, "\n\n"; - if ($return instanceof String) { - return $return; - } else { - return $return; - } - } - /** * Process Mustache section style * From 525770166949a8aacb3af346157eb82ae68c42d9 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Mon, 17 Mar 2014 16:41:54 +0100 Subject: [PATCH 6/9] Fixed indentation / code style --- src/Handlebars/Helpers.php | 2 +- src/Handlebars/Template.php | 94 ++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/Handlebars/Helpers.php b/src/Handlebars/Helpers.php index 3ff781e..485c90f 100644 --- a/src/Handlebars/Helpers.php +++ b/src/Handlebars/Helpers.php @@ -88,7 +88,7 @@ class Helpers * @var $args array * @var $source string */ - if( is_numeric($args) ) { + if ( is_numeric($args) ) { $tmp = $args; } else { $tmp = $context->get($args); diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index 1352b15..4940be7 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -152,42 +152,42 @@ class Template break; } switch ($current[Tokenizer::TYPE]) { - case Tokenizer::T_SECTION : - $newStack = isset($current[Tokenizer::NODES]) - ? $current[Tokenizer::NODES] : array(); - array_push($this->_stack, array(0, $newStack, false)); - $buffer .= $this->_section($context, $current); - array_pop($this->_stack); - break; - case Tokenizer::T_INVERTED : - $newStack = isset($current[Tokenizer::NODES]) ? - $current[Tokenizer::NODES] : array(); - array_push($this->_stack, array(0, $newStack, false)); - $buffer .= $this->_inverted($context, $current); - array_pop($this->_stack); - break; - case Tokenizer::T_COMMENT : - $buffer .= ''; - break; - case Tokenizer::T_PARTIAL: - case Tokenizer::T_PARTIAL_2: - $buffer .= $this->_partial($context, $current); - break; - case Tokenizer::T_UNESCAPED: - case Tokenizer::T_UNESCAPED_2: - $buffer .= $this->_get($context, $current, false); - break; - case Tokenizer::T_ESCAPED: + case Tokenizer::T_SECTION : + $newStack = isset($current[Tokenizer::NODES]) + ? $current[Tokenizer::NODES] : array(); + array_push($this->_stack, array(0, $newStack, false)); + $buffer .= $this->_section($context, $current); + array_pop($this->_stack); + break; + case Tokenizer::T_INVERTED : + $newStack = isset($current[Tokenizer::NODES]) ? + $current[Tokenizer::NODES] : array(); + array_push($this->_stack, array(0, $newStack, false)); + $buffer .= $this->_inverted($context, $current); + array_pop($this->_stack); + break; + case Tokenizer::T_COMMENT : + $buffer .= ''; + break; + case Tokenizer::T_PARTIAL: + case Tokenizer::T_PARTIAL_2: + $buffer .= $this->_partial($context, $current); + break; + case Tokenizer::T_UNESCAPED: + case Tokenizer::T_UNESCAPED_2: + $buffer .= $this->_get($context, $current, false); + break; + case Tokenizer::T_ESCAPED: - $buffer .= $this->_get($context, $current, true); - break; - case Tokenizer::T_TEXT: - $buffer .= $current[Tokenizer::VALUE]; - break; - default: - throw new \RuntimeException( - 'Invalid node type : ' . json_encode($current) - ); + $buffer .= $this->_get($context, $current, true); + break; + case Tokenizer::T_TEXT: + $buffer .= $current[Tokenizer::VALUE]; + break; + default: + throw new \RuntimeException( + 'Invalid node type : ' . json_encode($current) + ); } } if ($stop) { @@ -277,22 +277,22 @@ class Template $subexprs = array(); // will contain all subexpressions inside outermost brackets $lvl = 0; $cur_start = 0; - for( $i=0; $i < strlen($params[2]); $i++ ) { - $cur = substr( $params[2], $i, 1 ); - if( $cur == '(' ) { + for ( $i=0; $i < strlen($params[2]); $i++ ) { + $cur = substr($params[2], $i, 1); + if ( $cur == '(' ) { if( $lvl == 0 ) $cur_start = $i+1; $lvl++; } - if( $cur == ')' ) { + if ( $cur == ')' ) { $lvl--; - if( $lvl == 0 ) $subexprs[] = substr( $params[2], $cur_start, $i - $cur_start); + if( $lvl == 0 ) $subexprs[] = substr($params[2], $cur_start, $i - $cur_start); } } - if( ! empty( $subexprs ) ) { - foreach( $subexprs as $expr ) { - $cmd = explode( " ", $expr ); - $name = trim( $cmd[0] ); + if ( ! empty( $subexprs ) ) { + foreach ( $subexprs as $expr ) { + $cmd = explode(" ", $expr); + $name = trim($cmd[0]); // construct artificial section node $section_node = array( Tokenizer::TYPE => Tokenizer::T_ESCAPED, @@ -300,12 +300,12 @@ class Template Tokenizer::OTAG => $current[Tokenizer::OTAG], Tokenizer::CTAG => $current[Tokenizer::CTAG], Tokenizer::INDEX => $current[Tokenizer::INDEX], - Tokenizer::ARGS => implode( " ", array_slice( $cmd, 1 ) ) + Tokenizer::ARGS => implode(" ", array_slice( $cmd, 1 )) ); // resolve the node recursively - $resolved = $this->_handlebarsStyleSection( $context, $section_node ); + $resolved = $this->_handlebarsStyleSection($context, $section_node); // replace original subexpression with result - $params[2] = str_replace( '('.$expr.')', $resolved, $params[2] ); + $params[2] = str_replace('('.$expr.')', $resolved, $params[2]); } } From 30f7afeb840b3d312d767d0941eb047efbf8bf0f Mon Sep 17 00:00:00 2001 From: majortom731 Date: Mon, 17 Mar 2014 16:46:12 +0100 Subject: [PATCH 7/9] More code style / indentation fixes --- src/Handlebars/Helpers.php | 2 +- src/Handlebars/Template.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Handlebars/Helpers.php b/src/Handlebars/Helpers.php index 485c90f..893f33c 100644 --- a/src/Handlebars/Helpers.php +++ b/src/Handlebars/Helpers.php @@ -88,7 +88,7 @@ class Helpers * @var $args array * @var $source string */ - if ( is_numeric($args) ) { + if (is_numeric($args)) { $tmp = $args; } else { $tmp = $context->get($args); diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index 4940be7..216cc1e 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -277,20 +277,20 @@ class Template $subexprs = array(); // will contain all subexpressions inside outermost brackets $lvl = 0; $cur_start = 0; - for ( $i=0; $i < strlen($params[2]); $i++ ) { + for ($i=0; $i < strlen($params[2]); $i++) { $cur = substr($params[2], $i, 1); - if ( $cur == '(' ) { + if ($cur == '(') { if( $lvl == 0 ) $cur_start = $i+1; $lvl++; } - if ( $cur == ')' ) { + if ($cur == ')') { $lvl--; - if( $lvl == 0 ) $subexprs[] = substr($params[2], $cur_start, $i - $cur_start); + if ($lvl == 0) $subexprs[] = substr($params[2], $cur_start, $i - $cur_start); } } - if ( ! empty( $subexprs ) ) { - foreach ( $subexprs as $expr ) { + if (! empty($subexprs)) { + foreach ($subexprs as $expr) { $cmd = explode(" ", $expr); $name = trim($cmd[0]); // construct artificial section node From 5a58d27ad36501c62333b7a555cfec9023b07f61 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Mon, 17 Mar 2014 23:00:36 +0100 Subject: [PATCH 8/9] Added test for correctness of treatment of brackets inside string arguments ( {{foo '(bar)'}} ) --- tests/Xamin/HandlebarsTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 75dc2ec..aaf10eb 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -795,6 +795,11 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase // assert that subexpression result is inserted correctly as argument to top level helper $this->assertEquals('42', $engine->render('{{add 21 (add 10 (add 5 6))}}', array())); + + + // assert that bracketed expressions within string literals are treated correctly + $this->assertEquals("'(test)'Test.", $engine->render("{{test '(test)'}}", array())); + $this->assertEquals("')'Test.Test.", $engine->render("{{test (test ')')}}", array())); } } \ No newline at end of file From ffcbecf3465906624afa5a63ab400eacec4f46c4 Mon Sep 17 00:00:00 2001 From: majortom731 Date: Mon, 17 Mar 2014 23:01:30 +0100 Subject: [PATCH 9/9] Implemented proper treatment of brackets inside string arguments in subexpressions ( {{foo '(bar)'}} ) --- src/Handlebars/Template.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index 216cc1e..7974661 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -275,17 +275,30 @@ class Template // subexpression parsing loop $subexprs = array(); // will contain all subexpressions inside outermost brackets + $inside_of = array( 'single' => false, 'double' => false ); $lvl = 0; $cur_start = 0; for ($i=0; $i < strlen($params[2]); $i++) { $cur = substr($params[2], $i, 1); - if ($cur == '(') { - if( $lvl == 0 ) $cur_start = $i+1; - $lvl++; + if ($cur == "'" ) { + $inside_of['single'] = ! $inside_of['single']; } - if ($cur == ')') { + if ($cur == '"' ) { + $inside_of['double'] = ! $inside_of['double']; + } + if ($cur == '(' && ! $inside_of['single'] && ! $inside_of['double']) { + if ($lvl == 0) { + $cur_start = $i+1; + } + $lvl++; + continue; + } + if ($cur == ')' && ! $inside_of['single'] && ! $inside_of['double']) { $lvl--; - if ($lvl == 0) $subexprs[] = substr($params[2], $cur_start, $i - $cur_start); + if ($lvl == 0) { + $subexprs[] = substr($params[2], $cur_start, $i - $cur_start); + } + } } @@ -300,7 +313,7 @@ class Template Tokenizer::OTAG => $current[Tokenizer::OTAG], Tokenizer::CTAG => $current[Tokenizer::CTAG], Tokenizer::INDEX => $current[Tokenizer::INDEX], - Tokenizer::ARGS => implode(" ", array_slice( $cmd, 1 )) + Tokenizer::ARGS => implode(" ", array_slice($cmd, 1)) ); // resolve the node recursively $resolved = $this->_handlebarsStyleSection($context, $section_node);