summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/MiniTemplator.class.php1843
-rw-r--r--lib/gettext/gettext.inc.php (renamed from lib/gettext/gettext.inc)8
-rwxr-xr-xlib/gettext/gettext.php74
-rw-r--r--lib/gettext/plurals.php461
4 files changed, 1405 insertions, 981 deletions
diff --git a/lib/MiniTemplator.class.php b/lib/MiniTemplator.class.php
index e70f0a470..728cdf260 100644
--- a/lib/MiniTemplator.class.php
+++ b/lib/MiniTemplator.class.php
@@ -1,922 +1,921 @@
-<?php
-/**
-* File MiniTemplator.class.php
-* @package MiniTemplator
-*/
-
-/**
-* A compact template engine for HTML files.
-*
-* Requires PHP 4.0.4 or newer.
-*
-* <pre>
-* Template syntax:
-*
-* Variables:
-* ${VariableName}
-*
-* Blocks:
-* &lt;!-- $BeginBlock BlockName --&gt;
-* ... block content ...
-* &lt;!-- $EndBlock BlockName --&gt;
-*
-* Include a subtemplate:
-* &lt;!-- $Include RelativeFileName --&gt;
-* </pre>
-*
-* <pre>
-* 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.
-* </pre>
-*
-* <pre>
-* 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.
-* </pre>
-*
-* Home page: {@link http://www.source-code.biz/MiniTemplator}<br>
-* License: This module is released under the GNU/LGPL license ({@link http://www.gnu.org/licenses/lgpl.html}).<br>
-* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland. All rights reserved.<br>
-* This product is provided "as is" without warranty of any kind.<br>
-*
-* Version history:<br>
-* 2001-10-24 Christian d'Heureuse (chdh): VBasic version created.<br>
-* 2002-01-26 Markus Angst: ported to PHP4.<br>
-* 2003-04-07 chdh: changes to adjust to Java version.<br>
-* 2003-07-08 chdh: Method variableExists added.
-* Method setVariable changed to trigger an error when the variable does not exist.<br>
-* 2004-04-07 chdh: Parameter isOptional added to method setVariable.
-* Licensing changed from GPL to LGPL.<br>
-* 2004-04-18 chdh: Method blockExists added.<br>
-* 2004-10-28 chdh:<br>
-* Method setVariableEsc added.<br>
-* Multiple blocks with the same name may now occur within a template.<br>
-* No error ("unknown command") is generated any more, if a HTML comment starts with "${".<br>
-* 2004-11-06 chdh:<br>
-* "$Include" command implemented.<br>
-* 2004-11-20 chdh:<br>
-* "$Include" command changed so that the command text is not copied to the output file.<br>
-*/
-
-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 "<!-- $command parameters -->".
-* @return boolean true on success, false on error.
-* @access private
-*/
-function parseTemplateCommands() {
- $p = 0;
- while (true) {
- $p0 = strpos($this->template,'<!--',$p);
- if ($p0 === false) break;
- $p = 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 &lt;, &gt;, &amp;, ' 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"); }
-
-}
-?>
+<?php
+/**
+* File MiniTemplator.class.php
+* @package MiniTemplator
+*/
+
+/**
+* A compact template engine for HTML files.
+*
+* Requires PHP 4.0.4 or newer.
+*
+* <pre>
+* Template syntax:
+*
+* Variables:
+* ${VariableName}
+*
+* Blocks:
+* &lt;!-- $BeginBlock BlockName --&gt;
+* ... block content ...
+* &lt;!-- $EndBlock BlockName --&gt;
+*
+* Include a subtemplate:
+* &lt;!-- $Include RelativeFileName --&gt;
+* </pre>
+*
+* <pre>
+* 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.
+* </pre>
+*
+* <pre>
+* 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.
+* </pre>
+*
+* Home page: {@link http://www.source-code.biz/MiniTemplator}<br>
+* License: This module is released under the GNU/LGPL license ({@link http://www.gnu.org/licenses/lgpl.html}).<br>
+* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland. All rights reserved.<br>
+* This product is provided "as is" without warranty of any kind.<br>
+*
+* Version history:<br>
+* 2001-10-24 Christian d'Heureuse (chdh): VBasic version created.<br>
+* 2002-01-26 Markus Angst: ported to PHP4.<br>
+* 2003-04-07 chdh: changes to adjust to Java version.<br>
+* 2003-07-08 chdh: Method variableExists added.
+* Method setVariable changed to trigger an error when the variable does not exist.<br>
+* 2004-04-07 chdh: Parameter isOptional added to method setVariable.
+* Licensing changed from GPL to LGPL.<br>
+* 2004-04-18 chdh: Method blockExists added.<br>
+* 2004-10-28 chdh:<br>
+* Method setVariableEsc added.<br>
+* Multiple blocks with the same name may now occur within a template.<br>
+* No error ("unknown command") is generated any more, if a HTML comment starts with "${".<br>
+* 2004-11-06 chdh:<br>
+* "$Include" command implemented.<br>
+* 2004-11-20 chdh:<br>
+* "$Include" command changed so that the command text is not copied to the output file.<br>
+*/
+
+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 "<!-- $command parameters -->".
+* @return boolean true on success, false on error.
+* @access private
+*/
+function parseTemplateCommands() {
+ $p = 0;
+ while (true) {
+ $p0 = strpos($this->template,'<!--',$p);
+ if ($p0 === false) break;
+ $p = 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 &lt;, &gt;, &amp;, ' 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
+ print $s;
+ 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"); }
+
+}
+?>
diff --git a/lib/gettext/gettext.inc b/lib/gettext/gettext.inc.php
index c9f7dc016..ed5be6bbd 100644
--- a/lib/gettext/gettext.inc
+++ b/lib/gettext/gettext.inc.php
@@ -69,10 +69,10 @@ function get_list_of_locales($locale) {
* sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
*/
$locale_names = array();
- $lang = NULL;
- $country = NULL;
- $charset = NULL;
- $modifier = NULL;
+ $lang = null;
+ $country = null;
+ $charset = null;
+ $modifier = null;
if ($locale) {
if (preg_match("/^(?P<lang>[a-z]{2,3})" // language code
."(?:_(?P<country>[A-Z]{2}))?" // country code
diff --git a/lib/gettext/gettext.php b/lib/gettext/gettext.php
index edbd93304..173d4c448 100755
--- a/lib/gettext/gettext.php
+++ b/lib/gettext/gettext.php
@@ -21,6 +21,8 @@
*/
+require('plurals.php');
+
/**
* Provides a simple gettext replacement that works independently from
* the system's gettext abilities.
@@ -39,16 +41,16 @@ class gettext_reader {
//private:
var $BYTEORDER = 0; // 0: low endian, 1: big endian
- var $STREAM = NULL;
+ var $STREAM = null;
var $short_circuit = false;
var $enable_cache = false;
- var $originals = NULL; // offset of original table
- var $translations = NULL; // offset of translation table
- var $pluralheader = NULL; // cache header field for plural forms
+ var $originals = null; // offset of original table
+ var $translations = null; // offset of translation table
+ var $pluralheader = null; // cache header field for plural forms
var $total = 0; // total string count
- var $table_originals = NULL; // table for original strings (offsets)
- var $table_translations = NULL; // table for translated strings (offsets)
- var $cache_translations = NULL; // original -> translation mapping
+ var $table_originals = null; // table for original strings (offsets)
+ var $table_translations = null; // table for translated strings (offsets)
+ var $cache_translations = null; // original -> translation mapping
/* Methods */
@@ -270,41 +272,6 @@ class gettext_reader {
}
/**
- * Sanitize plural form expression for use in PHP eval call.
- *
- * @access private
- * @return string sanitized plural form expression
- */
- function sanitize_plural_expression($expr) {
- // Get rid of disallowed characters.
- $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
-
- // Add parenthesis for tertiary '?' operator.
- $expr .= ';';
- $res = '';
- $p = 0;
- for ($i = 0; $i < strlen($expr); $i++) {
- $ch = $expr[$i];
- switch ($ch) {
- case '?':
- $res .= ' ? (';
- $p++;
- break;
- case ':':
- $res .= ') : (';
- break;
- case ';':
- $res .= str_repeat( ')', $p) . ';';
- $p = 0;
- break;
- default:
- $res .= $ch;
- }
- }
- return $res;
- }
-
- /**
* Parse full PO header and extract only plural forms line.
*
* @access private
@@ -327,17 +294,17 @@ class gettext_reader {
function get_plural_forms() {
// lets assume message number 0 is header
// this is true, right?
- $this->load_tables();
+ $this->load_tables();
// cache header field for plural forms
- if (! is_string($this->pluralheader)) {
+ if ($this->pluralheader === null) {
if ($this->enable_cache) {
$header = $this->cache_translations[""];
} else {
$header = $this->get_translation_string(0);
}
$expr = $this->extract_plural_forms_header_from_po_header($header);
- $this->pluralheader = $this->sanitize_plural_expression($expr);
+ $this->pluralheader = new PluralHeader($expr);
}
return $this->pluralheader;
}
@@ -353,17 +320,14 @@ class gettext_reader {
if (!is_int($n)) {
throw new InvalidArgumentException(
"Select_string only accepts integers: " . $n);
- }
- $string = $this->get_plural_forms();
- $string = str_replace('nplurals',"\$total",$string);
- $string = str_replace("n",$n,$string);
- $string = str_replace('plural',"\$plural",$string);
+ }
+
+ $plural_header = $this->get_plural_forms();
+ $plural = $plural_header->expression->evaluate($n);
- $total = 0;
- $plural = 0;
+ if ($plural < 0) $plural = 0;
+ if ($plural >= $plural_header->total) $plural = $plural_header->total - 1;
- eval("$string");
- if ($plural >= $total) $plural = $total - 1;
return $plural;
}
@@ -387,7 +351,7 @@ class gettext_reader {
// find out the appropriate form
$select = $this->select_string($number);
- // this should contains all strings separated by NULLs
+ // this should contains all strings separated by nulls
$key = $single . chr(0) . $plural;
diff --git a/lib/gettext/plurals.php b/lib/gettext/plurals.php
new file mode 100644
index 000000000..332f5e97c
--- /dev/null
+++ b/lib/gettext/plurals.php
@@ -0,0 +1,461 @@
+<?php
+/*
+ Copyright (c) 2020 Sunil Mohan Adapa <sunil at medhas dot org>
+
+ Drop in replacement for native gettext.
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ PHP-gettext is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+/**
+ * Lexical analyzer for gettext plurals expression. Takes a string to parse
+ * during construction and returns a single token every time peek() or
+ * fetch_token() are called. The special string '__END__' is returned if there
+ * are no more tokens to be read. Spaces are ignored during tokenization.
+ */
+class PluralsLexer {
+ private $string;
+ private $position;
+
+ /**
+ * Constructor
+ *
+ * @param string string Contains the value gettext plurals expression to
+ * analyze.
+ */
+ public function __construct(string $string) {
+ $this->string = $string;
+ $this->position = 0;
+ }
+
+ /**
+ * Return the next token and the length to advance the read position without
+ * actually advancing the read position. Tokens for operators and variables
+ * are simple strings containing the operator or variable. If there are no
+ * more token to provide, the special value ['__END__', 0] is returned. If
+ * there was an unexpected input an Exception is raised.
+ *
+ * @access private
+ * @throws Exception If there is unexpected input in the provided string.
+ * @return array The next token and length to advance the current position.
+ */
+ private function _tokenize() {
+ $buf = $this->string;
+
+ // Consume all spaces until the next token
+ $index = $this->position;
+ while ($index < strlen($buf) && $buf[$index] == ' ') {
+ $index++;
+ }
+ $this->position = $index;
+
+ // Return special token if next of the string is reached.
+ if (strlen($buf) - $index == 0) {
+ return ['__END__', 0];
+ }
+
+ // Operators with two characters
+ $doubles = ['==', '!=', '>=', '<=', '&&', '||'];
+ $next = substr($buf, $index, 2);
+ if (in_array($next, $doubles)) {
+ return [$next, 2];
+ }
+
+ // Operators with single character or variable 'n'.
+ $singles = [
+ 'n', '(', ')', '?', ':', '+', '-', '*', '/', '%', '!', '>', '<'];
+ if (in_array($buf[$index], $singles)) {
+ return [$buf[$index], 1];
+ }
+
+ // Whole number constants, return an integer.
+ $digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ $pos = $index;
+ while ($pos < strlen($buf) && in_array($buf[$pos], $digits)) {
+ $pos++;
+ }
+ if ($pos != $index) {
+ $length = $pos - $index;
+ return [(int)substr($buf, $index, $length), $length];
+ }
+
+ // Throw and exception for all other unexpected input in the string.
+ throw new Exception('Lexical analysis failed');
+ }
+
+ /**
+ * Return the next token without actually advancing the read position.
+ * Tokens for operators and variables are simple strings containing the
+ * operator or variable. If there are no more tokens to provide, the special
+ * value '__END__' is returned. If there was an unexpected input an
+ * Exception is raised.
+ *
+ * @throws Exception If there is unexpected input in the provided string.
+ * @return string The next token.
+ */
+ public function peek() {
+ list($token, $length) = $this->_tokenize();
+ return $token;
+ }
+
+ /**
+ * Return the next token after advancing the read position. Tokens for
+ * operators and variables are simple strings containing the operator or
+ * variable. If there are no more token to provide, the special value
+ * '__END__' is returned. If there was an unexpected input an Exception is
+ * raised.
+ *
+ * @throws Exception If there is unexpected input in the provided string.
+ * @return string The next token.
+ */
+ public function fetch_token() {
+ list($token, $length) = $this->_tokenize();
+ $this->position += $length;
+ return $token;
+ }
+}
+
+/**
+ * A parsed representation of the gettext plural expression. This is a tree
+ * containing further expressions depending on how nested the given input is.
+ * Calling the evaluate() function computes the value of the expression if the
+ * variable 'n' is set a certain value. This is used to decide which plural
+ * string translation to use based on the number items at hand.
+ */
+class PluralsExpression {
+ private $operator;
+ private $operands;
+
+ const BINARY_OPERATORS = [
+ '==', '!=', '>=', '<=', '&&', '||', '+', '-', '*', '/', '%', '>', '<'];
+ const UNARY_OPERATORS = ['!'];
+
+ /**
+ * Constructor
+ *
+ * @param string Operator for the expression.
+ * @param (int|string|PuralsExpression)[] Variable number of operands of the
+ * expression. One int operand is expected in case the operator is 'const'.
+ * One string operand with value 'n' is expected in case the operator is
+ * 'var'. For all other operators, the operands much be objects of type
+ * PluralExpression. Unary operators expect one operand, binary operators
+ * expect two operands and trinary operators expect three operands.
+ */
+ public function __construct($operator, ...$operands) {
+ $this->operator = $operator;
+ $this->operands = $operands;
+ }
+
+ /**
+ * Return a parenthesized string representation of the expression for
+ * debugging purposes.
+ *
+ * @return string A string representation of the expression.
+ */
+ public function to_string() {
+ if ($this->operator == 'const' || $this->operator == 'var') {
+ return $this->operands[0];
+ } elseif (in_array($this->operator, self::BINARY_OPERATORS)) {
+ return sprintf(
+ "(%s %s %s)", $this->operands[0]->to_string(), $this->operator,
+ $this->operands[1]->to_string());
+ } elseif (in_array($this->operator, self::UNARY_OPERATORS)) {
+ return sprintf(
+ "(%s %s)", $this->operator, $this->operands[0]->to_string());
+ } elseif ($this->operator == '?') {
+ return sprintf(
+ "(%s ? %s : %s)", $this->operands[0]->to_string(),
+ $this->operands[1]->to_string(),
+ $this->operands[2]->to_string());
+ }
+ }
+
+ /**
+ * Return the computed value of the expression if the variable 'n' is set to
+ * a certain value.
+ *
+ * @param int The value of the variable n to use when evaluating.
+ * @throws Exception If the expression has been constructed incorrectly.
+ * @return int The value of the expression after evaluation.
+ */
+ public function evaluate($n) {
+ if (!in_array($this->operator, ['const', 'var'])) {
+ $operand1 = $this->operands[0]->evaluate($n);
+ }
+ if (in_array($this->operator, self::BINARY_OPERATORS) ||
+ $this->operator == '?') {
+ $operand2 = $this->operands[1]->evaluate($n);
+ }
+ if ($this->operator == '?') {
+ $operand3 = $this->operands[2]->evaluate($n);
+ }
+
+ switch ($this->operator) {
+ case 'const':
+ return $this->operands[0];
+ case 'var':
+ return $n;
+ case '!':
+ return !($operand1);
+ case '==':
+ return $operand1 == $operand2;
+ case '!=':
+ return $operand1 != $operand2;
+ case '>=':
+ return $operand1 >= $operand2;
+ case '<=':
+ return $operand1 <= $operand2;
+ case '>':
+ return $operand1 > $operand2;
+ case '<':
+ return $operand1 < $operand2;
+ case '&&':
+ return $operand1 && $operand2;
+ case '||':
+ return $operand1 || $operand2;
+ case '+':
+ return $operand1 + $operand2;
+ case '-':
+ return $operand1 - $operand2;
+ case '*':
+ return $operand1 * $operand2;
+ case '/':
+ return (int)($operand1 / $operand2);
+ case '%':
+ return $operand1 % $operand2;
+ case '?':
+ return $operand1 ? $operand2 : $operand3;
+ default:
+ throw new Exception('Invalid expression');
+ }
+ }
+}
+
+/**
+ * A simple operator-precedence parser for gettext plural expressions. Takes a
+ * string during construction and returns a PluralsExpression tree when
+ * parse() is called.
+ */
+class PluralsParser {
+ private $lexer;
+
+ /*
+ * Operator precedence. The parsing only happens with minimum precedence of
+ * 0. However, ':' and ')' exist here to make sure that parsing does not
+ * proceed beyond them when they are not to be parsed.
+ */
+ const PREC = [
+ ':' => -1, '?' => 0, '||' => 1, '&&' => 2, '==' => 3, '!=' => 3,
+ '>' => 4, '<' => 4, '>=' => 4, '<=' => 4, '+' => 5, '-' => 5, '*' => 6,
+ '/' => 6, '%' => 6, '!' => 7, '__END__' => -1, ')' => -1
+ ];
+
+ // List of right associative operators
+ const RIGHT_ASSOC = ['?'];
+
+ /**
+ * Constructor
+ *
+ * @param string string the plural expression to be parsed.
+ */
+ public function __construct(string $string) {
+ $this->lexer = new PluralsLexer($string);
+ }
+
+ /**
+ * Expect a primary next for parsing and return a PluralsExpression or throw
+ * and exception otherwise. A primary can be the variable 'n', an whole
+ * number constant, a unary operator expression string with '!', or a
+ * parenthesis expression.
+ *
+ * @throws Exception If the next token is not a primary or if parenthesis
+ * expression is not closes properly with ')'.
+ * @return PluralsExpression That is constructed from the parsed primary.
+ */
+ private function _parse_primary() {
+ $token = $this->lexer->fetch_token();
+ if ($token === 'n') {
+ return new PluralsExpression('var', 'n');
+ } elseif (is_int($token)) {
+ return new PluralsExpression('const', (int)$token);
+ } elseif ($token === '!') {
+ return new PluralsExpression('!', $this->_parse_primary());
+ } elseif ($token === '(') {
+ $result = $this->_parse($this->_parse_primary(), 0);
+ if ($this->lexer->fetch_token() != ')') {
+ throw new Exception('Mismatched parenthesis');
+ }
+ return $result;
+ }
+
+ throw new Exception('Primary expected');
+ }
+
+ /**
+ * Fetch an operator from the lexical analyzer and test for it. Optionally
+ * advance the position of the lexical analyzer to next token. Raise
+ * exception if the token retrieved is not an operator.
+ *
+ * @access private
+ * @param bool peek A flag to indicate whether the position of the lexical
+ * analyzer should *not* be advanced. If false, the lexical analyzer is
+ * advanced by one token.
+ * @throws Exception If the token read is not an operator.
+ * @return string The operator that has been fetched from the lexical
+ * analyzer.
+ */
+ private function _parse_operator($peek) {
+ if ($peek) {
+ $token = $this->lexer->peek();
+ } else {
+ $token = $this->lexer->fetch_token();
+ }
+
+ if ($token !== null && !array_key_exists($token, self::PREC)) {
+ throw new Exception('Operator expected');
+ }
+ return $token;
+ }
+
+ /**
+ * A parsing method suitable for recursion.
+ *
+ * @access private
+ * @param ParserExpression left_side A pre-parsed left-hand side expression
+ * of the file expression to be constructed. This helps with recursion.
+ * @param int min_precedence The minimum value of precedence for the
+ * operators to be considered for parsing. Parsing will stop and current
+ * expression is returned if an operator of a lower precedence is
+ * encountered.
+ * @throws Exception If the input string does not conform to the grammar of
+ * the gettext plural expression.
+ * @return ParserExpression A complete expression after parsing.
+ */
+ private function _parse($left_side, $min_precedence) {
+ $next_token = $this->_parse_operator(true);
+
+ while (self::PREC[$next_token] >= $min_precedence) {
+ $operator = $this->_parse_operator(false);
+ $right_side = $this->_parse_primary();
+
+ $next_token = $this->_parse_operator(true);
+
+ /*
+ * Consume (recursively) into right hand side all expressions of higher
+ * precedence.
+ */
+ while ((self::PREC[$operator] < self::PREC[$next_token]) ||
+ ((self::PREC[$operator] == self::PREC[$next_token]) &&
+ in_array($operator, self::RIGHT_ASSOC))) {
+ $right_side = $this->_parse(
+ $right_side, self::PREC[$next_token]);
+ $next_token = $this->_parse_operator(true);
+ }
+
+ if ($operator != '?') {
+ /*
+ * Handling for all binary operators. Consume into left hand side all
+ * expressions of equal precedence.
+ */
+ $left_side = new PluralsExpression($operator, $left_side, $right_side);
+ } else {
+ // Special handling for (a ? b : c) expression
+ $operator = $this->lexer->fetch_token();
+ if ($operator != ':') {
+ throw new Exception('Invalid ? expression');
+ }
+
+ $right_side2 = $this->_parse(
+ $this->_parse_primary(), self::PREC[$operator] + 1);
+ $next_token = $this->_parse_operator(true);
+ $left_side = new PluralsExpression(
+ '?', $left_side, $right_side, $right_side2);
+ }
+ }
+ return $left_side;
+ }
+
+ /**
+ * A simple implementation of an operator-precedence parser. See:
+ * https://en.wikipedia.org/wiki/Operator-precedence_parser for an analysis
+ * of the algorithm.
+ *
+ * @throws Exception If the input string does not conform to the grammar of
+ * the gettext plural expression.
+ * @return ParserExpression A complete expression after parsing.
+ */
+ public function parse() {
+ $expression = $this->_parse($this->_parse_primary(), 0);
+ // Special handling for an extra ')' at the end.
+ if ($this->lexer->peek() != '__END__') {
+ throw new Exception('Could not parse completely');
+ }
+ return $expression;
+ }
+}
+
+/**
+ * Provides a class to parse the value of the 'Plural-Forms:' header in the
+ * gettext translation files. Holds the expression tree and the number of
+ * plurals after parsing. Parsing happens during construction which takes as
+ * its only argument the string to parse. Error during parsing are silently
+ * suppressed and the fallback behavior is used with the value for Germanic
+ * languages as follows: "nplurals=2; plural=n == 1 ? 0 : 1;".
+ */
+class PluralHeader {
+ public $total;
+ public $expression;
+
+ /**
+ * Constructor
+ *
+ * @param string The value of the Plural-Forms: header as seen in .po files.
+ */
+ function __construct($string) {
+ try {
+ list($total, $expression) = $this->parse($string);
+ } catch (Exception $e) {
+ $string = "nplurals=2; plural=n == 1 ? 0 : 1;";
+ list($total, $expression) = $this->parse($string);
+ }
+ $this->total = $total;
+ $this->expression = $expression;
+ }
+
+ /**
+ * Return the number of plural forms and the parsed expression tree.
+ *
+ * @access private
+ * @param string string The value of the Plural-Forms: header.
+ * @throws Exception If the string could not be parsed.
+ * @return array The number of plural forms and parsed expression tree.
+ */
+ private function parse($string) {
+ $regex = "/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural\s*=([^;]+);/i";
+ if (preg_match($regex, $string, $matches)) {
+ $total = (int)$matches[1];
+ $expression_string = $matches[2];
+ } else {
+ throw new Exception('Invalid header value');
+ }
+
+ $parser = new PluralsParser($expression_string);
+ $expression = $parser->parse();
+ return [$total, $expression];
+ }
+}