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("", "", "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 ?>