diff --git a/Mustache.php b/Mustache.php
index e3c61a7..0656cef 100644
--- a/Mustache.php
+++ b/Mustache.php
@@ -98,7 +98,7 @@ class Mustache {
self::PRAGMA_UNESCAPED
);
- protected $_localPragmas;
+ protected $_localPragmas = array();
/**
* Mustache class constructor.
@@ -130,7 +130,7 @@ class Mustache {
public function __clone() {
$this->_otag = '{{';
$this->_ctag = '}}';
- $this->_localPragmas = null;
+ $this->_localPragmas = array();
if ($keys = array_keys($this->_context)) {
$last = array_pop($keys);
@@ -205,7 +205,7 @@ class Mustache {
protected function _renderSection($template) {
$otag = preg_quote($this->_otag, '/');
$ctag = preg_quote($this->_ctag, '/');
- $regex = '/' . $otag . '(\\^|\\#)\\s*(.+?)\\s*' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\s*\\2\\s*' . $ctag . '\\s*/m';
+ $regex = '/' . $otag . '(\\^|\\#)\\s*(.+?)\\s*' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\s*\\2\\s*' . $ctag . '\\s*/ms';
$matches = array();
while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE)) {
@@ -289,7 +289,7 @@ class Mustache {
$otag = preg_quote($this->_otag, '/');
$ctag = preg_quote($this->_ctag, '/');
- $regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/';
+ $regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/s';
return preg_replace_callback($regex, array($this, '_renderPragma'), $template);
}
@@ -389,7 +389,7 @@ class Mustache {
$otag = preg_quote($this->_otag, '/');
$ctag = preg_quote($this->_ctag, '/');
- $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/";
+ $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/s";
$html = '';
$matches = array();
@@ -456,14 +456,12 @@ class Mustache {
return $this->_renderUnescaped($tag_name);
}
break;
- case '':
- default:
- if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
- return $this->_renderUnescaped($tag_name);
- } else {
- return $this->_renderEscaped($tag_name);
- }
- break;
+ }
+
+ if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
+ return $this->_renderUnescaped($modifier . $tag_name);
+ } else {
+ return $this->_renderEscaped($modifier . $tag_name);
}
}
@@ -475,7 +473,7 @@ class Mustache {
* @return string
*/
protected function _renderEscaped($tag_name) {
- return htmlentities($this->_getVariable($tag_name), null, $this->_charset);
+ return htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset);
}
/**
@@ -527,7 +525,7 @@ class Mustache {
$otag = preg_quote($this->_otag, '/');
$ctag = preg_quote($this->_ctag, '/');
- $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/";
+ $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/s";
return '';
}
@@ -608,10 +606,10 @@ class Mustache {
protected function _findVariableInContext($tag_name, $context) {
foreach ($context as $view) {
if (is_object($view)) {
- if (isset($view->$tag_name)) {
- return $view->$tag_name;
- } else if (method_exists($view, $tag_name)) {
+ if (method_exists($view, $tag_name)) {
return $view->$tag_name();
+ } else if (isset($view->$tag_name)) {
+ return $view->$tag_name;
}
} else if (isset($view[$tag_name])) {
return $view[$tag_name];
diff --git a/README.markdown b/README.markdown
index ab27846..18e72d4 100644
--- a/README.markdown
+++ b/README.markdown
@@ -83,7 +83,7 @@ Known Issues
* Sections don't respect delimiter changes -- `delimiters` example currently fails with an
"unclosed section" exception.
- * Test coverage is incomplete.
+ * Mustache isn't always very good at whitespace.
See Also
diff --git a/examples/escaped/Escaped.php b/examples/escaped/Escaped.php
index e6605bc..2852196 100644
--- a/examples/escaped/Escaped.php
+++ b/examples/escaped/Escaped.php
@@ -1,5 +1,5 @@
Shark";
+ public $title = '"Bear" > "Shark"';
}
\ No newline at end of file
diff --git a/examples/escaped/escaped.txt b/examples/escaped/escaped.txt
index dcab18d..6ba3657 100644
--- a/examples/escaped/escaped.txt
+++ b/examples/escaped/escaped.txt
@@ -1 +1 @@
-
Bear > Shark
\ No newline at end of file
+"Bear" > "Shark"
\ No newline at end of file
diff --git a/examples/section_iterator_objects/SectionIteratorObjects.php b/examples/section_iterator_objects/SectionIteratorObjects.php
index fa5213c..7b65597 100644
--- a/examples/section_iterator_objects/SectionIteratorObjects.php
+++ b/examples/section_iterator_objects/SectionIteratorObjects.php
@@ -3,38 +3,14 @@
class SectionIteratorObjects extends Mustache {
public $start = "It worked the first time.";
- public function middle() {
- return new IteratorObject();
- }
-
- public $final = "Then, surprisingly, it worked the final time.";
-}
-
-class IteratorObject implements Iterator {
- protected $_position = 0;
-
protected $_data = array(
array('item' => 'And it worked the second time.'),
array('item' => 'As well as the third.'),
);
- public function rewind() {
- $this->_position = 0;
+ public function middle() {
+ return new ArrayIterator($this->_data);
}
- public function current() {
- return $this->_data[$this->_position];
- }
-
- public function key() {
- return $this->_position;
- }
-
- public function next() {
- ++$this->_position;
- }
-
- public function valid() {
- return isset($this->_data[$this->_position]);
- }
+ public $final = "Then, surprisingly, it worked the final time.";
}
\ No newline at end of file
diff --git a/examples/sections_spaces/SectionsSpaces.php b/examples/sections_spaces/SectionsSpaces.php
new file mode 100644
index 0000000..9b5adb8
--- /dev/null
+++ b/examples/sections_spaces/SectionsSpaces.php
@@ -0,0 +1,14 @@
+ "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
new file mode 100644
index 0000000..67e2606
--- /dev/null
+++ b/examples/sections_spaces/sections_spaces.mustache
@@ -0,0 +1,9 @@
+ * {{ 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
new file mode 100644
index 0000000..a7ca703
--- /dev/null
+++ b/examples/sections_spaces/sections_spaces.txt
@@ -0,0 +1,9 @@
+ * 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/MustacheExceptionTest.php b/test/MustacheExceptionTest.php
new file mode 100644
index 0000000..39a0f5b
--- /dev/null
+++ b/test/MustacheExceptionTest.php
@@ -0,0 +1,97 @@
+pickyMustache = new PickyMustache();
+ $this->slackerMustache = new SlackerMustache();
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testThrowsUnknownVariableException() {
+ $this->pickyMustache->render('{{not_a_variable}}');
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testThrowsUnclosedSectionException() {
+ $this->pickyMustache->render('{{#unclosed}}');
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testThrowsUnexpectedCloseSectionException() {
+ $this->pickyMustache->render('{{/unopened}}');
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testThrowsUnknownPartialException() {
+ $this->pickyMustache->render('{{>impartial}}');
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testThrowsUnknownPragmaException() {
+ $this->pickyMustache->render('{{%SWEET-MUSTACHE-BRO}}');
+ }
+
+ public function testDoesntThrowUnclosedSectionException() {
+ $this->assertEquals('', $this->slackerMustache->render('{{#unclosed}}'));
+ }
+
+ public function testDoesntThrowUnexpectedCloseSectionException() {
+ $this->assertEquals('', $this->slackerMustache->render('{{/unopened}}'));
+ }
+
+ public function testDoesntThrowUnknownPartialException() {
+ $this->assertEquals('', $this->slackerMustache->render('{{>impartial}}'));
+ }
+
+ /**
+ * @expectedException MustacheException
+ */
+ public function testGetPragmaOptionsThrowsExceptionsIfItThinksYouHaveAPragmaButItTurnsOutYouDont() {
+ $mustache = new TestableMustache();
+ $mustache->testableGetPragmaOptions('PRAGMATIC');
+ }
+}
+
+class PickyMustache extends Mustache {
+ protected $_throwsExceptions = array(
+ MustacheException::UNKNOWN_VARIABLE => true,
+ MustacheException::UNCLOSED_SECTION => true,
+ MustacheException::UNEXPECTED_CLOSE_SECTION => true,
+ MustacheException::UNKNOWN_PARTIAL => true,
+ MustacheException::UNKNOWN_PRAGMA => true,
+ );
+}
+
+class SlackerMustache extends Mustache {
+ protected $_throwsExceptions = array(
+ MustacheException::UNKNOWN_VARIABLE => false,
+ MustacheException::UNCLOSED_SECTION => false,
+ MustacheException::UNEXPECTED_CLOSE_SECTION => false,
+ MustacheException::UNKNOWN_PARTIAL => false,
+ MustacheException::UNKNOWN_PRAGMA => false,
+ );
+}
+
+class TestableMustache extends Mustache {
+ public function testableGetPragmaOptions($pragma_name) {
+ return $this->_getPragmaOptions($pragma_name);
+ }
+}
\ No newline at end of file
diff --git a/test/MustacheObjectSectionTest.php b/test/MustacheObjectSectionTest.php
index f9ae45b..17ad2e0 100644
--- a/test/MustacheObjectSectionTest.php
+++ b/test/MustacheObjectSectionTest.php
@@ -17,6 +17,12 @@ class MustacheObjectSectionTest extends PHPUnit_Framework_TestCase {
$gamma = new Gamma();
$this->assertEquals('Foo', $gamma->render('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}'));
}
+
+ public function testSectionObjectWithFunction() {
+ $alpha = new Alpha();
+ $alpha->foo = new Delta();
+ $this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}'));
+ }
}
class Alpha extends Mustache {
@@ -53,4 +59,12 @@ class Gamma extends Mustache {
public function __construct() {
$this->bar = new Beta();
}
+}
+
+class Delta extends Mustache {
+ protected $_name = 'Foo';
+
+ public function name() {
+ return $this->_name;
+ }
}
\ No newline at end of file
diff --git a/test/MustachePragmaImplicitIteratorTest.php b/test/MustachePragmaImplicitIteratorTest.php
index bf39091..207a8c6 100644
--- a/test/MustachePragmaImplicitIteratorTest.php
+++ b/test/MustachePragmaImplicitIteratorTest.php
@@ -1,7 +1,6 @@
"Known issue: sections don't respect delimiter changes",
+ 'SectionsSpaces' => "Known issue: Mustache fails miserably at whitespace",
+ );
+
/**
* Test Mustache constructor.
*
@@ -136,6 +141,23 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa')));
}
+ public function testRenderWithPartials() {
+ $m = new Mustache('{{>stache}}', null, array('stache' => '{{first_name}} {{last_name}}'));
+ $this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin')));
+ $this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa')));
+ }
+
+ /**
+ * Mustache should allow newlines (and other whitespace) in comments and all other tags.
+ *
+ * @access public
+ * @return void
+ */
+ public function testNewlinesInComments() {
+ $m = new Mustache("{{! comment \n \t still a comment... }}");
+ $this->assertEquals('', $m->render());
+ }
+
/**
* Mustache should return the same thing when invoked multiple times.
*
@@ -166,10 +188,9 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
$this->assertEquals($first, $second);
}
-
/**
* Mustache should not use templates passed to the render() method for subsequent invocations.
- *
+ *
* @access public
* @return void
*/
@@ -177,7 +198,7 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
$m = new Mustache('Sirve.');
$this->assertEquals('No sirve.', $m->render('No sirve.'));
$this->assertEquals('Sirve.', $m->render());
-
+
$m2 = new Mustache();
$this->assertEquals('No sirve.', $m2->render('No sirve.'));
$this->assertEquals('', $m2->render());
@@ -186,14 +207,17 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
/**
* testClone function.
*
+ * @group examples
* @dataProvider getExamples
* @access public
+ * @param string $class
+ * @param string $template
+ * @param string $output
* @return void
*/
public function test__clone($class, $template, $output) {
- if ($class == 'Delimiters') {
- $this->markTestSkipped("Known issue: sections don't respect delimiter changes");
- return;
+ if (isset($this->knownIssues[$class])) {
+ return $this->markTestSkipped($this->knownIssues[$class]);
}
$m = new $class;
@@ -214,17 +238,17 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
/**
* Test everything in the `examples` directory.
*
+ * @group examples
* @dataProvider getExamples
* @access public
- * @param mixed $class
- * @param mixed $template
- * @param mixed $output
+ * @param string $class
+ * @param string $template
+ * @param string $output
* @return void
*/
public function testExamples($class, $template, $output) {
- if ($class == 'Delimiters') {
- $this->markTestSkipped("Known issue: sections don't respect delimiter changes");
- return;
+ if (isset($this->knownIssues[$class])) {
+ return $this->markTestSkipped($this->knownIssues[$class]);
}
$m = new $class;