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 - sezaiy@linux.org.tr
*
* 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_]+))?}(.*){\\1\s+END\\2})|" .
"(?:{(" . $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("", "", "A;", "F;",
":", "<", ";");
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("//", "//", "/A;/", "/F;/",
"/:/", "/</", "/;/");
$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
?>