Merge branch 'feature/whitespace-overhaul' into dev
This commit is contained in:
+100
-36
@@ -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,6 +264,23 @@ 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(?<type>[%s])(?<tag_name>.+?)%s\\n?/s',
|
||||
preg_quote($otag, '/'),
|
||||
self::SECTION_TYPES,
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a section from $template.
|
||||
*
|
||||
@@ -268,9 +291,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;
|
||||
@@ -280,12 +301,12 @@ 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];
|
||||
$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 +344,22 @@ 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*(?<pragma_name>[\\w_-]+)(?<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
|
||||
preg_quote($otag, '/'),
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize pragmas and remove all pragma tags.
|
||||
*
|
||||
@@ -338,10 +375,8 @@ class Mustache {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$otag = preg_quote($this->_otag, '/');
|
||||
$ctag = preg_quote($this->_ctag, '/');
|
||||
$regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/s';
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,8 +389,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);
|
||||
@@ -364,7 +399,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];
|
||||
}
|
||||
}
|
||||
@@ -422,6 +457,23 @@ class Mustache {
|
||||
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'/(?<whitespace>(?<=\\n)[ \\t]*)?%s(?<type>[%s]?)(?<tag_name>.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s',
|
||||
preg_quote($otag, '/'),
|
||||
self::TAG_TYPES,
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through and render individual Mustache tags.
|
||||
*
|
||||
@@ -437,22 +489,31 @@ 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]);
|
||||
|
||||
if (isset($matches['whitespace']) && $matches['whitespace'][1] > -1) {
|
||||
$whitespace = $matches['whitespace'][0];
|
||||
} else {
|
||||
$whitespace = null;
|
||||
}
|
||||
|
||||
$html .= substr($template, 0, $offset);
|
||||
$html .= $this->_renderTag($modifier, $tag_name);
|
||||
$template = substr($template, $offset + strlen($tag));
|
||||
|
||||
$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, $whitespace);
|
||||
}
|
||||
|
||||
$this->_otag = $otag_orig;
|
||||
@@ -473,7 +534,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 '=':
|
||||
return $this->_changeDelimiter($tag_name);
|
||||
@@ -483,9 +544,13 @@ class Mustache {
|
||||
break;
|
||||
case '>':
|
||||
case '<':
|
||||
return $this->_renderPartial($tag_name);
|
||||
return $this->_renderPartial($tag_name, $whitespace);
|
||||
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);
|
||||
@@ -548,9 +613,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)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,13 +628,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 '';
|
||||
}
|
||||
|
||||
@@ -588,7 +653,6 @@ class Mustache {
|
||||
$this->_context = $new;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the latest context from the stack.
|
||||
*
|
||||
@@ -621,7 +685,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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
{{grand_parent_id}}
|
||||
{{#parent_contexts}}
|
||||
{{grand_parent_id}}
|
||||
{{parent_id}}
|
||||
{{parent_id}} ({{grand_parent_id}})
|
||||
{{#child_contexts}}
|
||||
{{grand_parent_id}}
|
||||
{{parent_id}}
|
||||
{{child_id}}
|
||||
{{child_id}} ({{parent_id}} << {{grand_parent_id}})
|
||||
{{/child_contexts}}
|
||||
{{/parent_contexts}}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user