#native_company# #native_desc#
#native_cta#

HTML Templates

By Sezai YILMAZ
on November 10, 2001

Version: 1.1

Type: Class

Category: HTML

License: GNU General Public License

Description: HTML Template language with advanced features. It supports simple variable replacements, and advanced expressions like looping, inclusion, conditioning.

<?php

/*
 *	class TemplateLanguage - Template Language for PHP4. Enables use of
 *			HTML templates with PHP4. This TemplateLanguage class has features
 *			of simple variable replacement, inclusion of external template 
 *			files, conditioning, and looping. Special expressions of
 *			this language is surrounded with curly braces - "{" and "}".
 *			Sample variable definition is defined like "{VARIABLE}" in normal
 *			HTML file. Other expression definitions are as follows:
 *
 *	License:	GPL
 *
 *	variable	condition			 iteration			 include
 *
 *	{VARIABLE}	{lbl IF VARIABLE}	 {lbl LOOP LOOPVAR}  {lbl INCLUDE "file"}
 *				positive block		 iterated block
 *				{lbl ELSE}			 {lbl ENDLOOP}
 *				negative block
 *				{lbl ENDIF}
 *
 *	Home Page: http://www.cs.hacettepe.edu.tr/~sezaiy/TemplateLanguage/
 *
 *	Sezai YILMAZ - [email protected]
 *
 *	Public Methods:
 *  ==========================================================================
 *	TemplateLanguage($path) - Constructor of TemplateLanguage.class
 *		$path is path to template file which will be parsed.
 *	bindVar($varName, $varValue) - Binds a variable name to its replacement
 *  	$varName is variable name which is defined in the template file.
 *		$varValue is variable value which is replacement of variable $varName. If
 *			$varValue is an array then it is assumed as a loop variable (array).
 *	bindVar($varArray) - Binds many variable names to their replacements at once. 
 *		$varArray is an associative array, keys are variable names and values
 *			are replacement of them. If value of a key is an array then
 *			it is assumed as a loop variable (array).
 *	isBound($varName) - Returns true if variable name is bound to a replacement,
 *		otherwise it returns false
 *		$varName is the name of variable whose boundness will be checked.
 *	getBoundVarType($varName) - Returns type of replacement of a bounded
 *		variable name, otherwise it returns false
 *		$varName is the name of bound variable whose replacement type is looked at.
 *	parseAndShow() - Parses the template and displays it 
 * 
 *	Simple Usage:
 *  ======================================================================
 *	$vars = array("VAR1"=>"replacemenet", "VAR2"=>"replacement");
 *  $loopVar[0] = array("LVAR1"=>"replacemenet1", "LVAR2"=>"replacement2");
 *  $loopVar[1] = array("LVAR1"=>"replacemenet3", "LVAR2"=>"replacement4");
 *  ...
 *  $page = new TemplateLanguage("/path/to/template/file");
 *	$page->bindVar($vars);	// array of variables example
 *	$page->bindVar("LOOPVAR1", $loopVar);	// loop array example
 *	$page->parseAndShow();
 *
 */

class TemplateLanguage {
	var $template = "";
	var $variableName = "[a-zA-Z]?[0-9a-zA-Z_]*";
	var $varRegExp = "";
	var $repRegExp = "";
	var $elseRegExp = "";
	var $blockList = array();
	var $parsedOut = "";
	var $varCache = array();

	var $instantinated = false;
	var $templateRead = false;
	var $rootDir = "";

