Commit 729e3ced authored by Taddes Kroes's avatar Taddes Kroes

Added block parsing fucntionality to template parser.

parent 3c8c11a1
......@@ -33,6 +33,13 @@ abstract class pQueryConfig {
*/
const ERROR_IS_FATAL = true;
/**
* Name of the utilities folder
*
* @var string
*/
const UTILS_FOLDER = 'utils/';
/**
* Config for MySQL plugin.
*
......
......@@ -150,10 +150,26 @@ class pQuery {
return $obj;
}
/**
* Try to load an utility file.
*
* @param string $basename
*/
static function load_util($basename) {
$path = PQUERY_ROOT.pQueryConfig::UTILS_FOLDER.$basename.'.php';
if( !file_exists($path) ) {
return self::error('Utility "%s" could not be loaded (looked in "%s").',
$basename, $path);
}
include_once $path;
}
/**
* Try to load the file containing the utility class for a specific variable type.
*
* @param mixed $type the variable type of the class to load.
* @param string $type the variable type of the class to load.
*/
static function load_plugin($type) {
$path = PQUERY_ROOT.sprintf(self::PLUGIN_FILENAME_PATTERN, $type);
......
......@@ -5,9 +5,11 @@
* @package pQuery
*/
__p::load_util('block');
/**
* @todo Documentation
* @property $ Alias for {@link pQuery::variable}.
* @property string $content The template's content.
*/
class pQueryTemplate extends pQuery implements pQueryExtension {
static $accepts = array('string' => 'open_template_file');
......@@ -23,7 +25,32 @@ class pQueryTemplate extends pQuery implements pQueryExtension {
* @see pQuery::$variable_alias
* @var string|array
*/
static $variable_alias = 'template';
static $variable_alias = 'content';
/**
* The path the template was found in.
*
* @var string
*/
var $path;
/**
* Nested variable values.
*
* @var Block
*/
var $data;
/**
* Constructor
*
* Create a new nested data block object for variable values.
*
* @see data
*/
function __construct() {
$this->data = new Block();
}
/**
* Open the given template filename in the current variable.
......@@ -35,17 +62,152 @@ class pQueryTemplate extends pQuery implements pQueryExtension {
$path = $root.$this->variable;
if( is_file($path) ) {
$found = true;
break;
$this->path = $path;
$this->content = file_get_contents($path);
return;
}
}
if( !$found ) {
return self::error("Could not find template file \"%s\", looked in folders:\n%s",
self::error("Could not find template file \"%s\", looked in folders:\n%s",
$this->variable, implode("\n", self::$include_path));
}
$this->content = file_get_contents($path);
/**
* Replace blocks and variables in the template's content.
*
* @returns string The template's content, with replaced variables.
*/
function parse() {
$lines = array('-');
$index = 0;
// Loop through the content character by character
for( $i = 0, $l = strlen($this->content); $i < $l; $i++ ) {
$c = $this->content[$i];
if( $c == '{' ) {
// Possible variable container found, add marker
$lines[] = '+';
$index++;
} elseif( $c == '}' ) {
// Variable container closed, add closure marker
$lines[] = '-';
$index++;
} else {
// Add character to the last line
$lines[$index] .= $c;
}
}
$line_count = 1;
$block = $root = new Block;
$block->children = array();
$in_block = false;
// Loop through the parsed lines, building the block tree
foreach( $lines as $line ) {
$marker = $line[0];
if( $marker == '+' ) {
if( strpos($line, 'block:') === 1 ) {
// Block start
$block = $block->add('block', array('name' => substr($line, 7)));
} elseif( strpos($line, 'end') === 1 ) {
// Block end
if( $block->parent === null ) {
return self::error('Unexpected "{end}" at line %d in template "%s".',
$line_count, $this->path);
}
$block = $block->parent;
} else {
// Variable enclosure
$block->add('variable', array('content' => substr($line, 1)));
}
} else {
$block->add('', array('content' => substr($line, 1)));
}
// Count lines for error messages
$line_count += substr_count($line, "\n");
}
// Use recursion to parse all blocks from the root level
return self::parse_block($root, $this->data);
}
/**
* Replace a variable name if it exists within a given data scope.
*
* Apply any of the following helper functions:
* - Translation: {_:name[:count_var_name]}
* - Default: <code>{var_name[:func1:func2:...]}</code>
*
* @param string $variable The variable to replace.
* @param Block $data The data block to search in for the value.
* @returns string The variable's value if it exists, the original string otherwise.
*/
static function parse_variable($variable, $data) {
$parts = explode(':', $variable);
$name = $parts[0];
$parts = array_slice($parts, 1);
switch( $name ) {
case '_':
// TODO: translations
return '--translation--';
break;
default:
$value = $data->get($name);
// Don't continue if the variable name is not foudn in the data block
if( $value === null )
break;
// Apply existing PHP functions to the variable's value
foreach( $parts as $func ) {
if( !is_callable($func) )
return self::error('Function "%s" does not exist.', $func);
$value = $func($value);
}
return $value;
}
return '{'.$variable.'}';
}
/**
* Parse a single block, recursively parsing its sub-blocks with a given data scope.
*
* @param Block $variable The block to parse.
* @param Block $data the data block to search in for the variable values.
* @returns string The parsed block.
* @uses parse_variable
*/
static function parse_block($block, $data) {
$content = '';
foreach( $block->children as $child ) {
switch( $child->name ) {
case 'block':
$block_name = $child->get('name');
foreach( $data->find($block_name) as $block_data ) {
$content .= self::parse_block($child, $block_data);
}
break;
case 'variable':
$content .= self::parse_variable($child->content, $data);
break;
default:
$content .= $child->content;
}
}
return $content;
}
/**
......
......@@ -22,6 +22,10 @@ debug($results);*/
__tpl::set_root('templates', false);
$tpl = _tpl('test.tpl');
debug($tpl->content);
$test1 = $tpl->data->add('test1', array('var' => 'some-variable'));
$tpl->data->add('test1', array('var' => 'some-other-variable'));
$test1->add('test2');
$tpl->data->add('test3');
debug($tpl->parse());
?>
\ No newline at end of file
testcontent
\ No newline at end of file
lorem
{block:test1}
block:test1
ipsum
{var:strtoupper}
{not_a_variable}
{block:test2}
block:test2
dolor
end:test2
{end}
sit
end:test1
{end}
amet
{block:test3}
block:test3
consectetur
end:test3
{end}
adipiscing
\ No newline at end of file
<?php
/**
* Tree data structure, used for rendering purposes.
*
* @package pQuery
* @author Taddeus Kroes
* @version 1.0
*/
class Block {
/**
*
*
* @var int
*/
static $count = 0;
/**
* The unique id of this block.
*
* @var int
*/
var $id;
/**
* The block's name.
*
* @var string
*/
var $name;
/**
* An optional parent block.
*
* If NULL, this block is the root of the data tree.
*
* @var Block
*/
var $parent;
/**
* Child blocks.
*
* @var array
*/
var $children = array();
/**
* Variables in this block.
*
* All variables in a block are also available in its descendants through {@link get()}.
*
* @var array
*/
var $vars = array();
/**
* Constructor.
*
* The id of the block is determined by the block counter.
*
* @param string $name The block's name.
* @param Block &$parent A parent block (optional).
* @see id, name, parent
* @uses $count
*/
function __construct($name=null, &$parent=null) {
$this->id = ++self::$count;
$this->name = $name;
$this->parent = $parent;
}
/**
* Add a child block.
*
* @param string $name The name of the block to add.
* @param array $data Data to add to the created block (optional).
* @returns Block The created block.
*/
function add($name, $data=array()) {
array_push($this->children, $block = new self($name, $this));
return $block->set($data);
}
/**
* Set the value of one or more variables in the block.
*
* @param string|array $vars Either a single variable name, or a set of name/value pairs.
* @param mixed $value The value of the single variable to set.
* @returns Block This block.
*/
function set($name, $value=null) {
if( is_array($name) ) {
foreach( $name as $var => $val )
$this->vars[$var] = $val;
} else
$this->vars[$name] = $value;
return $this;
}
/**
* Get the value of a variable.
*
* This method is an equivalent of {@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);
}
/**
* 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 block
if( isset($this->vars[$name]) )
return $this->vars[$name];
// Variable in one of parents
if( $this->parent !== null )
return $this->parent->get($name);
// If the tree's root block does not have the variable, it does not exist
return null;
}
/**
* Find all child blocks with a specified name.
*
* @param string $name The name of the blocks to find.
* @returns array The positively matched blocks.
*/
function find($name) {
return array_filter($this->children,
create_function('$c', 'return $c->name === "'.$name.'";'));
}
/**
* Remove a child block.
*
* @param Block &$child The block to remove.
* @returns Block This block.
*/
function remove_child(&$child) {
foreach( $this->children as $i => $block ) {
if( $block->id == $child->id ) {
array_splice($this->children, $i, 1);
$block->parent = null;
}
}
return $this;
}
/**
* Remove this block from its parent.
*
* @returns Block The removed block.
*/
function remove() {
!is_null($this->parent) && $this->parent->remove_child($this);
return $this;
}
}
?>
\ 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