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.
This commit is contained in:
Justin Hileman
2010-11-24 07:45:24 -05:00
parent d026315a2c
commit 3e6b8102b7
13 changed files with 87 additions and 98 deletions
+55 -23
View File
@@ -258,6 +258,17 @@ class Mustache {
return $template; return $template;
} }
const SECTION_TYPES = '^#/';
protected function _prepareSectionRegEx($otag, $ctag) {
return sprintf(
'/(?:(?<=\\n)[ \\t]*)?%s(?<type>[%s])(?<tag_name>.+?)%s\\n?/s',
preg_quote($otag, '/'),
preg_quote(self::SECTION_TYPES, '/'),
preg_quote($ctag, '/')
);
}
/** /**
* Extract a section from $template. * Extract a section from $template.
* *
@@ -268,9 +279,7 @@ class Mustache {
* @return array $section, $offset, $type, $tag_name and $content * @return array $section, $offset, $type, $tag_name and $content
*/ */
protected function _findSection($template) { protected function _findSection($template) {
$otag = preg_quote($this->_otag, '/'); $regex = $this->_prepareSectionRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/');
$regex = '/' . $otag . '([\\^\\#\\/])\\s*(.+?)\\s*' . $ctag . '\\s*/ms';
$section_start = null; $section_start = null;
$section_type = null; $section_type = null;
@@ -284,8 +293,8 @@ class Mustache {
$match = $matches[0][0]; $match = $matches[0][0];
$offset = $matches[0][1]; $offset = $matches[0][1];
$type = $matches[1][0]; $type = $matches['type'][0];
$tag_name = trim($matches[2][0]); $tag_name = trim($matches['tag_name'][0]);
$search_offset = $offset + strlen($match); $search_offset = $offset + strlen($match);
@@ -323,6 +332,14 @@ class Mustache {
} }
} }
protected function _preparePragmaRegEx($otag, $ctag) {
return sprintf(
'/%s%%\\s*(?<pragma_name>[\\w_-]+)(?<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
preg_quote($otag, '/'),
preg_quote($ctag, '/')
);
}
/** /**
* Initialize pragmas and remove all pragma tags. * Initialize pragmas and remove all pragma tags.
* *
@@ -338,9 +355,7 @@ class Mustache {
return $template; return $template;
} }
$otag = preg_quote($this->_otag, '/'); $regex = $this->_preparePragmaRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/');
$regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/s';
return preg_replace_callback($regex, array($this, '_renderPragma'), $template); return preg_replace_callback($regex, array($this, '_renderPragma'), $template);
} }
@@ -354,8 +369,8 @@ class Mustache {
*/ */
protected function _renderPragma($matches) { protected function _renderPragma($matches) {
$pragma = $matches[0]; $pragma = $matches[0];
$pragma_name = $matches[1]; $pragma_name = $matches['pragma_name'];
$options_string = $matches[2]; $options_string = $matches['options_string'];
if (!in_array($pragma_name, $this->_pragmasImplemented)) { if (!in_array($pragma_name, $this->_pragmasImplemented)) {
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
@@ -422,6 +437,17 @@ class Mustache {
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
} }
const TAG_TYPES = '#^/=!<>\\{&';
protected function _prepareTagRegex($otag, $ctag) {
return sprintf(
'/(?:(?<=\\n)[ \\t]*)?%s(?<type>[%s]?)(?<tag_name>.+?)(?:\\1|})?%s(?:\\s*(?=\\n))?/s',
preg_quote($otag, '/'),
preg_quote(self::TAG_TYPES, '/'),
preg_quote($ctag, '/')
);
}
/** /**
* Loop through and render individual Mustache tags. * Loop through and render individual Mustache tags.
* *
@@ -437,22 +463,25 @@ class Mustache {
$otag_orig = $this->_otag; $otag_orig = $this->_otag;
$ctag_orig = $this->_ctag; $ctag_orig = $this->_ctag;
$otag = preg_quote($this->_otag, '/'); $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/');
$this->_tagRegEx = '/' . $otag . "([#\^\/=!<>\\{&])?(.+?)\\1?" . $ctag . "+/s";
$html = ''; $html = '';
$matches = array(); $matches = array();
while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) { while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
$tag = $matches[0][0]; $tag = $matches[0][0];
$offset = $matches[0][1]; $offset = $matches[0][1];
$modifier = $matches[1][0]; $modifier = $matches['type'][0];
$tag_name = trim($matches[2][0]); $tag_name = trim($matches['tag_name'][0]);
$html .= substr($template, 0, $offset); $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); $html .= $this->_renderTag($modifier, $tag_name);
$template = substr($template, $offset + strlen($tag));
} }
$this->_otag = $otag_orig; $this->_otag = $otag_orig;
@@ -501,6 +530,10 @@ class Mustache {
return $this->_renderPartial($tag_name); return $this->_renderPartial($tag_name);
break; break;
case '{': case '{':
// strip the trailing } ...
if ($tag_name[(strlen($tag_name) - 1)] == '}') {
$tag_name = substr($tag_name, 0, -1);
}
case '&': case '&':
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
return $this->_renderEscaped($tag_name); return $this->_renderEscaped($tag_name);
@@ -571,13 +604,12 @@ class Mustache {
* @return string * @return string
*/ */
protected function _changeDelimiter($tag_name) { protected function _changeDelimiter($tag_name) {
$tags = explode(' ', $tag_name); list($otag, $ctag) = explode(' ', $tag_name);
$this->_otag = $tags[0]; $this->_otag = $otag;
$this->_ctag = $tags[1]; $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 ''; return '';
} }
-1
View File
@@ -83,7 +83,6 @@ Known Issues
* Sections don't respect delimiter changes -- `delimiters` example currently fails with an * Sections don't respect delimiter changes -- `delimiters` example currently fails with an
"unclosed section" exception. "unclosed section" exception.
* Mustache isn't always very good at whitespace.
See Also See Also
+11 -11
View File
@@ -1,16 +1,16 @@
<h1>{{header}}</h1> <h1>{{header}}</h1>
{{#notEmpty}} {{#notEmpty}}
<ul> <ul>
{{#item}} {{#item}}
{{#current}} {{#current}}
<li><strong>{{name}}</strong></li> <li><strong>{{name}}</strong></li>
{{/current}} {{/current}}
{{^current}} {{^current}}
<li><a href="{{url}}">{{name}}</a></li> <li><a href="{{url}}">{{name}}</a></li>
{{/current}} {{/current}}
{{/item}} {{/item}}
</ul> </ul>
{{/notEmpty}} {{/notEmpty}}
{{#isEmpty}} {{#isEmpty}}
<p>The list is empty.</p> <p>The list is empty.</p>
{{/isEmpty}} {{/isEmpty}}
+2 -2
View File
@@ -1,6 +1,6 @@
<h1>Colors</h1> <h1>Colors</h1>
<ul> <ul>
<li><strong>red</strong></li> <li><strong>red</strong></li>
<li><a href="#Green">green</a></li> <li><a href="#Green">green</a></li>
<li><a href="#Blue">blue</a></li> <li><a href="#Blue">blue</a></li>
</ul> </ul>
@@ -1,7 +1,7 @@
{{#t}} {{#t}}
* first * first
{{/t}} {{/t}}
* {{two}} * {{two}}
{{#t}} {{#t}}
* third * third
{{/t}} {{/t}}
@@ -1,10 +1,7 @@
{{grand_parent_id}} {{grand_parent_id}}
{{#parent_contexts}} {{#parent_contexts}}
{{grand_parent_id}} {{parent_id}} ({{grand_parent_id}})
{{parent_id}} {{#child_contexts}}
{{#child_contexts}} {{child_id}} ({{parent_id}} << {{grand_parent_id}})
{{grand_parent_id}} {{/child_contexts}}
{{parent_id}}
{{child_id}}
{{/child_contexts}}
{{/parent_contexts}} {{/parent_contexts}}
@@ -1,17 +1,7 @@
grand_parent1 grand_parent1
grand_parent1 parent1 (grand_parent1)
parent1 parent1-child1 (parent1 << grand_parent1)
grand_parent1 parent1-child2 (parent1 << grand_parent1)
parent1 parent2 (grand_parent1)
parent1-child1 parent2-child1 (parent2 << grand_parent1)
grand_parent1 parent2-child2 (parent2 << grand_parent1)
parent1
parent1-child2
grand_parent1
parent2
grand_parent1
parent2
parent2-child1
grand_parent1
parent2
parent2-child2
@@ -1,7 +1,7 @@
{{^t}} {{^t}}
* first * first
{{/t}} {{/t}}
* {{two}} * {{two}}
{{^t}} {{^t}}
* third * third
{{/t}} {{/t}}
@@ -1,14 +0,0 @@
<?php
class SectionsSpaces extends Mustache {
public $start = "It worked the first time.";
public function middle() {
return array(
array('item' => "And it worked the second time."),
array('item' => "As well as the third."),
);
}
public $final = "Then, surprisingly, it worked the final time.";
}
@@ -1,9 +0,0 @@
* {{ start }}
{{# middle }}
* {{ item }}
{{/ middle }}
* {{ final }}
* {{ start }}
{{# middle }} * {{ item }}{{/ middle }}
* {{ final }}
@@ -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.
+5 -1
View File
@@ -11,7 +11,11 @@ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase {
$m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}')); $m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}'));
$m->expects($this->exactly(1)) $m->expects($this->exactly(1))
->method('_renderPragma') ->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(); $m->render();
} }
-1
View File
@@ -37,7 +37,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
protected $knownIssues = array( protected $knownIssues = array(
'Delimiters' => "Known issue: sections don't respect delimiter changes", 'Delimiters' => "Known issue: sections don't respect delimiter changes",
'SectionsSpaces' => "Known issue: Mustache fails miserably at whitespace",
); );
/** /**