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", ); /**