From 3e6b8102b78dba421f987a05f5a32abd0d7b13f6 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 07:45:24 -0500 Subject: [PATCH 1/7] Massive overhaul of Mustache.php's whitespace handling. Move regex preparation into methods, use named subpatterns so the regex makes more sense. Fix the logic around eating newlines adjacent to tags. Update tests to reflect the changes. --- Mustache.php | 78 +++++++++++++------ README.markdown | 1 - examples/complex/complex.mustache | 22 +++--- examples/complex/complex.txt | 4 +- .../double_section/double_section.mustache | 4 +- .../grand_parent_context.mustache | 11 +-- .../grand_parent_context.txt | 22 ++---- .../inverted_double_section.mustache | 4 +- examples/sections_spaces/SectionsSpaces.php | 14 ---- .../sections_spaces/sections_spaces.mustache | 9 --- examples/sections_spaces/sections_spaces.txt | 9 --- test/MustachePragmaImplicitIteratorTest.php | 6 +- test/MustacheTest.php | 1 - 13 files changed, 87 insertions(+), 98 deletions(-) delete mode 100644 examples/sections_spaces/SectionsSpaces.php delete mode 100644 examples/sections_spaces/sections_spaces.mustache delete mode 100644 examples/sections_spaces/sections_spaces.txt diff --git a/Mustache.php b/Mustache.php index a41502b..255a627 100644 --- a/Mustache.php +++ b/Mustache.php @@ -258,6 +258,17 @@ class Mustache { return $template; } + const SECTION_TYPES = '^#/'; + + protected function _prepareSectionRegEx($otag, $ctag) { + return sprintf( + '/(?:(?<=\\n)[ \\t]*)?%s(?[%s])(?.+?)%s\\n?/s', + preg_quote($otag, '/'), + preg_quote(self::SECTION_TYPES, '/'), + preg_quote($ctag, '/') + ); + } + /** * Extract a section from $template. * @@ -268,9 +279,7 @@ class Mustache { * @return array $section, $offset, $type, $tag_name and $content */ protected function _findSection($template) { - $otag = preg_quote($this->_otag, '/'); - $ctag = preg_quote($this->_ctag, '/'); - $regex = '/' . $otag . '([\\^\\#\\/])\\s*(.+?)\\s*' . $ctag . '\\s*/ms'; + $regex = $this->_prepareSectionRegEx($this->_otag, $this->_ctag); $section_start = null; $section_type = null; @@ -284,8 +293,8 @@ class Mustache { $match = $matches[0][0]; $offset = $matches[0][1]; - $type = $matches[1][0]; - $tag_name = trim($matches[2][0]); + $type = $matches['type'][0]; + $tag_name = trim($matches['tag_name'][0]); $search_offset = $offset + strlen($match); @@ -323,6 +332,14 @@ class Mustache { } } + protected function _preparePragmaRegEx($otag, $ctag) { + return sprintf( + '/%s%%\\s*(?[\\w_-]+)(?(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s', + preg_quote($otag, '/'), + preg_quote($ctag, '/') + ); + } + /** * Initialize pragmas and remove all pragma tags. * @@ -338,9 +355,7 @@ class Mustache { return $template; } - $otag = preg_quote($this->_otag, '/'); - $ctag = preg_quote($this->_ctag, '/'); - $regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/s'; + $regex = $this->_preparePragmaRegEx($this->_otag, $this->_ctag); return preg_replace_callback($regex, array($this, '_renderPragma'), $template); } @@ -354,8 +369,8 @@ class Mustache { */ protected function _renderPragma($matches) { $pragma = $matches[0]; - $pragma_name = $matches[1]; - $options_string = $matches[2]; + $pragma_name = $matches['pragma_name']; + $options_string = $matches['options_string']; if (!in_array($pragma_name, $this->_pragmasImplemented)) { throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); @@ -422,6 +437,17 @@ class Mustache { return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); } + const TAG_TYPES = '#^/=!<>\\{&'; + + protected function _prepareTagRegex($otag, $ctag) { + return sprintf( + '/(?:(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\1|})?%s(?:\\s*(?=\\n))?/s', + preg_quote($otag, '/'), + preg_quote(self::TAG_TYPES, '/'), + preg_quote($ctag, '/') + ); + } + /** * Loop through and render individual Mustache tags. * @@ -437,22 +463,25 @@ class Mustache { $otag_orig = $this->_otag; $ctag_orig = $this->_ctag; - $otag = preg_quote($this->_otag, '/'); - $ctag = preg_quote($this->_ctag, '/'); - - $this->_tagRegEx = '/' . $otag . "([#\^\/=!<>\\{&])?(.+?)\\1?" . $ctag . "+/s"; + $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); $html = ''; $matches = array(); while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) { $tag = $matches[0][0]; $offset = $matches[0][1]; - $modifier = $matches[1][0]; - $tag_name = trim($matches[2][0]); + $modifier = $matches['type'][0]; + $tag_name = trim($matches['tag_name'][0]); $html .= substr($template, 0, $offset); + + $next_offset = $offset + strlen($tag); + if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) { + $next_offset++; + } + $template = substr($template, $next_offset); + $html .= $this->_renderTag($modifier, $tag_name); - $template = substr($template, $offset + strlen($tag)); } $this->_otag = $otag_orig; @@ -501,6 +530,10 @@ class Mustache { return $this->_renderPartial($tag_name); break; case '{': + // strip the trailing } ... + if ($tag_name[(strlen($tag_name) - 1)] == '}') { + $tag_name = substr($tag_name, 0, -1); + } case '&': if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { return $this->_renderEscaped($tag_name); @@ -571,13 +604,12 @@ class Mustache { * @return string */ protected function _changeDelimiter($tag_name) { - $tags = explode(' ', $tag_name); - $this->_otag = $tags[0]; - $this->_ctag = $tags[1]; + list($otag, $ctag) = explode(' ', $tag_name); + $this->_otag = $otag; + $this->_ctag = $ctag; + + $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); - $otag = preg_quote($this->_otag, '/'); - $ctag = preg_quote($this->_ctag, '/'); - $this->_tagRegEx = '/' . $otag . "([#\^\/=!<>\\{&])?(.+?)\\1?" . $ctag . "+/s"; return ''; } diff --git a/README.markdown b/README.markdown index 18e72d4..ac39899 100644 --- a/README.markdown +++ b/README.markdown @@ -83,7 +83,6 @@ Known Issues * Sections don't respect delimiter changes -- `delimiters` example currently fails with an "unclosed section" exception. - * Mustache isn't always very good at whitespace. See Also diff --git a/examples/complex/complex.mustache b/examples/complex/complex.mustache index 4bf1687..807c201 100644 --- a/examples/complex/complex.mustache +++ b/examples/complex/complex.mustache @@ -1,16 +1,16 @@