	function TemplateLanguage($filePath = "") {

		$pathParts = explode("/", $filePath);

		if (count($pathParts) == 1) {
			$rootDir = "./";
			$filename = $pathParts[0];
		} else if (count($pathParts) > 1) {
			$filename = $pathParts[count($pathParts) - 1];
			$rootDir = substr($filePath, 0,
				strlen($filePath) - strlen($filename));
		}

		$this->rootDir = $rootDir;

		$this->varRegExp = "@{(" . $this->variableName .
					")}@ism";
		$this->blockRegExp = "@(?:{(" . $this->variableName . 
			")s+(BLOCK|LOOP|IF)(?:s+([0-9a-zA-Z_]+))?}(.*){1s+END2})|" .
			"(?:{(" . $this->variableName . 
			")s+(INCLUDE)s+["']([0-9a-zA-Z_./]+)["']})@ism";
		$this->repRegExp = "@{(" . $this->variableName . ")s+REPLACE}@ism";
		$this->elseRegExp = "@{(" . $this->variableName . ")s+ELSE}@ism";
		$this->instantinated = true;

		$this->setTemplate($filename);
		$this->buildStructure();
	}


	function setTemplate($filename) {

		if (!$this->instantinated) {
			return false;
		}

		$this->template = $this->getFile($filename);

		if (!$this->template) {
			return false;
		}

		$this->templateRead = true;
		return true;
	}

	function &getFile($fname) {

		if ($fname[0] != "/") {	// relative path is given, add rootDir
			$filename = $this->rootDir . $fname;
		} else { // absolute path is given, try to load it
			$filename = $fname;
		}

		if (!($fh = @fopen($filename, "r"))) {
			$content = "nTemplateLanguage: ERROR! Can not read template :'" . 
				$fname . "'!n";
			return $content;
		}

		$content = fread($fh, filesize($filename));
		fclose($fh);
		// RegExp special characters "(", ")", "*", "?", "[", "]", "|"
		// appeared in context make headeache for perl compatible
		// regular expression library. Get rid of them
		$patterns = array("/(/",  "/)/",  "/*/",  "/?/", 
					"/[/",  "/]/",  "/|/");
		$replacements = array("&#28;", "&#29;", "&#2A;", "&#3F;",
					"&#58;", "&#60;", "&#59;");
		return preg_replace($patterns, $replacements, $content);

	}

	function buildStructure($bname = "global", $btype = "BLOCK", 
								$parameter = "", $template = "") {

		if(!$this->templateRead) return false;
		
		if(!$template) $template = &$this->template;

		// catch blocks
		$newBlocks = preg_match_all($this->blockRegExp, 
			$template,
			$regs,
			PREG_SET_ORDER);

		if($newBlocks) {	// has new blocks to be iterated

			foreach($regs as $k => $subArray) {
				// read external inclusions
				if(count($subArray) > 5) {
					// this is an INCLUDE expression
					// there is no difference between INCLUDE & BLOCK
					$type = "BLOCK";	// The same behavior with BLOCK
					$fname = $subArray[7];	// get filename
					$label = $subArray[5];	// label
					$content = $this->getFile($fname);

					$regs[$k] = array($subArray[0], // original
							$label, 			// label
							$type, 				// type
							$fname, 			// parameter (file name)
							$content);			// content (file content)
				}	// end of INCLUDE blocks

			}		// end of foreach

			$blockCount = count($regs);

			// prepare a replacement for each block associated with
			// its label
			foreach($regs as $key => $value) {
				$patterns[] = "@" . $value[0] . "@sm";
				$replacement[] = "{" . $value[1] . " REPLACE}";
			}

			// do replacements
			$template = preg_replace($patterns, $replacement, $template);

			// add current block that has replacements to the block list
			$this->blockList[$bname] = array("TYPE" => $btype,
				"PARAMETER" => $parameter, "CONTENT" => &$template);

			for($i = 0; $i < $blockCount; $i++) {

				$currentBlock = &$regs[$i][4];		// Content of block
				$currentBlockName = $regs[$i][1];	// Name of block
				$currentBlockType = $regs[$i][2];	// Type of block
				$currentBlockParameter = $regs[$i][3];	// Parameter of block

				// do the same for the other blocks
				$this->buildStructure($currentBlockName, $currentBlockType, 
						$currentBlockParameter, $currentBlock);
			}
		} else {	// Has not new blocks to be iterated

			$this->blockList[$bname] = array("TYPE" => $btype,
				"PARAMETER" => $parameter,
				"CONTENT" => $template);
		}
	}

	function bindVar($varName, $varValue = false) {
		if (is_array($varName) && !$varValue) {

			$keys = array_keys($varName);
			$elementCount = count($keys);
			for ($i = 0; $i < $elementCount; $i++) {
				$key = $keys[$i];
				$this->bindVar($key, $varName[$key]);
			}
		} else if (is_string($varName) && !is_object($varValue)
			&& !is_array($varValue) && !is_resource($varValue)) {
			$this->varCache[strtoupper($varName)] = $varValue;
		} else if (is_string($varName) && is_array($varValue)) {
			$this->bindLoopVar($varName, $varValue);
		} else return false;	// fails
		return true;	// sucessfull
	}

	function isBound($varName) {
		$varName = strtoupper($varName);
		return isset($this->varCache[$varName]) | isset($this->loopVarCache[$varName]);
	}

	function getVar($varName) {
		$varName = strtoupper($varName);
		if ($this->isBound($varName))
			if (isset($this->varCache[$varName]))
				return $this->varCache[$varName];
			else
				return $this->loopVarCache[$varName];
		else
			return false;
		return $this->isBound($varName) ? $this->varCache[$varName] : false;
	}

	function getBoundVarType($varName) {
		$varName = strtoupper($varName);
		if ($this->isBound($varName))
			if (gettype($this->varCache[$varName]) != "NULL")
				return gettype($this->varCache[$varName]);
			else
				return gettype($this->loopVarCache[$varName]);
			
		else
			return false;
	}
	
	function bindLoopVar($varName, &$varValue) {

		if (!is_array($varValue)) return false;

		$varName = strtoupper($varName);
		// return the array into an array whose indices are like 0..N by the
		// help of array_values function
		$this->loopVarCache[$varName] = array_values($varValue);
		$keys = array_keys($varValue);
		$this->bindVar($varValue[$keys[0]]);	// first inititalization
		$this->bindVar($varName . "_COUNT", count($varValue));
	}

	function rebindLoopVar($varName) {
		$varName = strtoupper($varName);
		if (!isset($this->loopVarCache[$varName])) return false;

		reset($this->loopVarCache[$varName]);
		return true;
	}

	function nextLoopVar($varName) {
		$varName = strtoupper($varName);
		if (!isset($this->loopVarCache[$varName])) return false;
		
		$result = each($this->loopVarCache[$varName]);

		if (!$result) return $result;

		$this->bindVar($result["value"]);
		$this->bindVar($varName . "_CURRENT", $result["key"] + 1);
		$this->bindVar($varName . "_COUNT", count($this->loopVarCache[$varName]));
		return true;
	}

	function &doComparison($string, &$content) {		// does if comparison

		$operand = trim($string);
		$whichPart = 0;	// default is positive block

		if (!$this->isBound($operand)) {
		// Template variable is not bound - negate
			$whichPart = 1;
		} else if (($this->getBoundVarType($operand) == "integer") 
		// Template variable is bound, is INTEGER, and is 0 - negate
			&& ($this->getVar($operand) == 0)) {
			$whichPart = 1;
		} else if (($this->getBoundVarType($operand) == "string") 
			&& !strlen($this->getVar($operand))) {
		// Template variable is bound, is STRING, and is EMPTY - negate
			$whichPart = 1;
		} else if (($this->getBoundVarType($operand) == "boolean")
			&& ($this->getVar($operand) == false)) {
		// Template variable is bound, is BOOLEAN, and is FALSE - negate
			$whichPart = 1;
		} else if (($this->getBoundVarType($operand) == "array")
			&& (!count($this->getVar($operand)))) {
		// Template variable is bound, is ARRAY, and is empty - negate
			$whichPart = 1;
		}


		$parts = preg_split($this->elseRegExp, $content);
		$content = $parts[$whichPart];
		return $content;
	}

	function parse($bname = "global") {

		if (!isset($this->blockList[$bname])) return false;

		$type = $this->blockList[$bname]["TYPE"];
		$parameter = $this->blockList[$bname]["PARAMETER"];
		$content = $this->blockList[$bname]["CONTENT"];

		switch($type) {
			case "IF":
					$content = $this->doComparison($parameter, $content);
					// continue with BLOCK case...

			case "BLOCK":
					// move it to suitable place
					// move it to suitable place
					$regs = array();
					$values = array();
					foreach ($this->varCache as $name => $value) {
						$regs[] = "@{" . $name . "}@ism";
						$values[] = $value;
					}

					// variable replacement
					$content = preg_replace($regs, $values, $content);
					$result = preg_match_all($this->repRegExp, $content,
						$middles, PREG_SET_ORDER);
					$parts = preg_split($this->repRegExp, $content);

					$this->parsedOut .= $parts[0];

					$max = count($middles);

					for($i = 0; $i < $max; $i++) {
						$subBlockName = $middles[$i][1];
						$this->parse($subBlockName);
						$this->parsedOut .= $parts[$i + 1];
					}
					break;

			case "LOOP":
					$this->rebindLoopVar($parameter);
					while($this->nextLoopVar($parameter)) {

						$regs = array();
						$values = array();
						// move it to suitable place
						// move it to suitable place
						foreach ($this->varCache as $name => $value) {
							$regs[] = "@{" . $name . "}@ism";
							$values[] = $value;
						}

						// variable replacement
						$newContent = preg_replace($regs, $values, $content);

						$result = preg_match_all($this->repRegExp,
								$newContent, $middles, 
								PREG_SET_ORDER);
						$parts = preg_split($this->repRegExp, $newContent);

						$max = count($middles);

						$this->parsedOut .= $parts[0];
						
						for($i = 0; $i < $max; $i++) {
							$subBlockName = $middles[$i][1];
							$this->parse($subBlockName);
							$this->parsedOut .= $parts[$i + 1];
						}
					}

					break;
		}
	}

	function show($cacheable = false) {
		if (!$cacheable && !headers_sent()) {
			header("Cache-Control: no-cache, must-revalidate");	// HTTP 1.1
			header("Pragma: no-cache");	// HTTP 1.0
		}

		$patterns = array("/&#28;/", "/&#29;/", "/&#2A;/", "/&#3F;/",
			"/&#58;/", "/&#60;/", "/&#59;/");
		$replacements = array("(",  ")",  "*",  "?",  "[",  "]",  "|");
		// convert RegExp special chars to their default values
		echo preg_replace($patterns, $replacements, $this->parsedOut);
	}

	function parseAndShow($cacheable = false) {
		$this->parse();
		$this->show($cacheable);
	}
}	// end of TemplateLanguage.class

?>