Commit 5c420197 authored by Taddeus Kroes's avatar Taddeus Kroes

Added template parser classes.

- The Template class represents a template file, consisting of blocks and
  variables.
- The Node class is used to create a tree structure of the blocks in a
  template file, and of the data that is mapped on the blocks.
parent 08ec5a5b
......@@ -69,9 +69,10 @@ class FileNotFoundError extends \RuntimeException {
* Sets an error message of the form 'File "path/to/file.php" does not exist.'.
*
* @param string $path Path to the file that does not exist.
* @param bool $is_dir Whether the path points to a directory (defaults to false).
*/
function __construct($path) {
$this->message = sprintf('File "%s" does not exist.', $path);
function __construct($path, $is_dir=false) {
$this->message = sprintf('%s "%s" does not exist.', $is_dir ? 'Directory' : 'File', $path);
}
}
......
<?php
/**
* Tree data structure, used for rendering purposes.
*
* @author Taddeus Kroes
* @version 1.0
* @date 13-07-2012
*/
namespace BasicWeb;
require_once 'base.php';
/**
* Tree node.
*
* Each tree node has a (non-unique) name, a list of variables, and zero or
* more children.
*
* @package BasicWeb
*/
class Node extends Base {
/**
* The number of Node instances, used to create unique node id's.
*
* @var int
*/
private static $count = 0;
/**
* The unique id of this Bloc.
*
* @var int
*/
private $id;
/**
* The node's name.
*
* @var string
*/
private $name;
/**
* An optional parent node.
*
* If NULL, this node is the root of the data tree.
*
* @var Node
*/
private $parent_node;
/**
* Child nodes.
*
* @var array
*/
private $children = array();
/**
* Variables in this node.
*
* All variables in a node are also available in its descendants through
* {@link get()}.
*
* @var array
*/
private $variables = array();
/**
* Constructor.
*
* The id of the node is determined by the node counter.
*
* @param string $name The node's name.
* @param Node|null &$parent_node A parent node (optional).
* @param int|null $id An id to assign. If none is specified, a new unique
* id is generated.
* @uses $count
*/
function __construct($name='', Node &$parent_node=null, $id=null) {
$this->id = $id ? $id : ++self::$count;
$this->name = $name;
$this->parent_node = $parent_node;
}
/**
* Get the node's unique id.
*
* @return int The node's id.
*/
function get_id() {
return $this->id;
}
/**
* Get the node's name.
*
* @return string The node's name.
*/
function get_name() {
return $this->name;
}
/**
* Get the node's parent.
*
* @return Node|null The parent node if any, NULL otherwise.
*/
function get_parent() {
return $this->parent_node;
}
/**
* Get the node's children.
*
* @return array A list of child nodes.
*/
function get_children() {
return $this->children;
}
/**
* Check if a node is the same instance or a copy of this node.
*
* @param Node $node The node to compare this node to.
* @return bool Whether the nodes have the same unique id.
*/
function is(Node $node) {
return $node->get_id() == $this->id;
}
/**
* Check if this node is the root node of the tree.
*
* A node is the root node if it has no parent.
*
* @return bool Whether this node is the root node.
*/
function is_root() {
return $this->parent_node === null;
}
/**
* Check if this node is a leaf node of the tree.
*
* A node is a leaf if it has no children.
*
* @return bool Whether this node is a leaf node.
*/
function is_leaf() {
return !count($this->children);
}
/**
* Add a child node.
*
* @param Node &$node The child node to add.
* @param bool $set_parent Whether to set this node as the child's parent
* (defaults to TRUE).
*/
function add_child(Node &$node, $set_parent=true) {
$this->children[] = $node;
$set_parent && $node->set_parent($this);
}
/**
* Add a child node.
*
* @param string $name The name of the node to add.
* @param array $data Data to set in the created node (optional).
* @return Node The created node.
*/
function add($name, array $data=array()) {
$node = new self($name, $this);
$this->add_child($node, false);
return $node->set($data);
}
/**
* Remove a child node.
*
* @param Node &$child The node to remove.
*/
function remove_child(Node &$child) {
foreach( $this->children as $i => $node )
$node->is($child) && array_splice($this->children, $i, 1);
}
/**
* Remove this node from its parent.
*
* @throws \RuntimeException If the node has no parent.
* @return Node This node.
*/
function remove() {
if( $this->is_root() )
throw new \RuntimeException('Cannot remove the root node of a tree.');
$this->parent_node->remove_child($this);
foreach( $this->children as $child )
$child->set_parent(null);
return $this;
}
/**
* Set the node's parent.
*
* Removes this node as child of the original parent, if a parent was
* already set.
*
* @param Node|null $parent The parent node to set.
* @return Node This node.
*/
function set_parent($parent) {
if( $this->parent_node !== null )
$this->parent_node->remove_child($this);
$this->parent_node = &$parent;
return $this;
}
/**
* Set the value of one or more variables in the node.
*
* @param string|array $name Either a single variable name, or a set of name/value pairs.
* @param mixed $value The value of a single variable to set.
* @return Node This node.
*/
function set($name, $value=null) {
if( is_array($name) ) {
foreach( $name as $var => $val )
$this->variables[$var] = $val;
} else {
$this->variables[$name] = $value;
}
return $this;
}
/**
* Get the value of a variable.
*
* @param string $name The name of the variable to get the value of.
* @return mixed The value of the variable if it exists, NULL otherwise.
*/
function get($name) {
// Variable inside this node?
if( isset($this->variables[$name]) )
return $this->variables[$name];
// Variable in one of ancestors?
if( $this->parent_node !== null )
return $this->parent_node->get($name);
// All nodes up to the tree's root node do not contain the variable
return null;
}
/**
* Set the value of a variable.
*
* This method provides a shortcut for {@link set()}.
*
* @param string $name The name of the variable to set the value of.
* @param mixed $value The value to set.
*/
function __set($name, $value) {
$this->set($name, $value);
}
/**
* Get the value of a variable.
*
* This method provides a shortcut for {@link get()}.
*
* @param string $name The name of the variable to get the value of.
* @return mixed The value of the variable if it exists, NULL otherwise.
*/
function __get($name) {
return $this->get($name);
}
/**
* Find all child nodes that have the specified name.
*
* @param string $name The name of the nodes to find.
* @return array The positively matched nodes.
*/
function find($name) {
$has_name = function($child) use ($name) {
return $child->get_name() == $name;
};
return array_values(array_filter($this->children, $has_name));
}
/**
* Create a copy of this node.
*
* The copy will have the same list of children and variables. In case of
* a 'deep copy', the list of children is also cloned recursively.
*
* @param bool $deep Whether to create a deep copy.
* @return Node A copy of this node.
*/
function copy($deep=false) {
$copy = new self($this->name, $this->parent_node, $this->id);
$copy->set($this->variables);
foreach( $this->children as $child ) {
if( $deep ) {
$child_copy = $child->copy(true);
$copy->add_child($child_copy);
} else {
$copy->add_child($child, false);
}
}
return $copy;
}
}
?>
\ No newline at end of file
<?php
/**
* HTML template rendering functions.
*
* @author Taddeus Kroes
* @version 1.0
* @date 14-07-2012
*/
namespace BasicWeb;
require_once 'node.php';
/**
* A Template object represents a template file.
*
* A template file contains 'blocks' that can be rendered zero or more times.
* Each block has a set of properties that can be accessed using curly
* brackets ('{' and '}'). Curly brackets may contain macro's to minimize
* common view logic in controllers.
*
* Example template 'page.tpl':
* <code>
* &lt;html&gt;
* &lt;head&gt;
* &lt;title&gt;{page_title}&lt;/title&gt;
* &lt;/head&gt;
* &lt;body&gt;
* &lt;h1&gt;{page_title}&lt;/h1&gt;
* &lt;div id="content"&gt;{page_content}&lt;/div&gt;
* &lt;div id="ads"&gt;
* {block:ad}
* &lt;div class="ad"&gt;{ad_content}&lt;/div&gt;
* {end}
* &lt;/div&gt;
* &lt;/body&gt;
* &lt;/html&gt;
* </code>
* And the corresponding PHP code:
* <code>
* $tpl = new Template('page');
* $tpl->set(array(
* 'page_title' => 'Some title',
* 'page_content' => 'Lorem ipsum ...'
* ));
*
* foreach( array('Some ad', 'Another ad', 'More ads') as $ad )
* $tpl->add('ad')->set('ad_content', $ad);
*
* echo $tpl->render();
* </code>
* The output will be:
* <code>
* &lt;html&gt;
* &lt;head&gt;
* &lt;title&gt;Some title&lt;/title&gt;
* &lt;/head&gt;
* &lt;body&gt;
* &lt;h1&gt;Some title&lt;/h1&gt;
* &lt;div id="content"&gt;Some content&lt;/div&gt;
* &lt;div id="ads"&gt;
* &lt;div class="ad"&gt;Some ad&lt;/div&gt;
* &lt;div class="ad"&gt;Another ad&lt;/div&gt;
* &lt;div class="ad"&gt;More ads&lt;/div&gt;
* &lt;/div&gt;
* &lt;/body&gt;
* &lt;/html&gt;
* </code>
*
* @package BasicWeb
*/
class Template extends Node {
/**
* Default extension of template files.
*
* @var array
*/
const DEFAULT_EXTENSION = '.tpl';
/**
* Root directories from which template files are included.
*
* @var array
*/
private static $include_path = array();
/**
* The path the template was found in.
*
* @var string
*/
private $path;
/**
* The content of the template file.
*
* @var string
*/
private $file_content;
/**
* The block structure of the template file.
*
* @var Node
*/
private $root_block;
/**
* Create a new Template object, representing a template file.
*
* Template files are assumed to have the .tpl extension. If no extension
* is specified, '.tpl' is appended to the filename.
*
* @param string $filename The path to the template file from one of the root directories.
*/
function __construct($filename) {
// Add default extension if none is found
strpos($filename, '.') === false && $filename .= self::DEFAULT_EXTENSION;
$look_in = count(self::$include_path) ? self::$include_path : array('.');
$found = false;
foreach( $look_in as $root ) {
$path = $root.$filename;
if( file_exists($path) ) {
$this->path = $path;
$this->file_content = file_get_contents($path);
$found = true;
break;
}
}
if( !$found ) {
throw new \RuntimeException(
sprintf("Could not find template file \"%s\", looked in folders:\n%s",
$filename, implode("\n", $look_in))
);
}
$this->parse_blocks();
}
/**
* Get the path to the template file (including one of the include paths).
*
* @return string the path to the template file.
*/
function get_path() {
return $this->path;
}
/**
* Parse the content of the template file into a tree structure of blocks
* and variables.
*/
private function parse_blocks() {
$current = $root = new Node('block');
$after = $this->file_content;
$line_count = 0;
while( preg_match('/(.*?)\{([^}]+)}(.*)/s', $after, $matches) ) {
list($before, $brackets_content, $after) = array_slice($matches, 1);
$line_count += substr_count($before, "\n");
//var_dump(array_slice($matches, 1));
// Everything before the new block belongs to its parent
$current->add('html')->set('content', $before);
if( $brackets_content == 'end' ) {
// {end} encountered, go one level up in the tree
if( $current->is_root() )
throw new ParseError($this, 'unexpected {end}', $line_count + 1);
$current = $current->get_parent();
} elseif( substr($brackets_content, 0, 6) == 'block:' ) {
// {block:...} encountered
$block_name = substr($brackets_content, 6);
// Go one level deeper into the tree
$current = $current->add('block')->set('name', $block_name);
} else {
// Variable or something else
$current->add('variable')->set('content', $brackets_content);
}
}
$line_count += substr_count($after, "\n");
if( $current !== $root )
throw new ParseError($this, 'missing {end}', $line_count + 1);
// Add the last remaining content to the root node
$root->add('html')->set('content', $after);
$this->root_block = $root;
}
/**
* Replace blocks and variables in the template's content.
*
* @return string The template's content, with replaced blocks and variables.
*/
function render() {
// Use recursion to parse all blocks from the root level
return self::render_block($this->root_block, $this);
}
/**
* Render a single block, recursively parsing its sub-blocks with a given data scope.
*
* @param Node $block The block to render.
* @param Node $data The data block to search in for the variable values.
* @return string The rendered block.
* @uses replace_variable()
*/
private static function render_block(Node $block, Node $data) {
$html = '';
foreach( $block->get_children() as $child ) {
switch( $child->get_name() ) {
case 'html':
$html .= $child->get('content');
break;
case 'block':
$block_name = $child->get('name');
foreach( $data->find($block_name) as $child_data )
$html .= self::render_block($child, $child_data);
break;
case 'variable':
$html .= self::replace_variable($child->get('content'), $data);
}
}
return $html;
}
/**
* Replace a variable name if it exists within a given data scope.
*
* Applies any of the following macro's:
*
* --------
* Variable
* --------
* <code>{var_name[:func1:func2:...]}</code>
* *var_name* can be of the form *foo.bar*. In this case, *foo* is the
* name of an object or associative array variable. *bar* is a property
* name to get of the object, or the associative index to the array.
* *func1*, *func2*, etc. are helper functions that are executed in the
* same order as listed. The retuen value of each helper function replaces
* the previous variable value.
*
* ------------
* If-statement
* ------------
* <code>{if:condition:success_variable[:else:failure_variable]}</code>
* *condition* is evaluated to a boolean. If it evaluates to TRUE, the
* value of *success_variable* is used. Otherwise, the value of
* *failure_variable* is used (defaults to an empty string if no
* else-statement is specified).
*
* @param string $variable The variable to replace.
* @param Node $data The data block to search in for a value.
* @return string The variable's value if it exists, the original string
* with curly brackets otherwise.
* @throws \UnexpectedValueException If a helper function is not callable.
*/
private static function replace_variable($variable, Node $data) {
// If-(else-)statement
if( preg_match('/^if:([^:]*):(.*?)(?::else:(.*))?$/', $variable, $matches) ) {
$condition = $data->get($matches[1]);
$if = $data->get($matches[2]);
if( $condition )
return $if;
return count($matches) > 3 ? self::replace_variable($matches[3], $data) : '';
}
// Default: variable with optional helper functions
$parts = explode(':', $variable);
$name = $parts[0];
$helper_functions = array_slice($parts, 1);
if( strpos($name, '.') !== false ) {
// Variable of the form 'foo.bar'
list($variable_name, $property) = explode('.', $name, 2);
$object = $data->get($variable_name);
if( is_object($object) && property_exists($object, $property) ) {
// Object property
$value = $object->$property;
} elseif( is_array($object) && isset($object[$property]) ) {
// Associative array index
$value = $object[$property];
}
}
// Default: Simple variable name
if( !isset($value) )
$value = $data->get($name);
// Don't continue if the variable name is not found in the data block
if( $value !== null ) {
// Apply helper functions to the variable's value iteratively
foreach( $helper_functions as $func ) {
if( !is_callable($func) ) {
throw new \UnexpectedValueException(
sprintf('Helper function "%s" is not callable.', $func)
);
}
$value = $func($value);
}
return $value;
}
return '{'.$variable.'}';
}
/**
* Remove all current include paths.
*/
static function clear_include_path() {
self::$include_path = array();
}
/**
* Replace all include paths by a single new one.
*
* @param string $path The new path to set as root.
* @uses clear_include_path()
*/
static function set_root($path) {
self::clear_include_path();
self::add_root($path);
}
/**
* Add a new include path.
*
* @param string $path The path to add.
* @throws FileNotFoundError If the path does not exist.
*/
static function add_root($path) {
if( $path[strlen($path) - 1] != '/' )
$path .= '/';
if( !is_dir($path) )
throw new FileNotFoundError($path, true);
self::$include_path[] = $path;
}
}
/**
* Error, thrown when an error occurs during the parsing of a template file.
*
* @package BasicWeb
*/
class ParseError extends \RuntimeException {
/**
* Constructor.
*
* Sets an error message with the path to the template file and a line number.
*
* @param Template $tpl The template in which the error occurred.
* @param string $message A message describing the error.
* @param int $line The line number at which the error occurred.
*/
function __construct(Template $tpl, $message, $line) {
$this->message = sprintf('Parse error in file %s, line %d: %s',
$tpl->get_path(), $line, $message);
}
}
?>
\ No newline at end of file
foofoo
foobar
foobar
foobaz
foofoo
foobaz
bar
foofoo
first_foobar_var
first_foobaz_var
foofoo
second_foobar_var
third_foobar_var
second_foobaz_var
baz
\ No newline at end of file
bar
{block:foo}
foofoo
{block:bar}
{foobar}
{end}
{foobaz:strtolower}
{end}
baz
\ No newline at end of file
foo
my_foobar_variable
bar
my_foobaz_variable
baz
\ No newline at end of file
{block:foo}
foofoo
{block:bar}
foobar
{end}
foobaz
{end}
\ No newline at end of file
test
\ No newline at end of file
bar
{block:foo}
foofoo
{block:bar}
{foobar}
{end}
{foobaz:strtolower}
{end}
baz
\ No newline at end of file
{block:foo}
foofoo
{block:bar}
foobar
{end}
foobaz
\ No newline at end of file
{block:foo}
foofoo
foobar
{end}
{end}
\ No newline at end of file
foo
{foobar}
bar
{foobaz:strtolower}
baz
\ No newline at end of file
<?php
require_once 'node.php';
use \BasicWeb\Node;
class NodeTest extends PHPUnit_Framework_TestCase {
var $autoloader;
function setUp() {
$this->root = new Node('test node');
}
function test_get_id() {
$this->assertEquals($this->root->get_id(), 1);
$this->assertEquals(Node::create('')->get_id(), 2);
}
function test_get_name() {
$this->assertEquals($this->root->get_name(), 'test node');
$this->assertEquals(Node::create('second node')->get_name(), 'second node');
}
function test_get_parent() {
$this->assertNull($this->root->get_parent());
$this->assertSame(Node::create('', $this->root)->get_parent(), $this->root);
}
function test_is() {
$mirror = $this->root;
$this->assertTrue($mirror->is($this->root));
$this->assertFalse(Node::create('')->is($this->root));
}
function test_is_root() {
$this->assertTrue($this->root->is_root());
$this->assertFalse(Node::create('', $this->root)->is_root());
}
function test_add_child() {
$node = new Node('');
$this->root->add_child($node);
$this->assertAttributeEquals(array($node), 'children', $this->root);
$this->assertSame($node->get_parent(), $this->root);
}
/**
* @depends test_add_child
*/
function test_get_children() {
$this->assertEquals($this->root->get_children(), array());
$node = new Node('');
$this->root->add_child($node);
$this->assertSame($this->root->get_children(), array($node));
}
function test_add_child_no_set_parent() {
$node = new Node('');
$this->root->add_child($node, false);
$this->assertAttributeEquals(array($node), 'children', $this->root);
$this->assertNull($node->get_parent());
}
/**
* @depends test_add_child
*/
function test_is_leaf() {
$node = new Node('');
$this->root->add_child($node);
$this->assertTrue($node->is_leaf());
$this->assertFalse($this->root->is_leaf());
}
/**
* @depends test_add_child
*/
function test_add() {
$node = $this->root->add('name', array('foo' => 'bar'));
$this->assertEquals($node->get_name(), 'name');
$this->assertEquals($node->get('foo'), 'bar');
$this->assertSame($node->get_parent(), $this->root);
}
/**
* @depends test_add
*/
function test_remove_child() {
$node1 = $this->root->add('name', array('foo' => 'bar'));
$node2 = $this->root->add('name', array('foo' => 'bar'));
$this->root->remove_child($node2);
$this->assertAttributeSame(array($node1), 'children', $this->root);
}
/**
* @depends test_remove_child
*/
function test_remove_leaf() {
$node1 = $this->root->add('name', array('foo' => 'bar'));
$node2 = $this->root->add('name', array('foo' => 'bar'));
$node1->remove();
$this->assertAttributeSame(array($node2), 'children', $this->root);
}
/**
* @depends test_remove_leaf
*/
function test_remove_node() {
$node = $this->root->add('node');
$leaf = $node->add('leaf');
$node->remove();
$this->assertAttributeEquals(array(), 'children', $this->root);
$this->assertNull($leaf->get_parent());
}
/**
* @depends test_remove_child
* @expectedException \RuntimeException
*/
function test_remove_root() {
$node1 = $this->root->add('name', array('foo' => 'bar'));
$node2 = $this->root->add('name', array('foo' => 'bar'));
$this->root->remove();
$this->assertAttributeSame(array($node2), 'children', $this->root);
}
function test_set_single() {
$this->root->set('foo', 'bar');
$this->assertAttributeEquals(array('foo' => 'bar'), 'variables', $this->root);
$this->root->set('bar', 'baz');
$this->assertAttributeEquals(array('foo' => 'bar', 'bar' => 'baz'), 'variables', $this->root);
}
function test_set_return() {
$this->assertSame($this->root->set('foo', 'bar'), $this->root);
}
function test_set_multiple() {
$this->root->set(array('foo' => 'bar'));
$this->assertAttributeEquals(array('foo' => 'bar'), 'variables', $this->root);
$this->root->set(array('bar' => 'baz'));
$this->assertAttributeEquals(array('foo' => 'bar', 'bar' => 'baz'), 'variables', $this->root);
}
/**
* @depends test_set_single
*/
function test___set() {
$this->root->foo = 'bar';
$this->assertAttributeEquals(array('foo' => 'bar'), 'variables', $this->root);
$this->root->bar = 'baz';
$this->assertAttributeEquals(array('foo' => 'bar', 'bar' => 'baz'), 'variables', $this->root);
}
/**
* @depends test_set_multiple
*/
function test_get_direct() {
$this->root->set(array('foo' => 'bar', 'bar' => 'baz'));
$this->assertEquals($this->root->get('foo'), 'bar');
$this->assertEquals($this->root->get('bar'), 'baz');
}
/**
* @depends test_get_direct
*/
function test___get() {
$this->root->set(array('foo' => 'bar', 'bar' => 'baz'));
$this->assertEquals($this->root->foo, 'bar');
$this->assertEquals($this->root->bar, 'baz');
}
/**
* @depends test_set_single
*/
function test_get_ancestor() {
$this->root->set('foo', 'bar');
$node = $this->root->add('');
$this->assertEquals($node->get('foo'), 'bar');
}
function test_get_failure() {
$this->assertNull($this->root->get('foo'));
}
/**
* @depends test_get_name
*/
function test_find() {
$node1 = $this->root->add('foo');
$node2 = $this->root->add('bar');
$node3 = $this->root->add('foo');
$this->assertSame($this->root->find('foo'), array($node1, $node3));
}
/**
* @depends test_set_multiple
*/
function test_copy_simple() {
$copy = $this->root->copy();
$this->assertEquals($this->root, $copy);
$this->assertNotSame($this->root, $copy);
}
/**
* @depends test_copy_simple
*/
function test_copy_shallow() {
$child = $this->root->add('');
$copy = $this->root->copy();
$this->assertAttributeSame(array($child), 'children', $copy);
}
/**
* @depends test_get_children
* @depends test_copy_simple
*/
function test_copy_deep() {
$child = $this->root->add('foo');
$copy = $this->root->copy(true);
$copy_children = $copy->get_children();
$child_copy = reset($copy_children);
$this->assertNotSame($copy_children, $this->root->get_children());
$this->assertSame($child_copy->get_parent(), $copy);
}
}
?>
\ No newline at end of file
<?php
require_once 'template.php';
use BasicWeb\Template;
use BasicWeb\Node;
define('TEMPLATES_DIR', 'tests/_files/templates/');
class TemplateTest extends PHPUnit_Framework_TestCase {
/**
* @depends test_add_root_success
*/
function setUp() {
Template::set_root(TEMPLATES_DIR);
$this->tpl = new Template('foo');
$this->data = new Node();
$object = new stdClass();
$object->foo = 'bar';
$object->bar = 'baz';
$this->data->set(array(
'foo' => 'bar',
'bar' => 'baz',
'FOO' => 'BAR',
'true' => true,
'false' => false,
'array' => array('foo' => 'bar', 'bar' => 'baz'),
'object' => $object,
'foobar' => 'my_foobar_variable',
'foobaz' => 'MY_FOOBAZ_VARIABLE',
));
}
/**
* @expectedException BasicWeb\FileNotFoundError
* @expectedExceptionMessage Directory "non_existing_folder/" does not exist.
*/
function test_add_root_failure() {
Template::add_root('non_existing_folder');
}
function assert_include_path_equals($expected) {
$include_path = new ReflectionProperty('BasicWeb\Template', 'include_path');
$include_path->setAccessible(true);
$this->assertEquals($expected, $include_path->getValue());
}
function test_clear_include_path() {
Template::clear_include_path();
$this->assert_include_path_equals(array());
}
/**
* @depends test_clear_include_path
*/
function test_add_root_success() {
Template::clear_include_path();
Template::add_root(TEMPLATES_DIR);
$this->assert_include_path_equals(array(TEMPLATES_DIR));
Template::add_root('tests/_files');
$this->assert_include_path_equals(array(TEMPLATES_DIR, 'tests/_files/'));
}
/**
* @depends test_add_root_success
*/
function test_set_root() {
Template::clear_include_path();
Template::add_root(TEMPLATES_DIR);
Template::add_root('tests/_files');
Template::set_root(TEMPLATES_DIR);
$this->assert_include_path_equals(array(TEMPLATES_DIR));
}
/**
* @expectedException RuntimeException
*/
function test_non_existing_template() {
$bar = new Template('bar');
}
function test_other_root() {
Template::add_root('tests/_files/other_templates');
new Template('bar');
}
function test_get_path() {
$this->assertEquals(TEMPLATES_DIR.'foo.tpl', $this->tpl->get_path());
}
function get_property($object, $property_name) {
$rp = new ReflectionProperty($object, $property_name);
$rp->setAccessible(true);
return $rp->getValue($object);
}
function test_parse_blocks_simple() {
$root_block = $this->get_property($this->tpl, 'root_block');
$this->assert_is_block_node($root_block, null, 1);
list($child) = $root_block->get_children();
$this->assert_is_html_node($child, 'test');
}
function assert_is_html_node($node, $content) {
$this->assertEquals('html', $node->get_name());
$this->assertEquals($content, $node->get('content'));
$this->assertEquals(array(), $node->get_children());
}
function assert_is_block_node($node, $block_name, $child_count) {
$this->assertEquals('block', $node->get_name());
$this->assertSame($block_name, $node->get('name'));
$this->assertNull($node->get('content'));
$this->assertEquals($child_count, count($node->get_children()));
}
function assert_is_variable_node($node, $brackets_content) {
$this->assertEquals('variable', $node->get_name());
$this->assertEquals($brackets_content, $node->get('content'));
$this->assertEquals(array(), $node->get_children());
}
/**
* @depends test_parse_blocks_simple
*/
function test_parse_blocks_blocks() {
$tpl = new Template('blocks');
$root_block = $this->get_property($tpl, 'root_block');
$this->assert_is_block_node($root_block, null, 3);
list($before, $foo, $after) = $root_block->get_children();
$this->assert_is_html_node($before, '');
$this->assert_is_block_node($foo, 'foo', 3);
$this->assert_is_html_node($after, '');
list($foofoo, $bar, $foobaz) = $foo->get_children();
$this->assert_is_html_node($foofoo, "\nfoofoo\n\t");
$this->assert_is_block_node($bar, 'bar', 1);
$this->assert_is_html_node($foobaz, "\nfoobaz\n");
list($foobar) = $bar->get_children();
$this->assert_is_html_node($foobar, "\n\tfoobar\n\t");
}
/**
* @depends test_parse_blocks_blocks
* @expectedException BasicWeb\ParseError
* @expectedExceptionMessage Parse error in file tests/_files/templates/unexpected_end.tpl, line 5: unexpected {end}
*/
function test_parse_blocks_unexpected_end() {
new Template('unexpected_end');
}
/**
* @depends test_parse_blocks_blocks
* @expectedException BasicWeb\ParseError
* @expectedExceptionMessage Parse error in file tests/_files/templates/missing_end.tpl, line 6: missing {end}
*/
function test_parse_blocks_missing_end() {
new Template('missing_end');
}
/**
* @depends test_parse_blocks_simple
*/
function test_parse_blocks_variables() {
$tpl = new Template('variables');
$root_block = $this->get_property($tpl, 'root_block');
$this->assert_is_block_node($root_block, null, 5);
list($foo, $foobar, $bar, $foobaz, $baz) = $root_block->get_children();
$this->assert_is_html_node($foo, "foo\n");
$this->assert_is_variable_node($foobar, 'foobar');
$this->assert_is_html_node($bar, "\nbar\n");
$this->assert_is_variable_node($foobaz, 'foobaz:strtolower');
$this->assert_is_html_node($baz, "\nbaz");
}
/**
* @depends test_parse_blocks_blocks
* @depends test_parse_blocks_variables
*/
function test_parse_blocks_full() {
$tpl = new Template('full');
$root_block = $this->get_property($tpl, 'root_block');
$this->assert_is_block_node($root_block, null, 3);
list($bar, $foo, $baz) = $root_block->get_children();
$this->assert_is_html_node($bar, "bar\n");
$this->assert_is_block_node($foo, 'foo', 5);
$this->assert_is_html_node($baz, "\nbaz");
list($foofoo, $bar, $first_space, $foobaz, $second_space) = $foo->get_children();
$this->assert_is_html_node($foofoo, "\nfoofoo\n\t");
$this->assert_is_block_node($bar, 'bar', 3);
$this->assert_is_html_node($first_space, "\n");
$this->assert_is_variable_node($foobaz, 'foobaz:strtolower');
$this->assert_is_html_node($second_space, "\n");
list($space_before, $foobar, $space_after) = $bar->get_children();
$this->assert_is_html_node($space_before, "\n\t");
$this->assert_is_variable_node($foobar, 'foobar');
$this->assert_is_html_node($space_after, "\n\t");
}
function assert_replaces($expected, $variable) {
$rm = new ReflectionMethod('BasicWeb\Template', 'replace_variable');
$rm->setAccessible(true);
$this->assertEquals($expected, $rm->invoke(null, $variable, $this->data));
}
function test_replace_variable_simple() {
$this->assert_replaces('bar', 'foo');
}
/**
* @depends test_replace_variable_simple
*/
function test_replace_variable_helper_functions() {
$this->assert_replaces('Bar', 'foo:ucfirst');
$this->assert_replaces('bar', 'FOO:strtolower');
$this->assert_replaces('Bar', 'FOO:strtolower:ucfirst');
}
/**
* @expectedException UnexpectedValueException
* @expectedExceptionMessage Helper function "idonotexist" is not callable.
*/
function test_replace_variable_non_callable_helper_function() {
$this->assert_replaces(null, 'foo:idonotexist');
}
function test_replace_variable_not_found() {
$this->assert_replaces('{idonotexist}', 'idonotexist');
}
function test_replace_variable_associative_array() {
$this->assert_replaces('bar', 'array.foo');
$this->assert_replaces('baz', 'array.bar');
}
function test_replace_variable_object_property() {
$this->assert_replaces('bar', 'object.foo');
$this->assert_replaces('baz', 'object.bar');
}
function test_replace_variable_if_statement() {
$this->assert_replaces('bar', 'if:true:foo');
$this->assert_replaces('', 'if:false:foo');
$this->assert_replaces('bar', 'if:true:foo:else:bar');
$this->assert_replaces('baz', 'if:false:foo:else:bar');
$this->assert_replaces('Bar', 'if:false:foo:else:FOO:strtolower:ucfirst');
}
/*function assert_block_renders($expected_file, $block, $data) {
$rm = new ReflectionMethod('BasicWeb\Template', 'render_block');
$rm->setAccessible(true);
$expected_file = "tests/_files/rendered/$expected_file.html";
$this->assertStringEqualsFile($expected_file, $rm->invoke(null, $block, $data));
}*/
function assert_renders($expected_file, $tpl) {
$expected_file = "tests/_files/rendered/$expected_file.html";
$this->assertStringEqualsFile($expected_file, $tpl->render());
}
function test_render_simple() {
$this->assertEquals('test', $this->tpl->render());
}
/**
* @depends test_replace_variable_helper_functions
*/
function test_render_variable() {
$tpl = new Template('variables');
$tpl->set(array(
'foobar' => 'my_foobar_variable',
'foobaz' => 'MY_FOOBAZ_VARIABLE'
));
$this->assert_renders('variables', $tpl);
}
/**
* @depends test_render_simple
*/
function test_render_blocks() {
$tpl = new Template('blocks');
$foo = $tpl->add('foo');
$foo->add('bar');
$foo->add('bar');
$tpl->add('foo');
$this->assert_renders('blocks', $tpl);
}
/**
* @depends test_render_variable
* @depends test_render_blocks
*/
function test_render_full() {
$tpl = new Template('full');
$first_foo = $tpl->add('foo')->set('foobaz', 'FIRST_FOOBAZ_VAR');
$first_foo->add('bar')->set('foobar', 'first_foobar_var');
$second_foo = $tpl->add('foo')->set('foobaz', 'SECOND_FOOBAZ_VAR');
$second_foo->add('bar')->set('foobar', 'second_foobar_var');
$second_foo->add('bar')->set('foobar', 'third_foobar_var');
$this->assert_renders('full', $tpl);
}
}
?>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment