diff --git a/lib/MiniTemplator.class.php b/lib/MiniTemplator.class.php index e70f0a470..6be345834 100644 --- a/lib/MiniTemplator.class.php +++ b/lib/MiniTemplator.class.php @@ -1,922 +1,922 @@ - -* Template syntax: -* -* Variables: -* ${VariableName} -* -* Blocks: -* <!-- $BeginBlock BlockName --> -* ... block content ... -* <!-- $EndBlock BlockName --> -* -* Include a subtemplate: -* <!-- $Include RelativeFileName --> -* -* -*
-* General remarks:
-*  - Variable names and block names are case-insensitive.
-*  - The same variable may be used multiple times within a template.
-*  - Blocks can be nested.
-*  - Multiple blocks with the same name may occur within a template.
-* 
-* -*
-* Public methods:
-*   readTemplateFromFile   - Reads the template from a file.
-*   setTemplateString      - Assigns a new template string.
-*   setVariable            - Sets a template variable.
-*   setVariableEsc         - Sets a template variable to an escaped string value.
-*   variableExists         - Checks whether a template variable exists.
-*   addBlock               - Adds an instance of a template block.
-*   blockExists            - Checks whether a block exists.
-*   reset                  - Clears all variables and blocks.
-*   generateOutput         - Generates the HTML page and writes it to the PHP output stream.
-*   generateOutputToFile   - Generates the HTML page and writes it to a file.
-*   generateOutputToString - Generates the HTML page and writes it to a string.
-* 
-* -* Home page: {@link http://www.source-code.biz/MiniTemplator}
-* License: This module is released under the GNU/LGPL license ({@link http://www.gnu.org/licenses/lgpl.html}).
-* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland. All rights reserved.
-* This product is provided "as is" without warranty of any kind.
-* -* Version history:
-* 2001-10-24 Christian d'Heureuse (chdh): VBasic version created.
-* 2002-01-26 Markus Angst: ported to PHP4.
-* 2003-04-07 chdh: changes to adjust to Java version.
-* 2003-07-08 chdh: Method variableExists added. -* Method setVariable changed to trigger an error when the variable does not exist.
-* 2004-04-07 chdh: Parameter isOptional added to method setVariable. -* Licensing changed from GPL to LGPL.
-* 2004-04-18 chdh: Method blockExists added.
-* 2004-10-28 chdh:
-* Method setVariableEsc added.
-* Multiple blocks with the same name may now occur within a template.
-* No error ("unknown command") is generated any more, if a HTML comment starts with "${".
-* 2004-11-06 chdh:
-* "$Include" command implemented.
-* 2004-11-20 chdh:
-* "$Include" command changed so that the command text is not copied to the output file.
-*/ - -class MiniTemplator { - -//--- public member variables --------------------------------------------------------------------------------------- - -/** -* Base path for relative file names of subtemplates (for the $Include command). -* This path is prepended to the subtemplate file names. It must be set before -* readTemplateFromFile or setTemplateString. -* @access public -*/ -var $subtemplateBasePath; - -//--- private member variables -------------------------------------------------------------------------------------- - -/**#@+ -* @access private -*/ - -var $maxNestingLevel = 50; // maximum number of block nestings -var $maxInclTemplateSize = 1000000; // maximum length of template string when including subtemplates -var $template; // Template file data -var $varTab; // variables table, array index is variable no - // Fields: - // varName // variable name - // varValue // variable value -var $varTabCnt; // no of entries used in VarTab -var $varNameToNoMap; // maps variable names to variable numbers -var $varRefTab; // variable references table - // Contains an entry for each variable reference in the template. Ordered by TemplatePos. - // Fields: - // varNo // variable no - // tPosBegin // template position of begin of variable reference - // tPosEnd // template position of end of variable reference - // blockNo // block no of the (innermost) block that contains this variable reference - // blockVarNo // block variable no. Index into BlockInstTab.BlockVarTab -var $varRefTabCnt; // no of entries used in VarRefTab -var $blockTab; // Blocks table, array index is block no - // Contains an entry for each block in the template. Ordered by TPosBegin. - // Fields: - // blockName // block name - // nextWithSameName; // block no of next block with same name or -1 (blocks are backward linked in relation to template position) - // tPosBegin // template position of begin of block - // tPosContentsBegin // template pos of begin of block contents - // tPosContentsEnd // template pos of end of block contents - // tPosEnd // template position of end of block - // nestingLevel // block nesting level - // parentBlockNo // block no of parent block - // definitionIsOpen // true while $BeginBlock processed but no $EndBlock - // instances // number of instances of this block - // firstBlockInstNo // block instance no of first instance of this block or -1 - // lastBlockInstNo // block instance no of last instance of this block or -1 - // currBlockInstNo // current block instance no, used during generation of output file - // blockVarCnt // no of variables in block - // blockVarNoToVarNoMap // maps block variable numbers to variable numbers - // firstVarRefNo // variable reference no of first variable of this block or -1 -var $blockTabCnt; // no of entries used in BlockTab -var $blockNameToNoMap; // maps block names to block numbers -var $openBlocksTab; - // During parsing, this table contains the block numbers of the open parent blocks (nested outer blocks). - // Indexed by the block nesting level. -var $blockInstTab; // block instances table - // This table contains an entry for each block instance that has been added. - // Indexed by BlockInstNo. - // Fields: - // blockNo // block number - // instanceLevel // instance level of this block - // InstanceLevel is an instance counter per block. - // (In contrast to blockInstNo, which is an instance counter over the instances of all blocks) - // parentInstLevel // instance level of parent block - // nextBlockInstNo // pointer to next instance of this block or -1 - // Forward chain for instances of same block. - // blockVarTab // block instance variables -var $blockInstTabCnt; // no of entries used in BlockInstTab - -var $currentNestingLevel; // Current block nesting level during parsing. -var $templateValid; // true if a valid template is prepared -var $outputMode; // 0 = to PHP output stream, 1 = to file, 2 = to string -var $outputFileHandle; // file handle during writing of output file -var $outputError; // true when an output error occurred -var $outputString; // string buffer for the generated HTML page - -/**#@-*/ - -//--- constructor --------------------------------------------------------------------------------------------------- - -/** -* Constructs a MiniTemplator object. -* @access public -*/ -function __construct() { - $this->templateValid = false; } - -//--- template string handling -------------------------------------------------------------------------------------- - -/** -* Reads the template from a file. -* @param string $fileName name of the file that contains the template. -* @return boolean true on success, false on error. -* @access public -*/ -function readTemplateFromFile ($fileName) { - if (!$this->readFileIntoString($fileName,$s)) { - $this->triggerError ("Error while reading template file " . $fileName . "."); - return false; } - if (!$this->setTemplateString($s)) return false; - return true; } - -/** -* Assigns a new template string. -* @param string $templateString contents of the template file. -* @return boolean true on success, false on error. -* @access public -*/ -function setTemplateString ($templateString) { - $this->templateValid = false; - $this->template = $templateString; - if (!$this->parseTemplate()) return false; - $this->reset(); - $this->templateValid = true; - return true; } - -/** -* Loads the template string for a subtemplate (used for the $Include command). -* @return boolean true on success, false on error. -* @access private -*/ -function loadSubtemplate ($subtemplateName, &$s) { - $subtemplateFileName = $this->combineFileSystemPath($this->subtemplateBasePath,$subtemplateName); - if (!$this->readFileIntoString($subtemplateFileName,$s)) { - $this->triggerError ("Error while reading subtemplate file " . $subtemplateFileName . "."); - return false; } - return true; } - -//--- template parsing ---------------------------------------------------------------------------------------------- - -/** -* Parses the template. -* @return boolean true on success, false on error. -* @access private -*/ -function parseTemplate() { - $this->initParsing(); - $this->beginMainBlock(); - if (!$this->parseTemplateCommands()) return false; - $this->endMainBlock(); - if (!$this->checkBlockDefinitionsComplete()) return false; - if (!$this->parseTemplateVariables()) return false; - $this->associateVariablesWithBlocks(); - return true; } - -/** -* @access private -*/ -function initParsing() { - $this->varTab = array(); - $this->varTabCnt = 0; - $this->varNameToNoMap = array(); - $this->varRefTab = array(); - $this->varRefTabCnt = 0; - $this->blockTab = array(); - $this->blockTabCnt = 0; - $this->blockNameToNoMap = array(); - $this->openBlocksTab = array(); } - -/** -* Registers the main block. -* The main block is an implicitly defined block that covers the whole template. -* @access private -*/ -function beginMainBlock() { - $blockNo = 0; - $this->registerBlock('@@InternalMainBlock@@', $blockNo); - $bte =& $this->blockTab[$blockNo]; - $bte['tPosBegin'] = 0; - $bte['tPosContentsBegin'] = 0; - $bte['nestingLevel'] = 0; - $bte['parentBlockNo'] = -1; - $bte['definitionIsOpen'] = true; - $this->openBlocksTab[0] = $blockNo; - $this->currentNestingLevel = 1; } - -/** -* Completes the main block registration. -* @access private -*/ -function endMainBlock() { - $bte =& $this->blockTab[0]; - $bte['tPosContentsEnd'] = strlen($this->template); - $bte['tPosEnd'] = strlen($this->template); - $bte['definitionIsOpen'] = false; - $this->currentNestingLevel -= 1; } - -/** -* Parses commands within the template in the format "". -* @return boolean true on success, false on error. -* @access private -*/ -function parseTemplateCommands() { - $p = 0; - while (true) { - $p0 = strpos($this->template,'',$p0); - if ($p === false) { - $this->triggerError ("Invalid HTML comment in template at offset $p0."); - return false; } - $p += 3; - $cmdL = substr($this->template,$p0+4,$p-$p0-7); - if (!$this->processTemplateCommand($cmdL,$p0,$p,$resumeFromStart)) - return false; - if ($resumeFromStart) $p = $p0; } - return true; } - -/** -* @return boolean true on success, false on error. -* @access private -*/ -function processTemplateCommand ($cmdL, $cmdTPosBegin, $cmdTPosEnd, &$resumeFromStart) { - $resumeFromStart = false; - $p = 0; - $cmd = ''; - if (!$this->parseWord($cmdL,$p,$cmd)) return true; - $parms = substr($cmdL,$p); - switch (strtoupper($cmd)) { - case '$BEGINBLOCK': - if (!$this->processBeginBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) - return false; - break; - case '$ENDBLOCK': - if (!$this->processEndBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) - return false; - break; - case '$INCLUDE': - if (!$this->processincludeCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) - return false; - $resumeFromStart = true; - break; - default: - if ($cmd{0} == '$' && !(strlen($cmd) >= 2 && $cmd{1} == '{')) { - $this->triggerError ("Unknown command \"$cmd\" in template at offset $cmdTPosBegin."); - return false; }} - return true; } - -/** -* Processes the $BeginBlock command. -* @return boolean true on success, false on error. -* @access private -*/ -function processBeginBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { - $p = 0; - if (!$this->parseWord($parms,$p,$blockName)) { - $this->triggerError ("Missing block name in \$BeginBlock command in template at offset $cmdTPosBegin."); - return false; } - if (trim(substr($parms,$p)) != '') { - $this->triggerError ("Extra parameter in \$BeginBlock command in template at offset $cmdTPosBegin."); - return false; } - $this->registerBlock ($blockName, $blockNo); - $btr =& $this->blockTab[$blockNo]; - $btr['tPosBegin'] = $cmdTPosBegin; - $btr['tPosContentsBegin'] = $cmdTPosEnd; - $btr['nestingLevel'] = $this->currentNestingLevel; - $btr['parentBlockNo'] = $this->openBlocksTab[$this->currentNestingLevel-1]; - $this->openBlocksTab[$this->currentNestingLevel] = $blockNo; - $this->currentNestingLevel += 1; - if ($this->currentNestingLevel > $this->maxNestingLevel) { - $this->triggerError ("Block nesting overflow in template at offset $cmdTPosBegin."); - return false; } - return true; } - -/** -* Processes the $EndBlock command. -* @return boolean true on success, false on error. -* @access private -*/ -function processEndBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { - $p = 0; - if (!$this->parseWord($parms,$p,$blockName)) { - $this->triggerError ("Missing block name in \$EndBlock command in template at offset $cmdTPosBegin."); - return false; } - if (trim(substr($parms,$p)) != '') { - $this->triggerError ("Extra parameter in \$EndBlock command in template at offset $cmdTPosBegin."); - return false; } - if (!$this->lookupBlockName($blockName,$blockNo)) { - $this->triggerError ("Undefined block name \"$blockName\" in \$EndBlock command in template at offset $cmdTPosBegin."); - return false; } - $this->currentNestingLevel -= 1; - $btr =& $this->blockTab[$blockNo]; - if (!$btr['definitionIsOpen']) { - $this->triggerError ("Multiple \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin."); - return false; } - if ($btr['nestingLevel'] != $this->currentNestingLevel) { - $this->triggerError ("Block nesting level mismatch at \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin."); - return false; } - $btr['tPosContentsEnd'] = $cmdTPosBegin; - $btr['tPosEnd'] = $cmdTPosEnd; - $btr['definitionIsOpen'] = false; - return true; } - -/** -* @access private -*/ -function registerBlock($blockName, &$blockNo) { - $blockNo = $this->blockTabCnt++; - $btr =& $this->blockTab[$blockNo]; - $btr = array(); - $btr['blockName'] = $blockName; - if (!$this->lookupBlockName($blockName,$btr['nextWithSameName'])) - $btr['nextWithSameName'] = -1; - $btr['definitionIsOpen'] = true; - $btr['instances'] = 0; - $btr['firstBlockInstNo'] = -1; - $btr['lastBlockInstNo'] = -1; - $btr['blockVarCnt'] = 0; - $btr['firstVarRefNo'] = -1; - $btr['blockVarNoToVarNoMap'] = array(); - $this->blockNameToNoMap[strtoupper($blockName)] = $blockNo; } - -/** -* Checks that all block definitions are closed. -* @return boolean true on success, false on error. -* @access private -*/ -function checkBlockDefinitionsComplete() { - for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) { - $btr =& $this->blockTab[$blockNo]; - if ($btr['definitionIsOpen']) { - $this->triggerError ("Missing \$EndBlock command in template for block " . $btr['blockName'] . "."); - return false; }} - if ($this->currentNestingLevel != 0) { - $this->triggerError ("Block nesting level error at end of template."); - return false; } - return true; } - -/** -* Processes the $Include command. -* @return boolean true on success, false on error. -* @access private -*/ -function processIncludeCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { - $p = 0; - if (!$this->parseWordOrQuotedString($parms,$p,$subtemplateName)) { - $this->triggerError ("Missing or invalid subtemplate name in \$Include command in template at offset $cmdTPosBegin."); - return false; } - if (trim(substr($parms,$p)) != '') { - $this->triggerError ("Extra parameter in \$include command in template at offset $cmdTPosBegin."); - return false; } - return $this->insertSubtemplate($subtemplateName,$cmdTPosBegin,$cmdTPosEnd); } - -/** -* Processes the $Include command. -* @return boolean true on success, false on error. -* @access private -*/ -function insertSubtemplate ($subtemplateName, $tPos1, $tPos2) { - if (strlen($this->template) > $this->maxInclTemplateSize) { - $this->triggerError ("Subtemplate include aborted because the internal template string is longer than $this->maxInclTemplateSize characters."); - return false; } - if (!$this->loadSubtemplate($subtemplateName,$subtemplate)) return false; - // (Copying the template to insert a subtemplate is a bit slow. In a future implementation of MiniTemplator, - // a table could be used that contains references to the string fragments.) - $this->template = substr($this->template,0,$tPos1) . $subtemplate . substr($this->template,$tPos2); - return true; } - -/** -* Parses variable references within the template in the format "${VarName}". -* @return boolean true on success, false on error. -* @access private -*/ -function parseTemplateVariables() { - $p = 0; - while (true) { - $p = strpos($this->template, '${', $p); - if ($p === false) break; - $p0 = $p; - $p = strpos($this->template, '}', $p); - if ($p === false) { - $this->triggerError ("Invalid variable reference in template at offset $p0."); - return false; } - $p += 1; - $varName = trim(substr($this->template, $p0+2, $p-$p0-3)); - if (strlen($varName) == 0) { - $this->triggerError ("Empty variable name in template at offset $p0."); - return false; } - $this->registerVariableReference ($varName, $p0, $p); } - return true; } - -/** -* @access private -*/ -function registerVariableReference ($varName, $tPosBegin, $tPosEnd) { - if (!$this->lookupVariableName($varName,$varNo)) - $this->registerVariable($varName,$varNo); - $varRefNo = $this->varRefTabCnt++; - $vrtr =& $this->varRefTab[$varRefNo]; - $vrtr = array(); - $vrtr['tPosBegin'] = $tPosBegin; - $vrtr['tPosEnd'] = $tPosEnd; - $vrtr['varNo'] = $varNo; } - -/** -* @access private -*/ -function registerVariable ($varName, &$varNo) { - $varNo = $this->varTabCnt++; - $vtr =& $this->varTab[$varNo]; - $vtr = array(); - $vtr['varName'] = $varName; - $vtr['varValue'] = ''; - $this->varNameToNoMap[strtoupper($varName)] = $varNo; } - -/** -* Associates variable references with blocks. -* @access private -*/ -function associateVariablesWithBlocks() { - $varRefNo = 0; - $activeBlockNo = 0; - $nextBlockNo = 1; - while ($varRefNo < $this->varRefTabCnt) { - $vrtr =& $this->varRefTab[$varRefNo]; - $varRefTPos = $vrtr['tPosBegin']; - $varNo = $vrtr['varNo']; - if ($varRefTPos >= $this->blockTab[$activeBlockNo]['tPosEnd']) { - $activeBlockNo = $this->blockTab[$activeBlockNo]['parentBlockNo']; - continue; } - if ($nextBlockNo < $this->blockTabCnt) { - if ($varRefTPos >= $this->blockTab[$nextBlockNo]['tPosBegin']) { - $activeBlockNo = $nextBlockNo; - $nextBlockNo += 1; - continue; }} - $btr =& $this->blockTab[$activeBlockNo]; - if ($varRefTPos < $btr['tPosBegin']) - $this->programLogicError(1); - $blockVarNo = $btr['blockVarCnt']++; - $btr['blockVarNoToVarNoMap'][$blockVarNo] = $varNo; - if ($btr['firstVarRefNo'] == -1) - $btr['firstVarRefNo'] = $varRefNo; - $vrtr['blockNo'] = $activeBlockNo; - $vrtr['blockVarNo'] = $blockVarNo; - $varRefNo += 1; }} - -//--- build up (template variables and blocks) ---------------------------------------------------------------------- - -/** -* Clears all variables and blocks. -* This method can be used to produce another HTML page with the same -* template. It is faster than creating another MiniTemplator object, -* because the template does not have to be parsed again. -* All variable values are cleared and all added block instances are deleted. -* @access public -*/ -function reset() { - for ($varNo=0; $varNo<$this->varTabCnt; $varNo++) - $this->varTab[$varNo]['varValue'] = ''; - for ($blockNo=0; $blockNo<$this->blockTabCnt; $blockNo++) { - $btr =& $this->blockTab[$blockNo]; - $btr['instances'] = 0; - $btr['firstBlockInstNo'] = -1; - $btr['lastBlockInstNo'] = -1; } - $this->blockInstTab = array(); - $this->blockInstTabCnt = 0; } - -/** -* Sets a template variable. -* For variables that are used in blocks, the variable value -* must be set before {@link addBlock} is called. -* @param string $variableName the name of the variable to be set. -* @param string $variableValue the new value of the variable. -* @param boolean $isOptional Specifies whether an error should be -* generated when the variable does not exist in the template. If -* $isOptional is false and the variable does not exist, an error is -* generated. -* @return boolean true on success, or false on error (e.g. when no -* variable with the specified name exists in the template and -* $isOptional is false). -* @access public -*/ -function setVariable ($variableName, $variableValue, $isOptional=false) { - if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } - if (!$this->lookupVariableName($variableName,$varNo)) { - if ($isOptional) return true; - $this->triggerError ("Variable \"$variableName\" not defined in template."); - return false; } - $this->varTab[$varNo]['varValue'] = $variableValue; - return true; } - -/** -* Sets a template variable to an escaped string. -* This method is identical to (@link setVariable), except that -* the characters <, >, &, ' and " of variableValue are -* replaced by their corresponding HTML/XML character entity codes. -* For variables that are used in blocks, the variable value -* must be set before {@link addBlock} is called. -* @param string $variableName the name of the variable to be set. -* @param string $variableValue the new value of the variable. Special HTML/XML characters are escaped. -* @param boolean $isOptional Specifies whether an error should be -* generated when the variable does not exist in the template. If -* $isOptional is false and the variable does not exist, an error is -* generated. -* @return boolean true on success, or false on error (e.g. when no -* variable with the specified name exists in the template and -* $isOptional is false). -* @access public -*/ -function setVariableEsc ($variableName, $variableValue, $isOptional=false) { - return $this->setVariable($variableName,htmlspecialchars($variableValue,ENT_QUOTES),$isOptional); } - -/** -* Checks whether a variable with the specified name exists within the template. -* @param string $variableName the name of the variable. -* @return boolean true if the variable exists, or false when no -* variable with the specified name exists in the template. -* @access public -*/ -function variableExists ($variableName) { - if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } - return $this->lookupVariableName($variableName,$varNo); } - -/** -* Adds an instance of a template block. -* If the block contains variables, these variables must be set -* before the block is added. -* If the block contains subblocks (nested blocks), the subblocks -* must be added before this block is added. -* If multiple blocks exist with the specified name, an instance -* is added for each block occurence. -* @param string blockName the name of the block to be added. -* @return boolean true on success, false on error (e.g. when no -* block with the specified name exists in the template). -* @access public -*/ -function addBlock($blockName) { - if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } - if (!$this->lookupBlockName($blockName,$blockNo)) { - $this->triggerError ("Block \"$blockName\" not defined in template."); - return false; } - while ($blockNo != -1) { - $this->addBlockByNo($blockNo); - $blockNo = $this->blockTab[$blockNo]['nextWithSameName']; } - return true; } - -/** -* @access private -*/ -function addBlockByNo ($blockNo) { - $btr =& $this->blockTab[$blockNo]; - $this->registerBlockInstance ($blockInstNo); - $bitr =& $this->blockInstTab[$blockInstNo]; - if ($btr['firstBlockInstNo'] == -1) - $btr['firstBlockInstNo'] = $blockInstNo; - if ($btr['lastBlockInstNo'] != -1) - $this->blockInstTab[$btr['lastBlockInstNo']]['nextBlockInstNo'] = $blockInstNo; - // set forward pointer of chain - $btr['lastBlockInstNo'] = $blockInstNo; - $parentBlockNo = $btr['parentBlockNo']; - $blockVarCnt = $btr['blockVarCnt']; - $bitr['blockNo'] = $blockNo; - $bitr['instanceLevel'] = $btr['instances']++; - if ($parentBlockNo == -1) - $bitr['parentInstLevel'] = -1; - else - $bitr['parentInstLevel'] = $this->blockTab[$parentBlockNo]['instances']; - $bitr['nextBlockInstNo'] = -1; - $bitr['blockVarTab'] = array(); - // copy instance variables for this block - for ($blockVarNo=0; $blockVarNo<$blockVarCnt; $blockVarNo++) { - $varNo = $btr['blockVarNoToVarNoMap'][$blockVarNo]; - $bitr['blockVarTab'][$blockVarNo] = $this->varTab[$varNo]['varValue']; }} - -/** -* @access private -*/ -function registerBlockInstance (&$blockInstNo) { - $blockInstNo = $this->blockInstTabCnt++; } - -/** -* Checks whether a block with the specified name exists within the template. -* @param string $blockName the name of the block. -* @return boolean true if the block exists, or false when no -* block with the specified name exists in the template. -* @access public -*/ -function blockExists ($blockName) { - if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } - return $this->lookupBlockName($blockName,$blockNo); } - -//--- output generation --------------------------------------------------------------------------------------------- - -/** -* Generates the HTML page and writes it to the PHP output stream. -* @return boolean true on success, false on error. -* @access public -*/ -function generateOutput () { - $this->outputMode = 0; - if (!$this->generateOutputPage()) return false; - return true; } - -/** -* Generates the HTML page and writes it to a file. -* @param string $fileName name of the output file. -* @return boolean true on success, false on error. -* @access public -*/ -function generateOutputToFile ($fileName) { - $fh = fopen($fileName,"wb"); - if ($fh === false) return false; - $this->outputMode = 1; - $this->outputFileHandle = $fh; - $ok = $this->generateOutputPage(); - fclose ($fh); - return $ok; } - -/** -* Generates the HTML page and writes it to a string. -* @param string $outputString variable that receives -* the contents of the generated HTML page. -* @return boolean true on success, false on error. -* @access public -*/ -function generateOutputToString (&$outputString) { - $outputString = "Error"; - $this->outputMode = 2; - $this->outputString = ""; - if (!$this->generateOutputPage()) return false; - $outputString = $this->outputString; - return true; } - -/** -* @access private -* @return boolean true on success, false on error. -*/ -function generateOutputPage() { - if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } - if ($this->blockTab[0]['instances'] == 0) - $this->addBlockByNo (0); // add main block - for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) { - $btr =& $this->blockTab[$blockNo]; - $btr['currBlockInstNo'] = $btr['firstBlockInstNo']; } - $this->outputError = false; - $this->writeBlockInstances (0, -1); - if ($this->outputError) return false; - return true; } - -/** -* Writes all instances of a block that are contained within a specific -* parent block instance. -* Called recursively. -* @access private -*/ -function writeBlockInstances ($blockNo, $parentInstLevel) { - $btr =& $this->blockTab[$blockNo]; - while (!$this->outputError) { - $blockInstNo = $btr['currBlockInstNo']; - if ($blockInstNo == -1) break; - $bitr =& $this->blockInstTab[$blockInstNo]; - if ($bitr['parentInstLevel'] < $parentInstLevel) - $this->programLogicError (2); - if ($bitr['parentInstLevel'] > $parentInstLevel) break; - $this->writeBlockInstance ($blockInstNo); - $btr['currBlockInstNo'] = $bitr['nextBlockInstNo']; }} - -/** -* @access private -*/ -function writeBlockInstance($blockInstNo) { - $bitr =& $this->blockInstTab[$blockInstNo]; - $blockNo = $bitr['blockNo']; - $btr =& $this->blockTab[$blockNo]; - $tPos = $btr['tPosContentsBegin']; - $subBlockNo = $blockNo + 1; - $varRefNo = $btr['firstVarRefNo']; - while (!$this->outputError) { - $tPos2 = $btr['tPosContentsEnd']; - $kind = 0; // assume end-of-block - if ($varRefNo != -1 && $varRefNo < $this->varRefTabCnt) { // check for variable reference - $vrtr =& $this->varRefTab[$varRefNo]; - if ($vrtr['tPosBegin'] < $tPos) { - $varRefNo += 1; - continue; } - if ($vrtr['tPosBegin'] < $tPos2) { - $tPos2 = $vrtr['tPosBegin']; - $kind = 1; }} - if ($subBlockNo < $this->blockTabCnt) { // check for subblock - $subBtr =& $this->blockTab[$subBlockNo]; - if ($subBtr['tPosBegin'] < $tPos) { - $subBlockNo += 1; - continue; } - if ($subBtr['tPosBegin'] < $tPos2) { - $tPos2 = $subBtr['tPosBegin']; - $kind = 2; }} - if ($tPos2 > $tPos) - $this->writeString (substr($this->template,$tPos,$tPos2-$tPos)); - switch ($kind) { - case 0: // end of block - return; - case 1: // variable - $vrtr =& $this->varRefTab[$varRefNo]; - if ($vrtr['blockNo'] != $blockNo) - $this->programLogicError (4); - $variableValue = $bitr['blockVarTab'][$vrtr['blockVarNo']]; - $this->writeString ($variableValue); - $tPos = $vrtr['tPosEnd']; - $varRefNo += 1; - break; - case 2: // sub block - $subBtr =& $this->blockTab[$subBlockNo]; - if ($subBtr['parentBlockNo'] != $blockNo) - $this->programLogicError (3); - $this->writeBlockInstances ($subBlockNo, $bitr['instanceLevel']); // recursive call - $tPos = $subBtr['tPosEnd']; - $subBlockNo += 1; - break; }}} - -/** -* @access private -*/ -function writeString ($s) { - if ($this->outputError) return; - switch ($this->outputMode) { - case 0: // output to PHP output stream - if (!print($s)) - $this->outputError = true; - break; - case 1: // output to file - $rc = fwrite($this->outputFileHandle, $s); - if ($rc === false) $this->outputError = true; - break; - case 2: // output to string - $this->outputString .= $s; - break; }} - -//--- name lookup routines ------------------------------------------------------------------------------------------ - -/** -* Maps variable name to variable number. -* @return boolean true on success, false if the variable is not found. -* @access private -*/ -function lookupVariableName ($varName, &$varNo) { - $x =& $this->varNameToNoMap[strtoupper($varName)]; - if (!isset($x)) return false; - $varNo = $x; - return true; } - -/** -* Maps block name to block number. -* If there are multiple blocks with the same name, the block number of the last -* registered block with that name is returned. -* @return boolean true on success, false when the block is not found. -* @access private -*/ -function lookupBlockName ($blockName, &$blockNo) { - $x =& $this->blockNameToNoMap[strtoupper($blockName)]; - if (!isset($x)) return false; - $blockNo = $x; - return true; } - -//--- general utility routines ----------------------------------------------------------------------------------------- - -/** -* Reads a file into a string. -* @return boolean true on success, false on error. -* @access private -*/ -function readFileIntoString ($fileName, &$s) { - if (function_exists('version_compare') && version_compare(phpversion(),"4.3.0",">=")) { - $s = file_get_contents($fileName); - if ($s === false) return false; - return true; } - $fh = fopen($fileName,"rb"); - if ($fh === false) return false; - $fileSize = filesize($fileName); - if ($fileSize === false) {fclose ($fh); return false; } - $s = fread($fh,$fileSize); - fclose ($fh); - if (strlen($s) != $fileSize) return false; - return true; } - -/** -* @access private -* @return boolean true on success, false when the end of the string is reached. -*/ -function parseWord ($s, &$p, &$w) { - $sLen = strlen($s); - while ($p < $sLen && ord($s{$p}) <= 32) $p++; - if ($p >= $sLen) return false; - $p0 = $p; - while ($p < $sLen && ord($s{$p}) > 32) $p++; - $w = substr($s, $p0, $p - $p0); - return true; } - -/** -* @access private -* @return boolean true on success, false on error. -*/ -function parseQuotedString ($s, &$p, &$w) { - $sLen = strlen($s); - while ($p < $sLen && ord($s{$p}) <= 32) $p++; - if ($p >= $sLen) return false; - if (substr($s,$p,1) != '"') return false; - $p++; $p0 = $p; - while ($p < $sLen && $s{$p} != '"') $p++; - if ($p >= $sLen) return false; - $w = substr($s, $p0, $p - $p0); - $p++; - return true; } - -/** -* @access private -* @return boolean true on success, false on error. -*/ -function parseWordOrQuotedString ($s, &$p, &$w) { - $sLen = strlen($s); - while ($p < $sLen && ord($s{$p}) <= 32) $p++; - if ($p >= $sLen) return false; - if (substr($s,$p,1) == '"') - return $this->parseQuotedString($s,$p,$w); - else - return $this->parseWord($s,$p,$w); } - -/** -* Combine two file system paths. -* @access private -*/ -function combineFileSystemPath ($path1, $path2) { - if ($path1 == '' || $path2 == '') return $path2; - $s = $path1; - if (substr($s,-1) != '\\' && substr($s,-1) != '/') $s = $s . "/"; - if (substr($path2,0,1) == '\\' || substr($path2,0,1) == '/') - $s = $s . substr($path2,1); - else - $s = $s . $path2; - return $s; } - -/** -* @access private -*/ -function triggerError ($msg) { - trigger_error ("MiniTemplator error: $msg", E_USER_ERROR); } - -/** -* @access private -*/ -function programLogicError ($errorId) { - die ("MiniTemplator: Program logic error $errorId.\n"); } - -} -?> + +* Template syntax: +* +* Variables: +* ${VariableName} +* +* Blocks: +* <!-- $BeginBlock BlockName --> +* ... block content ... +* <!-- $EndBlock BlockName --> +* +* Include a subtemplate: +* <!-- $Include RelativeFileName --> +* +* +*
+* General remarks:
+*  - Variable names and block names are case-insensitive.
+*  - The same variable may be used multiple times within a template.
+*  - Blocks can be nested.
+*  - Multiple blocks with the same name may occur within a template.
+* 
+* +*
+* Public methods:
+*   readTemplateFromFile   - Reads the template from a file.
+*   setTemplateString      - Assigns a new template string.
+*   setVariable            - Sets a template variable.
+*   setVariableEsc         - Sets a template variable to an escaped string value.
+*   variableExists         - Checks whether a template variable exists.
+*   addBlock               - Adds an instance of a template block.
+*   blockExists            - Checks whether a block exists.
+*   reset                  - Clears all variables and blocks.
+*   generateOutput         - Generates the HTML page and writes it to the PHP output stream.
+*   generateOutputToFile   - Generates the HTML page and writes it to a file.
+*   generateOutputToString - Generates the HTML page and writes it to a string.
+* 
+* +* Home page: {@link http://www.source-code.biz/MiniTemplator}
+* License: This module is released under the GNU/LGPL license ({@link http://www.gnu.org/licenses/lgpl.html}).
+* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland. All rights reserved.
+* This product is provided "as is" without warranty of any kind.
+* +* Version history:
+* 2001-10-24 Christian d'Heureuse (chdh): VBasic version created.
+* 2002-01-26 Markus Angst: ported to PHP4.
+* 2003-04-07 chdh: changes to adjust to Java version.
+* 2003-07-08 chdh: Method variableExists added. +* Method setVariable changed to trigger an error when the variable does not exist.
+* 2004-04-07 chdh: Parameter isOptional added to method setVariable. +* Licensing changed from GPL to LGPL.
+* 2004-04-18 chdh: Method blockExists added.
+* 2004-10-28 chdh:
+* Method setVariableEsc added.
+* Multiple blocks with the same name may now occur within a template.
+* No error ("unknown command") is generated any more, if a HTML comment starts with "${".
+* 2004-11-06 chdh:
+* "$Include" command implemented.
+* 2004-11-20 chdh:
+* "$Include" command changed so that the command text is not copied to the output file.
+*/ + +class MiniTemplator { + +//--- public member variables --------------------------------------------------------------------------------------- + +/** +* Base path for relative file names of subtemplates (for the $Include command). +* This path is prepended to the subtemplate file names. It must be set before +* readTemplateFromFile or setTemplateString. +* @access public +*/ +var $subtemplateBasePath; + +//--- private member variables -------------------------------------------------------------------------------------- + +/**#@+ +* @access private +*/ + +var $maxNestingLevel = 50; // maximum number of block nestings +var $maxInclTemplateSize = 1000000; // maximum length of template string when including subtemplates +var $template; // Template file data +var $varTab; // variables table, array index is variable no + // Fields: + // varName // variable name + // varValue // variable value +var $varTabCnt; // no of entries used in VarTab +var $varNameToNoMap; // maps variable names to variable numbers +var $varRefTab; // variable references table + // Contains an entry for each variable reference in the template. Ordered by TemplatePos. + // Fields: + // varNo // variable no + // tPosBegin // template position of begin of variable reference + // tPosEnd // template position of end of variable reference + // blockNo // block no of the (innermost) block that contains this variable reference + // blockVarNo // block variable no. Index into BlockInstTab.BlockVarTab +var $varRefTabCnt; // no of entries used in VarRefTab +var $blockTab; // Blocks table, array index is block no + // Contains an entry for each block in the template. Ordered by TPosBegin. + // Fields: + // blockName // block name + // nextWithSameName; // block no of next block with same name or -1 (blocks are backward linked in relation to template position) + // tPosBegin // template position of begin of block + // tPosContentsBegin // template pos of begin of block contents + // tPosContentsEnd // template pos of end of block contents + // tPosEnd // template position of end of block + // nestingLevel // block nesting level + // parentBlockNo // block no of parent block + // definitionIsOpen // true while $BeginBlock processed but no $EndBlock + // instances // number of instances of this block + // firstBlockInstNo // block instance no of first instance of this block or -1 + // lastBlockInstNo // block instance no of last instance of this block or -1 + // currBlockInstNo // current block instance no, used during generation of output file + // blockVarCnt // no of variables in block + // blockVarNoToVarNoMap // maps block variable numbers to variable numbers + // firstVarRefNo // variable reference no of first variable of this block or -1 +var $blockTabCnt; // no of entries used in BlockTab +var $blockNameToNoMap; // maps block names to block numbers +var $openBlocksTab; + // During parsing, this table contains the block numbers of the open parent blocks (nested outer blocks). + // Indexed by the block nesting level. +var $blockInstTab; // block instances table + // This table contains an entry for each block instance that has been added. + // Indexed by BlockInstNo. + // Fields: + // blockNo // block number + // instanceLevel // instance level of this block + // InstanceLevel is an instance counter per block. + // (In contrast to blockInstNo, which is an instance counter over the instances of all blocks) + // parentInstLevel // instance level of parent block + // nextBlockInstNo // pointer to next instance of this block or -1 + // Forward chain for instances of same block. + // blockVarTab // block instance variables +var $blockInstTabCnt; // no of entries used in BlockInstTab + +var $currentNestingLevel; // Current block nesting level during parsing. +var $templateValid; // true if a valid template is prepared +var $outputMode; // 0 = to PHP output stream, 1 = to file, 2 = to string +var $outputFileHandle; // file handle during writing of output file +var $outputError; // true when an output error occurred +var $outputString; // string buffer for the generated HTML page + +/**#@-*/ + +//--- constructor --------------------------------------------------------------------------------------------------- + +/** +* Constructs a MiniTemplator object. +* @access public +*/ +function __construct() { + $this->templateValid = false; } + +//--- template string handling -------------------------------------------------------------------------------------- + +/** +* Reads the template from a file. +* @param string $fileName name of the file that contains the template. +* @return boolean true on success, false on error. +* @access public +*/ +function readTemplateFromFile ($fileName) { + if (!$this->readFileIntoString($fileName,$s)) { + $this->triggerError ("Error while reading template file " . $fileName . "."); + return false; } + if (!$this->setTemplateString($s)) return false; + return true; } + +/** +* Assigns a new template string. +* @param string $templateString contents of the template file. +* @return boolean true on success, false on error. +* @access public +*/ +function setTemplateString ($templateString) { + $this->templateValid = false; + $this->template = $templateString; + if (!$this->parseTemplate()) return false; + $this->reset(); + $this->templateValid = true; + return true; } + +/** +* Loads the template string for a subtemplate (used for the $Include command). +* @return boolean true on success, false on error. +* @access private +*/ +function loadSubtemplate ($subtemplateName, &$s) { + $subtemplateFileName = $this->combineFileSystemPath($this->subtemplateBasePath,$subtemplateName); + if (!$this->readFileIntoString($subtemplateFileName,$s)) { + $this->triggerError ("Error while reading subtemplate file " . $subtemplateFileName . "."); + return false; } + return true; } + +//--- template parsing ---------------------------------------------------------------------------------------------- + +/** +* Parses the template. +* @return boolean true on success, false on error. +* @access private +*/ +function parseTemplate() { + $this->initParsing(); + $this->beginMainBlock(); + if (!$this->parseTemplateCommands()) return false; + $this->endMainBlock(); + if (!$this->checkBlockDefinitionsComplete()) return false; + if (!$this->parseTemplateVariables()) return false; + $this->associateVariablesWithBlocks(); + return true; } + +/** +* @access private +*/ +function initParsing() { + $this->varTab = array(); + $this->varTabCnt = 0; + $this->varNameToNoMap = array(); + $this->varRefTab = array(); + $this->varRefTabCnt = 0; + $this->blockTab = array(); + $this->blockTabCnt = 0; + $this->blockNameToNoMap = array(); + $this->openBlocksTab = array(); } + +/** +* Registers the main block. +* The main block is an implicitly defined block that covers the whole template. +* @access private +*/ +function beginMainBlock() { + $blockNo = 0; + $this->registerBlock('@@InternalMainBlock@@', $blockNo); + $bte =& $this->blockTab[$blockNo]; + $bte['tPosBegin'] = 0; + $bte['tPosContentsBegin'] = 0; + $bte['nestingLevel'] = 0; + $bte['parentBlockNo'] = -1; + $bte['definitionIsOpen'] = true; + $this->openBlocksTab[0] = $blockNo; + $this->currentNestingLevel = 1; } + +/** +* Completes the main block registration. +* @access private +*/ +function endMainBlock() { + $bte =& $this->blockTab[0]; + $bte['tPosContentsEnd'] = strlen($this->template); + $bte['tPosEnd'] = strlen($this->template); + $bte['definitionIsOpen'] = false; + $this->currentNestingLevel -= 1; } + +/** +* Parses commands within the template in the format "". +* @return boolean true on success, false on error. +* @access private +*/ +function parseTemplateCommands() { + $p = 0; + while (true) { + $p0 = strpos($this->template,'',$p0); + if ($p === false) { + $this->triggerError ("Invalid HTML comment in template at offset $p0."); + return false; } + $p += 3; + $cmdL = substr($this->template,$p0+4,$p-$p0-7); + if (!$this->processTemplateCommand($cmdL,$p0,$p,$resumeFromStart)) + return false; + if ($resumeFromStart) $p = $p0; } + return true; } + +/** +* @return boolean true on success, false on error. +* @access private +*/ +function processTemplateCommand ($cmdL, $cmdTPosBegin, $cmdTPosEnd, &$resumeFromStart) { + $resumeFromStart = false; + $p = 0; + $cmd = ''; + if (!$this->parseWord($cmdL,$p,$cmd)) return true; + $parms = substr($cmdL,$p); + switch (strtoupper($cmd)) { + case '$BEGINBLOCK': + if (!$this->processBeginBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) + return false; + break; + case '$ENDBLOCK': + if (!$this->processEndBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) + return false; + break; + case '$INCLUDE': + if (!$this->processincludeCmd($parms,$cmdTPosBegin,$cmdTPosEnd)) + return false; + $resumeFromStart = true; + break; + default: + if ($cmd[0] == '$' && !(strlen($cmd) >= 2 && $cmd[1] == '{')) { + $this->triggerError ("Unknown command \"$cmd\" in template at offset $cmdTPosBegin."); + return false; }} + return true; } + +/** +* Processes the $BeginBlock command. +* @return boolean true on success, false on error. +* @access private +*/ +function processBeginBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { + $p = 0; + if (!$this->parseWord($parms,$p,$blockName)) { + $this->triggerError ("Missing block name in \$BeginBlock command in template at offset $cmdTPosBegin."); + return false; } + if (trim(substr($parms,$p)) != '') { + $this->triggerError ("Extra parameter in \$BeginBlock command in template at offset $cmdTPosBegin."); + return false; } + $this->registerBlock ($blockName, $blockNo); + $btr =& $this->blockTab[$blockNo]; + $btr['tPosBegin'] = $cmdTPosBegin; + $btr['tPosContentsBegin'] = $cmdTPosEnd; + $btr['nestingLevel'] = $this->currentNestingLevel; + $btr['parentBlockNo'] = $this->openBlocksTab[$this->currentNestingLevel-1]; + $this->openBlocksTab[$this->currentNestingLevel] = $blockNo; + $this->currentNestingLevel += 1; + if ($this->currentNestingLevel > $this->maxNestingLevel) { + $this->triggerError ("Block nesting overflow in template at offset $cmdTPosBegin."); + return false; } + return true; } + +/** +* Processes the $EndBlock command. +* @return boolean true on success, false on error. +* @access private +*/ +function processEndBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { + $p = 0; + if (!$this->parseWord($parms,$p,$blockName)) { + $this->triggerError ("Missing block name in \$EndBlock command in template at offset $cmdTPosBegin."); + return false; } + if (trim(substr($parms,$p)) != '') { + $this->triggerError ("Extra parameter in \$EndBlock command in template at offset $cmdTPosBegin."); + return false; } + if (!$this->lookupBlockName($blockName,$blockNo)) { + $this->triggerError ("Undefined block name \"$blockName\" in \$EndBlock command in template at offset $cmdTPosBegin."); + return false; } + $this->currentNestingLevel -= 1; + $btr =& $this->blockTab[$blockNo]; + if (!$btr['definitionIsOpen']) { + $this->triggerError ("Multiple \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin."); + return false; } + if ($btr['nestingLevel'] != $this->currentNestingLevel) { + $this->triggerError ("Block nesting level mismatch at \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin."); + return false; } + $btr['tPosContentsEnd'] = $cmdTPosBegin; + $btr['tPosEnd'] = $cmdTPosEnd; + $btr['definitionIsOpen'] = false; + return true; } + +/** +* @access private +*/ +function registerBlock($blockName, &$blockNo) { + $blockNo = $this->blockTabCnt++; + $btr =& $this->blockTab[$blockNo]; + $btr = array(); + $btr['blockName'] = $blockName; + if (!$this->lookupBlockName($blockName,$btr['nextWithSameName'])) + $btr['nextWithSameName'] = -1; + $btr['definitionIsOpen'] = true; + $btr['instances'] = 0; + $btr['firstBlockInstNo'] = -1; + $btr['lastBlockInstNo'] = -1; + $btr['blockVarCnt'] = 0; + $btr['firstVarRefNo'] = -1; + $btr['blockVarNoToVarNoMap'] = array(); + $this->blockNameToNoMap[strtoupper($blockName)] = $blockNo; } + +/** +* Checks that all block definitions are closed. +* @return boolean true on success, false on error. +* @access private +*/ +function checkBlockDefinitionsComplete() { + for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) { + $btr =& $this->blockTab[$blockNo]; + if ($btr['definitionIsOpen']) { + $this->triggerError ("Missing \$EndBlock command in template for block " . $btr['blockName'] . "."); + return false; }} + if ($this->currentNestingLevel != 0) { + $this->triggerError ("Block nesting level error at end of template."); + return false; } + return true; } + +/** +* Processes the $Include command. +* @return boolean true on success, false on error. +* @access private +*/ +function processIncludeCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) { + $p = 0; + if (!$this->parseWordOrQuotedString($parms,$p,$subtemplateName)) { + $this->triggerError ("Missing or invalid subtemplate name in \$Include command in template at offset $cmdTPosBegin."); + return false; } + if (trim(substr($parms,$p)) != '') { + $this->triggerError ("Extra parameter in \$include command in template at offset $cmdTPosBegin."); + return false; } + return $this->insertSubtemplate($subtemplateName,$cmdTPosBegin,$cmdTPosEnd); } + +/** +* Processes the $Include command. +* @return boolean true on success, false on error. +* @access private +*/ +function insertSubtemplate ($subtemplateName, $tPos1, $tPos2) { + if (strlen($this->template) > $this->maxInclTemplateSize) { + $this->triggerError ("Subtemplate include aborted because the internal template string is longer than $this->maxInclTemplateSize characters."); + return false; } + if (!$this->loadSubtemplate($subtemplateName,$subtemplate)) return false; + // (Copying the template to insert a subtemplate is a bit slow. In a future implementation of MiniTemplator, + // a table could be used that contains references to the string fragments.) + $this->template = substr($this->template,0,$tPos1) . $subtemplate . substr($this->template,$tPos2); + return true; } + +/** +* Parses variable references within the template in the format "${VarName}". +* @return boolean true on success, false on error. +* @access private +*/ +function parseTemplateVariables() { + $p = 0; + while (true) { + $p = strpos($this->template, '${', $p); + if ($p === false) break; + $p0 = $p; + $p = strpos($this->template, '}', $p); + if ($p === false) { + $this->triggerError ("Invalid variable reference in template at offset $p0."); + return false; } + $p += 1; + $varName = trim(substr($this->template, $p0+2, $p-$p0-3)); + if (strlen($varName) == 0) { + $this->triggerError ("Empty variable name in template at offset $p0."); + return false; } + $this->registerVariableReference ($varName, $p0, $p); } + return true; } + +/** +* @access private +*/ +function registerVariableReference ($varName, $tPosBegin, $tPosEnd) { + if (!$this->lookupVariableName($varName,$varNo)) + $this->registerVariable($varName,$varNo); + $varRefNo = $this->varRefTabCnt++; + $vrtr =& $this->varRefTab[$varRefNo]; + $vrtr = array(); + $vrtr['tPosBegin'] = $tPosBegin; + $vrtr['tPosEnd'] = $tPosEnd; + $vrtr['varNo'] = $varNo; } + +/** +* @access private +*/ +function registerVariable ($varName, &$varNo) { + $varNo = $this->varTabCnt++; + $vtr =& $this->varTab[$varNo]; + $vtr = array(); + $vtr['varName'] = $varName; + $vtr['varValue'] = ''; + $this->varNameToNoMap[strtoupper($varName)] = $varNo; } + +/** +* Associates variable references with blocks. +* @access private +*/ +function associateVariablesWithBlocks() { + $varRefNo = 0; + $activeBlockNo = 0; + $nextBlockNo = 1; + while ($varRefNo < $this->varRefTabCnt) { + $vrtr =& $this->varRefTab[$varRefNo]; + $varRefTPos = $vrtr['tPosBegin']; + $varNo = $vrtr['varNo']; + if ($varRefTPos >= $this->blockTab[$activeBlockNo]['tPosEnd']) { + $activeBlockNo = $this->blockTab[$activeBlockNo]['parentBlockNo']; + continue; } + if ($nextBlockNo < $this->blockTabCnt) { + if ($varRefTPos >= $this->blockTab[$nextBlockNo]['tPosBegin']) { + $activeBlockNo = $nextBlockNo; + $nextBlockNo += 1; + continue; }} + $btr =& $this->blockTab[$activeBlockNo]; + if ($varRefTPos < $btr['tPosBegin']) + $this->programLogicError(1); + $blockVarNo = $btr['blockVarCnt']++; + $btr['blockVarNoToVarNoMap'][$blockVarNo] = $varNo; + if ($btr['firstVarRefNo'] == -1) + $btr['firstVarRefNo'] = $varRefNo; + $vrtr['blockNo'] = $activeBlockNo; + $vrtr['blockVarNo'] = $blockVarNo; + $varRefNo += 1; }} + +//--- build up (template variables and blocks) ---------------------------------------------------------------------- + +/** +* Clears all variables and blocks. +* This method can be used to produce another HTML page with the same +* template. It is faster than creating another MiniTemplator object, +* because the template does not have to be parsed again. +* All variable values are cleared and all added block instances are deleted. +* @access public +*/ +function reset() { + for ($varNo=0; $varNo<$this->varTabCnt; $varNo++) + $this->varTab[$varNo]['varValue'] = ''; + for ($blockNo=0; $blockNo<$this->blockTabCnt; $blockNo++) { + $btr =& $this->blockTab[$blockNo]; + $btr['instances'] = 0; + $btr['firstBlockInstNo'] = -1; + $btr['lastBlockInstNo'] = -1; } + $this->blockInstTab = array(); + $this->blockInstTabCnt = 0; } + +/** +* Sets a template variable. +* For variables that are used in blocks, the variable value +* must be set before {@link addBlock} is called. +* @param string $variableName the name of the variable to be set. +* @param string $variableValue the new value of the variable. +* @param boolean $isOptional Specifies whether an error should be +* generated when the variable does not exist in the template. If +* $isOptional is false and the variable does not exist, an error is +* generated. +* @return boolean true on success, or false on error (e.g. when no +* variable with the specified name exists in the template and +* $isOptional is false). +* @access public +*/ +function setVariable ($variableName, $variableValue, $isOptional=false) { + if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } + if (!$this->lookupVariableName($variableName,$varNo)) { + if ($isOptional) return true; + $this->triggerError ("Variable \"$variableName\" not defined in template."); + return false; } + $this->varTab[$varNo]['varValue'] = $variableValue; + return true; } + +/** +* Sets a template variable to an escaped string. +* This method is identical to (@link setVariable), except that +* the characters <, >, &, ' and " of variableValue are +* replaced by their corresponding HTML/XML character entity codes. +* For variables that are used in blocks, the variable value +* must be set before {@link addBlock} is called. +* @param string $variableName the name of the variable to be set. +* @param string $variableValue the new value of the variable. Special HTML/XML characters are escaped. +* @param boolean $isOptional Specifies whether an error should be +* generated when the variable does not exist in the template. If +* $isOptional is false and the variable does not exist, an error is +* generated. +* @return boolean true on success, or false on error (e.g. when no +* variable with the specified name exists in the template and +* $isOptional is false). +* @access public +*/ +function setVariableEsc ($variableName, $variableValue, $isOptional=false) { + return $this->setVariable($variableName,htmlspecialchars($variableValue,ENT_QUOTES),$isOptional); } + +/** +* Checks whether a variable with the specified name exists within the template. +* @param string $variableName the name of the variable. +* @return boolean true if the variable exists, or false when no +* variable with the specified name exists in the template. +* @access public +*/ +function variableExists ($variableName) { + if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } + return $this->lookupVariableName($variableName,$varNo); } + +/** +* Adds an instance of a template block. +* If the block contains variables, these variables must be set +* before the block is added. +* If the block contains subblocks (nested blocks), the subblocks +* must be added before this block is added. +* If multiple blocks exist with the specified name, an instance +* is added for each block occurence. +* @param string blockName the name of the block to be added. +* @return boolean true on success, false on error (e.g. when no +* block with the specified name exists in the template). +* @access public +*/ +function addBlock($blockName) { + if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } + if (!$this->lookupBlockName($blockName,$blockNo)) { + $this->triggerError ("Block \"$blockName\" not defined in template."); + return false; } + while ($blockNo != -1) { + $this->addBlockByNo($blockNo); + $blockNo = $this->blockTab[$blockNo]['nextWithSameName']; } + return true; } + +/** +* @access private +*/ +function addBlockByNo ($blockNo) { + $btr =& $this->blockTab[$blockNo]; + $this->registerBlockInstance ($blockInstNo); + $bitr =& $this->blockInstTab[$blockInstNo]; + if ($btr['firstBlockInstNo'] == -1) + $btr['firstBlockInstNo'] = $blockInstNo; + if ($btr['lastBlockInstNo'] != -1) + $this->blockInstTab[$btr['lastBlockInstNo']]['nextBlockInstNo'] = $blockInstNo; + // set forward pointer of chain + $btr['lastBlockInstNo'] = $blockInstNo; + $parentBlockNo = $btr['parentBlockNo']; + $blockVarCnt = $btr['blockVarCnt']; + $bitr['blockNo'] = $blockNo; + $bitr['instanceLevel'] = $btr['instances']++; + if ($parentBlockNo == -1) + $bitr['parentInstLevel'] = -1; + else + $bitr['parentInstLevel'] = $this->blockTab[$parentBlockNo]['instances']; + $bitr['nextBlockInstNo'] = -1; + $bitr['blockVarTab'] = array(); + // copy instance variables for this block + for ($blockVarNo=0; $blockVarNo<$blockVarCnt; $blockVarNo++) { + $varNo = $btr['blockVarNoToVarNoMap'][$blockVarNo]; + $bitr['blockVarTab'][$blockVarNo] = $this->varTab[$varNo]['varValue']; }} + +/** +* @access private +*/ +function registerBlockInstance (&$blockInstNo) { + $blockInstNo = $this->blockInstTabCnt++; } + +/** +* Checks whether a block with the specified name exists within the template. +* @param string $blockName the name of the block. +* @return boolean true if the block exists, or false when no +* block with the specified name exists in the template. +* @access public +*/ +function blockExists ($blockName) { + if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } + return $this->lookupBlockName($blockName,$blockNo); } + +//--- output generation --------------------------------------------------------------------------------------------- + +/** +* Generates the HTML page and writes it to the PHP output stream. +* @return boolean true on success, false on error. +* @access public +*/ +function generateOutput () { + $this->outputMode = 0; + if (!$this->generateOutputPage()) return false; + return true; } + +/** +* Generates the HTML page and writes it to a file. +* @param string $fileName name of the output file. +* @return boolean true on success, false on error. +* @access public +*/ +function generateOutputToFile ($fileName) { + $fh = fopen($fileName,"wb"); + if ($fh === false) return false; + $this->outputMode = 1; + $this->outputFileHandle = $fh; + $ok = $this->generateOutputPage(); + fclose ($fh); + return $ok; } + +/** +* Generates the HTML page and writes it to a string. +* @param string $outputString variable that receives +* the contents of the generated HTML page. +* @return boolean true on success, false on error. +* @access public +*/ +function generateOutputToString (&$outputString) { + $outputString = "Error"; + $this->outputMode = 2; + $this->outputString = ""; + if (!$this->generateOutputPage()) return false; + $outputString = $this->outputString; + return true; } + +/** +* @access private +* @return boolean true on success, false on error. +*/ +function generateOutputPage() { + if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; } + if ($this->blockTab[0]['instances'] == 0) + $this->addBlockByNo (0); // add main block + for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) { + $btr =& $this->blockTab[$blockNo]; + $btr['currBlockInstNo'] = $btr['firstBlockInstNo']; } + $this->outputError = false; + $this->writeBlockInstances (0, -1); + if ($this->outputError) return false; + return true; } + +/** +* Writes all instances of a block that are contained within a specific +* parent block instance. +* Called recursively. +* @access private +*/ +function writeBlockInstances ($blockNo, $parentInstLevel) { + $btr =& $this->blockTab[$blockNo]; + while (!$this->outputError) { + $blockInstNo = $btr['currBlockInstNo']; + if ($blockInstNo == -1) break; + $bitr =& $this->blockInstTab[$blockInstNo]; + if ($bitr['parentInstLevel'] < $parentInstLevel) + $this->programLogicError (2); + if ($bitr['parentInstLevel'] > $parentInstLevel) break; + $this->writeBlockInstance ($blockInstNo); + $btr['currBlockInstNo'] = $bitr['nextBlockInstNo']; }} + +/** +* @access private +*/ +function writeBlockInstance($blockInstNo) { + $bitr =& $this->blockInstTab[$blockInstNo]; + $blockNo = $bitr['blockNo']; + $btr =& $this->blockTab[$blockNo]; + $tPos = $btr['tPosContentsBegin']; + $subBlockNo = $blockNo + 1; + $varRefNo = $btr['firstVarRefNo']; + while (!$this->outputError) { + $tPos2 = $btr['tPosContentsEnd']; + $kind = 0; // assume end-of-block + if ($varRefNo != -1 && $varRefNo < $this->varRefTabCnt) { // check for variable reference + $vrtr =& $this->varRefTab[$varRefNo]; + if ($vrtr['tPosBegin'] < $tPos) { + $varRefNo += 1; + continue; } + if ($vrtr['tPosBegin'] < $tPos2) { + $tPos2 = $vrtr['tPosBegin']; + $kind = 1; }} + if ($subBlockNo < $this->blockTabCnt) { // check for subblock + $subBtr =& $this->blockTab[$subBlockNo]; + if ($subBtr['tPosBegin'] < $tPos) { + $subBlockNo += 1; + continue; } + if ($subBtr['tPosBegin'] < $tPos2) { + $tPos2 = $subBtr['tPosBegin']; + $kind = 2; }} + if ($tPos2 > $tPos) + $this->writeString (substr($this->template,$tPos,$tPos2-$tPos)); + switch ($kind) { + case 0: // end of block + return; + case 1: // variable + $vrtr =& $this->varRefTab[$varRefNo]; + if ($vrtr['blockNo'] != $blockNo) + $this->programLogicError (4); + $variableValue = $bitr['blockVarTab'][$vrtr['blockVarNo']]; + $this->writeString ($variableValue); + $tPos = $vrtr['tPosEnd']; + $varRefNo += 1; + break; + case 2: // sub block + $subBtr =& $this->blockTab[$subBlockNo]; + if ($subBtr['parentBlockNo'] != $blockNo) + $this->programLogicError (3); + $this->writeBlockInstances ($subBlockNo, $bitr['instanceLevel']); // recursive call + $tPos = $subBtr['tPosEnd']; + $subBlockNo += 1; + break; }}} + +/** +* @access private +*/ +function writeString ($s) { + if ($this->outputError) return; + switch ($this->outputMode) { + case 0: // output to PHP output stream + if (!print($s)) + $this->outputError = true; + break; + case 1: // output to file + $rc = fwrite($this->outputFileHandle, $s); + if ($rc === false) $this->outputError = true; + break; + case 2: // output to string + $this->outputString .= $s; + break; }} + +//--- name lookup routines ------------------------------------------------------------------------------------------ + +/** +* Maps variable name to variable number. +* @return boolean true on success, false if the variable is not found. +* @access private +*/ +function lookupVariableName ($varName, &$varNo) { + $x =& $this->varNameToNoMap[strtoupper($varName)]; + if (!isset($x)) return false; + $varNo = $x; + return true; } + +/** +* Maps block name to block number. +* If there are multiple blocks with the same name, the block number of the last +* registered block with that name is returned. +* @return boolean true on success, false when the block is not found. +* @access private +*/ +function lookupBlockName ($blockName, &$blockNo) { + $x =& $this->blockNameToNoMap[strtoupper($blockName)]; + if (!isset($x)) return false; + $blockNo = $x; + return true; } + +//--- general utility routines ----------------------------------------------------------------------------------------- + +/** +* Reads a file into a string. +* @return boolean true on success, false on error. +* @access private +*/ +function readFileIntoString ($fileName, &$s) { + if (function_exists('version_compare') && version_compare(phpversion(),"4.3.0",">=")) { + $s = file_get_contents($fileName); + if ($s === false) return false; + return true; } + $fh = fopen($fileName,"rb"); + if ($fh === false) return false; + $fileSize = filesize($fileName); + if ($fileSize === false) {fclose ($fh); return false; } + $s = fread($fh,$fileSize); + fclose ($fh); + if (strlen($s) != $fileSize) return false; + return true; } + +/** +* @access private +* @return boolean true on success, false when the end of the string is reached. +*/ +function parseWord ($s, &$p, &$w) { + $sLen = strlen($s); + while ($p < $sLen && ord($s[$p]) <= 32) $p++; + if ($p >= $sLen) return false; + $p0 = $p; + while ($p < $sLen && ord($s[$p]) > 32) $p++; + $w = substr($s, $p0, $p - $p0); + return true; } + +/** +* @access private +* @return boolean true on success, false on error. +*/ +function parseQuotedString ($s, &$p, &$w) { + $sLen = strlen($s); + while ($p < $sLen && ord($s[$p]) <= 32) $p++; + if ($p >= $sLen) return false; + if (substr($s,$p,1) != '"') return false; + $p++; $p0 = $p; + while ($p < $sLen && $s[$p] != '"') $p++; + if ($p >= $sLen) return false; + $w = substr($s, $p0, $p - $p0); + $p++; + return true; } + +/** +* @access private +* @return boolean true on success, false on error. +*/ +function parseWordOrQuotedString ($s, &$p, &$w) { + $sLen = strlen($s); + while ($p < $sLen && ord($s[$p]) <= 32) $p++; + if ($p >= $sLen) return false; + if (substr($s,$p,1) == '"') + return $this->parseQuotedString($s,$p,$w); + else + return $this->parseWord($s,$p,$w); } + +/** +* Combine two file system paths. +* @access private +*/ +function combineFileSystemPath ($path1, $path2) { + if ($path1 == '' || $path2 == '') return $path2; + $s = $path1; + if (substr($s,-1) != '\\' && substr($s,-1) != '/') $s = $s . "/"; + if (substr($path2,0,1) == '\\' || substr($path2,0,1) == '/') + $s = $s . substr($path2,1); + else + $s = $s . $path2; + return $s; } + +/** +* @access private +*/ +function triggerError ($msg) { + trigger_error ("MiniTemplator error: $msg", E_USER_ERROR); } + +/** +* @access private +*/ +function programLogicError ($errorId) { + die ("MiniTemplator: Program logic error $errorId.\n"); } + +} +?>