{{header}}

{{#notEmpty}} -
    - {{#item}} - {{#current}} -
  • {{name}}
  • - {{/current}} - {{^current}} -
  • {{name}}
  • - {{/current}} - {{/item}} -
+
    +{{#item}} +{{#current}} +
  • {{name}}
  • +{{/current}} +{{^current}} +
  • {{name}}
  • +{{/current}} +{{/item}} +
{{/notEmpty}} {{#isEmpty}} -

The list is empty.

+

The list is empty.

{{/isEmpty}} \ No newline at end of file diff --git a/examples/complex/complex.txt b/examples/complex/complex.txt index eb78d45..facee6d 100644 --- a/examples/complex/complex.txt +++ b/examples/complex/complex.txt @@ -1,6 +1,6 @@

Colors

+ diff --git a/examples/double_section/double_section.mustache b/examples/double_section/double_section.mustache index f5ad673..c831645 100644 --- a/examples/double_section/double_section.mustache +++ b/examples/double_section/double_section.mustache @@ -1,7 +1,7 @@ {{#t}} - * first +* first {{/t}} * {{two}} {{#t}} - * third +* third {{/t}} \ No newline at end of file diff --git a/examples/grand_parent_context/grand_parent_context.mustache b/examples/grand_parent_context/grand_parent_context.mustache index e6c07a2..6d03ddf 100644 --- a/examples/grand_parent_context/grand_parent_context.mustache +++ b/examples/grand_parent_context/grand_parent_context.mustache @@ -1,10 +1,7 @@ {{grand_parent_id}} {{#parent_contexts}} -{{grand_parent_id}} -{{parent_id}} -{{#child_contexts}} -{{grand_parent_id}} -{{parent_id}} -{{child_id}} -{{/child_contexts}} + {{parent_id}} ({{grand_parent_id}}) + {{#child_contexts}} + {{child_id}} ({{parent_id}} << {{grand_parent_id}}) + {{/child_contexts}} {{/parent_contexts}} diff --git a/examples/grand_parent_context/grand_parent_context.txt b/examples/grand_parent_context/grand_parent_context.txt index 64996ad..2687f84 100644 --- a/examples/grand_parent_context/grand_parent_context.txt +++ b/examples/grand_parent_context/grand_parent_context.txt @@ -1,17 +1,7 @@ grand_parent1 -grand_parent1 -parent1 -grand_parent1 -parent1 -parent1-child1 -grand_parent1 -parent1 -parent1-child2 -grand_parent1 -parent2 -grand_parent1 -parent2 -parent2-child1 -grand_parent1 -parent2 -parent2-child2 + parent1 (grand_parent1) + parent1-child1 (parent1 << grand_parent1) + parent1-child2 (parent1 << grand_parent1) + parent2 (grand_parent1) + parent2-child1 (parent2 << grand_parent1) + parent2-child2 (parent2 << grand_parent1) diff --git a/examples/inverted_double_section/inverted_double_section.mustache b/examples/inverted_double_section/inverted_double_section.mustache index f65109c..acc3ae0 100644 --- a/examples/inverted_double_section/inverted_double_section.mustache +++ b/examples/inverted_double_section/inverted_double_section.mustache @@ -1,7 +1,7 @@ {{^t}} - * first +* first {{/t}} * {{two}} {{^t}} - * third +* third {{/t}} \ No newline at end of file diff --git a/examples/sections_spaces/SectionsSpaces.php b/examples/sections_spaces/SectionsSpaces.php deleted file mode 100644 index 9b5adb8..0000000 --- a/examples/sections_spaces/SectionsSpaces.php +++ /dev/null @@ -1,14 +0,0 @@ - "And it worked the second time."), - array('item' => "As well as the third."), - ); - } - - public $final = "Then, surprisingly, it worked the final time."; -} \ No newline at end of file diff --git a/examples/sections_spaces/sections_spaces.mustache b/examples/sections_spaces/sections_spaces.mustache deleted file mode 100644 index 67e2606..0000000 --- a/examples/sections_spaces/sections_spaces.mustache +++ /dev/null @@ -1,9 +0,0 @@ - * {{ start }} -{{# middle }} - * {{ item }} -{{/ middle }} - * {{ final }} - - * {{ start }} -{{# middle }} * {{ item }}{{/ middle }} - * {{ final }} \ No newline at end of file diff --git a/examples/sections_spaces/sections_spaces.txt b/examples/sections_spaces/sections_spaces.txt deleted file mode 100644 index a7ca703..0000000 --- a/examples/sections_spaces/sections_spaces.txt +++ /dev/null @@ -1,9 +0,0 @@ - * It worked the first time. - * And it worked the second time. - * As well as the third. - * Then, surprisingly, it worked the final time. - - * It worked the first time. - * And it worked the second time. - * As well as the third. - * Then, surprisingly, it worked the final time. \ No newline at end of file diff --git a/test/MustachePragmaImplicitIteratorTest.php b/test/MustachePragmaImplicitIteratorTest.php index 9a88359..507620d 100644 --- a/test/MustachePragmaImplicitIteratorTest.php +++ b/test/MustachePragmaImplicitIteratorTest.php @@ -11,7 +11,11 @@ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase { $m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}')); $m->expects($this->exactly(1)) ->method('_renderPragma') - ->with(array('{{%IMPLICIT-ITERATOR}}', 'IMPLICIT-ITERATOR', null)); + ->with(array( + 0 => '{{%IMPLICIT-ITERATOR}}', + 1 => 'IMPLICIT-ITERATOR', 'pragma_name' => 'IMPLICIT-ITERATOR', + 2 => null, 'options_string' => null + )); $m->render(); } diff --git a/test/MustacheTest.php b/test/MustacheTest.php index 69a2029..504b2d4 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -37,7 +37,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase { protected $knownIssues = array( 'Delimiters' => "Known issue: sections don't respect delimiter changes", - 'SectionsSpaces' => "Known issue: Mustache fails miserably at whitespace", ); /** From 2b8e2f6e84bbb95a29770694b584ef3e16fbdfce Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 08:11:09 -0500 Subject: [PATCH 2/7] Capture whitespace before tags, prefix it to partials. --- Mustache.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Mustache.php b/Mustache.php index 255a627..19a72d6 100644 --- a/Mustache.php +++ b/Mustache.php @@ -441,7 +441,7 @@ class Mustache { protected function _prepareTagRegex($otag, $ctag) { return sprintf( - '/(?:(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\1|})?%s(?:\\s*(?=\\n))?/s', + '/(?(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', preg_quote($otag, '/'), preg_quote(self::TAG_TYPES, '/'), preg_quote($ctag, '/') @@ -473,6 +473,12 @@ class Mustache { $modifier = $matches['type'][0]; $tag_name = trim($matches['tag_name'][0]); + if (isset($matches['whitespace']) && $matches['whitespace'][1] > -1) { + $whitespace = $matches['whitespace'][0]; + } else { + $whitespace = null; + } + $html .= substr($template, 0, $offset); $next_offset = $offset + strlen($tag); @@ -481,7 +487,7 @@ class Mustache { } $template = substr($template, $next_offset); - $html .= $this->_renderTag($modifier, $tag_name); + $html .= $this->_renderTag($modifier, $tag_name, $whitespace); } $this->_otag = $otag_orig; @@ -502,7 +508,7 @@ class Mustache { * @throws MustacheException Unmatched section tag encountered. * @return string */ - protected function _renderTag($modifier, $tag_name) { + protected function _renderTag($modifier, $tag_name, $whitespace) { switch ($modifier) { case '#': case '^': @@ -527,7 +533,7 @@ class Mustache { break; case '>': case '<': - return $this->_renderPartial($tag_name); + return $this->_renderPartial($tag_name, $whitespace); break; case '{': // strip the trailing } ... @@ -590,9 +596,10 @@ class Mustache { * @param string $tag_name * @return string */ - protected function _renderPartial($tag_name) { + protected function _renderPartial($tag_name, $whitespace = '') { $view = clone($this); - return $view->render($this->_getPartial($tag_name)); + + return $whitespace . preg_replace('/\n(?!$)/s', "\n" . $whitespace, $view->render($this->_getPartial($tag_name))); } /** From fe91be1fb1b184601de7404fcc187a5ac4f41cf8 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 26 Nov 2010 14:07:10 -0500 Subject: [PATCH 3/7] Pre-escape tag type constants (saves us from doing it each time the regex strings are prepared) --- Mustache.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mustache.php b/Mustache.php index 19a72d6..fc7bd32 100644 --- a/Mustache.php +++ b/Mustache.php @@ -258,13 +258,13 @@ class Mustache { return $template; } - const SECTION_TYPES = '^#/'; + const SECTION_TYPES = '\^#\/'; protected function _prepareSectionRegEx($otag, $ctag) { return sprintf( '/(?:(?<=\\n)[ \\t]*)?%s(?[%s])(?.+?)%s\\n?/s', preg_quote($otag, '/'), - preg_quote(self::SECTION_TYPES, '/'), + self::SECTION_TYPES, preg_quote($ctag, '/') ); } @@ -437,13 +437,13 @@ class Mustache { return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); } - const TAG_TYPES = '#^/=!<>\\{&'; + const TAG_TYPES = '#\^\/=!<>\\{&'; protected function _prepareTagRegex($otag, $ctag) { return sprintf( '/(?(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', preg_quote($otag, '/'), - preg_quote(self::TAG_TYPES, '/'), + self::TAG_TYPES, preg_quote($ctag, '/') ); } From b33b1a7bf2784e07bee46f21722f6ee506800c49 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 26 Nov 2010 15:38:45 -0500 Subject: [PATCH 4/7] Remove an extra trim(); --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 56fb153..afbc353 100644 --- a/Mustache.php +++ b/Mustache.php @@ -379,7 +379,7 @@ class Mustache { $options = array(); foreach (explode(' ', trim($options_string)) as $o) { if ($p = trim($o)) { - $p = explode('=', trim($p)); + $p = explode('=', $p); $options[$p[0]] = $p[1]; } } From 289cd023d423b8ccb8c27a317c95d4e9a791fe09 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 26 Nov 2010 15:44:36 -0500 Subject: [PATCH 5/7] Optimize dot notation check Don't try to traverse dot notation unless there's a dot in the variable name. --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index afbc353..4638a2a 100644 --- a/Mustache.php +++ b/Mustache.php @@ -660,7 +660,7 @@ class Mustache { * @return string */ protected function _getVariable($tag_name) { - if ($this->_hasPragma(self::PRAGMA_DOT_NOTATION) && $tag_name != '.') { + if ($tag_name != '.' && strpos($tag_name, '.') !== false && $this->_hasPragma(self::PRAGMA_DOT_NOTATION)) { $chunks = explode('.', $tag_name); $first = array_shift($chunks); From c5bf83f9a954946182ecdf4b6fd9eee1de189249 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 8 Dec 2010 08:28:51 -0500 Subject: [PATCH 6/7] Move class constants to their logical places. --- Mustache.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Mustache.php b/Mustache.php index 4638a2a..d4bff90 100644 --- a/Mustache.php +++ b/Mustache.php @@ -14,9 +14,6 @@ */ class Mustache { - public $_otag = '{{'; - public $_ctag = '}}'; - /** * Should this Mustache throw exceptions when it finds unexpected tags? * @@ -85,6 +82,15 @@ class Mustache { */ const PRAGMA_UNESCAPED = 'UNESCAPED'; + /** + * Constants used for section and tag RegEx + */ + const SECTION_TYPES = '\^#\/'; + const TAG_TYPES = '#\^\/=!<>\\{&'; + + public $_otag = '{{'; + public $_ctag = '}}'; + protected $_tagRegEx; protected $_template = ''; @@ -258,8 +264,6 @@ class Mustache { return $template; } - const SECTION_TYPES = '\^#\/'; - protected function _prepareSectionRegEx($otag, $ctag) { return sprintf( '/(?:(?<=\\n)[ \\t]*)?%s(?[%s])(?.+?)%s\\n?/s', @@ -437,8 +441,6 @@ class Mustache { return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); } - const TAG_TYPES = '#\^\/=!<>\\{&'; - protected function _prepareTagRegex($otag, $ctag) { return sprintf( '/(?(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', From 64b5cd0964d04c881a6680814ac5cdda6c148b3c Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 8 Dec 2010 08:34:27 -0500 Subject: [PATCH 7/7] Cleanup. Make RegEx variable capitalization consistent. Add docblocks for prepareFooRegEx functions. --- Mustache.php | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/Mustache.php b/Mustache.php index d4bff90..3b43a4f 100644 --- a/Mustache.php +++ b/Mustache.php @@ -264,6 +264,14 @@ class Mustache { return $template; } + /** + * Prepare a section RegEx string for the given opening/closing tags. + * + * @access protected + * @param string $otag + * @param string $ctag + * @return string + */ protected function _prepareSectionRegEx($otag, $ctag) { return sprintf( '/(?:(?<=\\n)[ \\t]*)?%s(?[%s])(?.+?)%s\\n?/s', @@ -283,7 +291,7 @@ class Mustache { * @return array $section, $offset, $type, $tag_name and $content */ protected function _findSection($template) { - $regex = $this->_prepareSectionRegEx($this->_otag, $this->_ctag); + $regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag); $section_start = null; $section_type = null; @@ -293,7 +301,7 @@ class Mustache { $section_stack = array(); $matches = array(); - while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { + while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { $match = $matches[0][0]; $offset = $matches[0][1]; @@ -336,6 +344,14 @@ class Mustache { } } + /** + * Prepare a pragma RegEx for the given opening/closing tags. + * + * @access protected + * @param string $otag + * @param string $ctag + * @return string + */ protected function _preparePragmaRegEx($otag, $ctag) { return sprintf( '/%s%%\\s*(?[\\w_-]+)(?(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s', @@ -359,8 +375,8 @@ class Mustache { return $template; } - $regex = $this->_preparePragmaRegEx($this->_otag, $this->_ctag); - return preg_replace_callback($regex, array($this, '_renderPragma'), $template); + $regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag); + return preg_replace_callback($regEx, array($this, '_renderPragma'), $template); } /** @@ -441,7 +457,15 @@ class Mustache { return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); } - protected function _prepareTagRegex($otag, $ctag) { + /** + * Prepare a tag RegEx for the given opening/closing tags. + * + * @access protected + * @param string $otag + * @param string $ctag + * @return string + */ + protected function _prepareTagRegEx($otag, $ctag) { return sprintf( '/(?(?<=\\n)[ \\t]*)?%s(?[%s]?)(?.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', preg_quote($otag, '/'), @@ -629,7 +653,6 @@ class Mustache { $this->_context = $new; } - /** * Remove the latest context from the stack. *