Explorar o código

Added block parsing fucntionality to template parser.

Taddes Kroes %!s(int64=14) %!d(string=hai) anos
pai
achega
729e3ced1f
Modificáronse 6 ficheiros con 401 adicións e 11 borrados
  1. 7 0
      pquery.config.php
  2. 17 1
      pquery.php
  3. 170 8
      pquery.template.php
  4. 5 1
      test/index.php
  5. 28 1
      test/templates/test.tpl
  6. 174 0
      utils/block.php

+ 7 - 0
pquery.config.php

@@ -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.
 	 * 

+ 17 - 1
pquery.php

@@ -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);

+ 170 - 8
pquery.template.php

@@ -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;
+			}
+		}
+		
+		self::error("Could not find template file \"%s\", looked in folders:\n%s",
+			$this->variable, implode("\n", self::$include_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;
 			}
 		}
 		
-		if( !$found ) {
-			return self::error("Could not find template file \"%s\", looked in folders:\n%s",
-				$this->variable, implode("\n", self::$include_path));
+		$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;
+			}
 		}
 		
-		$this->content = file_get_contents($path);
+		return $content;
 	}
 	
 	/**

+ 5 - 1
test/index.php

@@ -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());
 
 ?>

+ 28 - 1
test/templates/test.tpl

@@ -1 +1,28 @@
-testcontent
+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

+ 174 - 0
utils/block.php

@@ -0,0 +1,174 @@
+<?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;
+	}
+}
+
+?>