]> git.wh0rd.org Git - tt-rss.git/blob - lib/MiniTemplator.class.php
pluginhost: use PDO
[tt-rss.git] / lib / MiniTemplator.class.php
1 <?php\r
2 /**\r
3 * File MiniTemplator.class.php\r
4 * @package MiniTemplator\r
5 */\r
6 \r
7 /**\r
8 * A compact template engine for HTML files.\r
9 *\r
10 * Requires PHP 4.0.4 or newer.\r
11 *\r
12 * <pre>\r
13 * Template syntax:\r
14 *\r
15 *   Variables:\r
16 *     ${VariableName}\r
17 *\r
18 *   Blocks:\r
19 *     &lt;!-- $BeginBlock BlockName --&gt;\r
20 *     ... block content ...\r
21 *     &lt;!-- $EndBlock BlockName --&gt;\r
22 *\r
23 *   Include a subtemplate:\r
24 *     &lt;!-- $Include RelativeFileName --&gt;\r
25 * </pre>\r
26 *\r
27 * <pre>\r
28 * General remarks:\r
29 *  - Variable names and block names are case-insensitive.\r
30 *  - The same variable may be used multiple times within a template.\r
31 *  - Blocks can be nested.\r
32 *  - Multiple blocks with the same name may occur within a template.\r
33 * </pre>\r
34 *\r
35 * <pre>\r
36 * Public methods:\r
37 *   readTemplateFromFile   - Reads the template from a file.\r
38 *   setTemplateString      - Assigns a new template string.\r
39 *   setVariable            - Sets a template variable.\r
40 *   setVariableEsc         - Sets a template variable to an escaped string value.\r
41 *   variableExists         - Checks whether a template variable exists.\r
42 *   addBlock               - Adds an instance of a template block.\r
43 *   blockExists            - Checks whether a block exists.\r
44 *   reset                  - Clears all variables and blocks.\r
45 *   generateOutput         - Generates the HTML page and writes it to the PHP output stream.\r
46 *   generateOutputToFile   - Generates the HTML page and writes it to a file.\r
47 *   generateOutputToString - Generates the HTML page and writes it to a string.\r
48 * </pre>\r
49 *\r
50 * Home page: {@link http://www.source-code.biz/MiniTemplator}<br>\r
51 * License: This module is released under the GNU/LGPL license ({@link http://www.gnu.org/licenses/lgpl.html}).<br>\r
52 * Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland. All rights reserved.<br>\r
53 * This product is provided "as is" without warranty of any kind.<br>\r
54 *\r
55 * Version history:<br>\r
56 * 2001-10-24 Christian d'Heureuse (chdh): VBasic version created.<br>\r
57 * 2002-01-26 Markus Angst: ported to PHP4.<br>\r
58 * 2003-04-07 chdh: changes to adjust to Java version.<br>\r
59 * 2003-07-08 chdh: Method variableExists added.\r
60 *   Method setVariable changed to trigger an error when the variable does not exist.<br>\r
61 * 2004-04-07 chdh: Parameter isOptional added to method setVariable.\r
62 *   Licensing changed from GPL to LGPL.<br>\r
63 * 2004-04-18 chdh: Method blockExists added.<br>\r
64 * 2004-10-28 chdh:<br>\r
65 *   Method setVariableEsc added.<br>\r
66 *   Multiple blocks with the same name may now occur within a template.<br>\r
67 *   No error ("unknown command") is generated any more, if a HTML comment starts with "${".<br>\r
68 * 2004-11-06 chdh:<br>\r
69 *   "$Include" command implemented.<br>\r
70 * 2004-11-20 chdh:<br>\r
71 *   "$Include" command changed so that the command text is not copied to the output file.<br>\r
72 */\r
73 \r
74 class MiniTemplator {\r
75 \r
76 //--- public member variables ---------------------------------------------------------------------------------------\r
77 \r
78 /**\r
79 * Base path for relative file names of subtemplates (for the $Include command).\r
80 * This path is prepended to the subtemplate file names. It must be set before\r
81 * readTemplateFromFile or setTemplateString.\r
82 * @access public\r
83 */\r
84 var $subtemplateBasePath;\r
85 \r
86 //--- private member variables --------------------------------------------------------------------------------------\r
87 \r
88 /**#@+\r
89 * @access private\r
90 */\r
91 \r
92 var $maxNestingLevel = 50;            // maximum number of block nestings\r
93 var $maxInclTemplateSize = 1000000;   // maximum length of template string when including subtemplates\r
94 var $template;                        // Template file data\r
95 var $varTab;                          // variables table, array index is variable no\r
96     // Fields:\r
97     //  varName                       // variable name\r
98     //  varValue                      // variable value\r
99 var $varTabCnt;                       // no of entries used in VarTab\r
100 var $varNameToNoMap;                  // maps variable names to variable numbers\r
101 var $varRefTab;                       // variable references table\r
102     // Contains an entry for each variable reference in the template. Ordered by TemplatePos.\r
103     // Fields:\r
104     //  varNo                         // variable no\r
105     //  tPosBegin                     // template position of begin of variable reference\r
106     //  tPosEnd                       // template position of end of variable reference\r
107     //  blockNo                       // block no of the (innermost) block that contains this variable reference\r
108     //  blockVarNo                    // block variable no. Index into BlockInstTab.BlockVarTab\r
109 var $varRefTabCnt;                    // no of entries used in VarRefTab\r
110 var $blockTab;                        // Blocks table, array index is block no\r
111     // Contains an entry for each block in the template. Ordered by TPosBegin.\r
112     // Fields:\r
113     //  blockName                     // block name\r
114     //  nextWithSameName;             // block no of next block with same name or -1 (blocks are backward linked in relation to template position)\r
115     //  tPosBegin                     // template position of begin of block\r
116     //  tPosContentsBegin             // template pos of begin of block contents\r
117     //  tPosContentsEnd               // template pos of end of block contents\r
118     //  tPosEnd                       // template position of end of block\r
119     //  nestingLevel                  // block nesting level\r
120     //  parentBlockNo                 // block no of parent block\r
121     //  definitionIsOpen              // true while $BeginBlock processed but no $EndBlock\r
122     //  instances                     // number of instances of this block\r
123     //  firstBlockInstNo              // block instance no of first instance of this block or -1\r
124     //  lastBlockInstNo               // block instance no of last instance of this block or -1\r
125     //  currBlockInstNo               // current block instance no, used during generation of output file\r
126     //  blockVarCnt                   // no of variables in block\r
127     //  blockVarNoToVarNoMap          // maps block variable numbers to variable numbers\r
128     //  firstVarRefNo                 // variable reference no of first variable of this block or -1\r
129 var $blockTabCnt;                     // no of entries used in BlockTab\r
130 var $blockNameToNoMap;                // maps block names to block numbers\r
131 var $openBlocksTab;\r
132     // During parsing, this table contains the block numbers of the open parent blocks (nested outer blocks).\r
133     // Indexed by the block nesting level.\r
134 var $blockInstTab;                    // block instances table\r
135     // This table contains an entry for each block instance that has been added.\r
136     // Indexed by BlockInstNo.\r
137     // Fields:\r
138     //  blockNo                       // block number\r
139     //  instanceLevel                 // instance level of this block\r
140     //     InstanceLevel is an instance counter per block.\r
141     //     (In contrast to blockInstNo, which is an instance counter over the instances of all blocks)\r
142     //  parentInstLevel               // instance level of parent block\r
143     //  nextBlockInstNo               // pointer to next instance of this block or -1\r
144     //     Forward chain for instances of same block.\r
145     //  blockVarTab                   // block instance variables\r
146 var $blockInstTabCnt;                 // no of entries used in BlockInstTab\r
147 \r
148 var $currentNestingLevel;             // Current block nesting level during parsing.\r
149 var $templateValid;                   // true if a valid template is prepared\r
150 var $outputMode;                      // 0 = to PHP output stream, 1 = to file, 2 = to string\r
151 var $outputFileHandle;                // file handle during writing of output file\r
152 var $outputError;                     // true when an output error occurred\r
153 var $outputString;                    // string buffer for the generated HTML page\r
154 \r
155 /**#@-*/\r
156 \r
157 //--- constructor ---------------------------------------------------------------------------------------------------\r
158 \r
159 /**\r
160 * Constructs a MiniTemplator object.\r
161 * @access public\r
162 */\r
163 function __construct() {\r
164    $this->templateValid = false; }\r
165 \r
166 //--- template string handling --------------------------------------------------------------------------------------\r
167 \r
168 /**\r
169 * Reads the template from a file.\r
170 * @param  string   $fileName  name of the file that contains the template.\r
171 * @return boolean  true on success, false on error.\r
172 * @access public\r
173 */\r
174 function readTemplateFromFile ($fileName) {\r
175    if (!$this->readFileIntoString($fileName,$s)) {\r
176       $this->triggerError ("Error while reading template file " . $fileName . ".");\r
177       return false; }\r
178    if (!$this->setTemplateString($s)) return false;\r
179    return true; }\r
180 \r
181 /**\r
182 * Assigns a new template string.\r
183 * @param  string   $templateString  contents of the template file.\r
184 * @return boolean  true on success, false on error.\r
185 * @access public\r
186 */\r
187 function setTemplateString ($templateString) {\r
188    $this->templateValid = false;\r
189    $this->template = $templateString;\r
190    if (!$this->parseTemplate()) return false;\r
191    $this->reset();\r
192    $this->templateValid = true;\r
193    return true; }\r
194 \r
195 /**\r
196 * Loads the template string for a subtemplate (used for the $Include command).\r
197 * @return boolean  true on success, false on error.\r
198 * @access private\r
199 */\r
200 function loadSubtemplate ($subtemplateName, &$s) {\r
201    $subtemplateFileName = $this->combineFileSystemPath($this->subtemplateBasePath,$subtemplateName);\r
202    if (!$this->readFileIntoString($subtemplateFileName,$s)) {\r
203       $this->triggerError ("Error while reading subtemplate file " . $subtemplateFileName . ".");\r
204       return false; }\r
205    return true; }\r
206 \r
207 //--- template parsing ----------------------------------------------------------------------------------------------\r
208 \r
209 /**\r
210 * Parses the template.\r
211 * @return boolean  true on success, false on error.\r
212 * @access private\r
213 */\r
214 function parseTemplate() {\r
215    $this->initParsing();\r
216    $this->beginMainBlock();\r
217    if (!$this->parseTemplateCommands()) return false;\r
218    $this->endMainBlock();\r
219    if (!$this->checkBlockDefinitionsComplete()) return false;\r
220    if (!$this->parseTemplateVariables()) return false;\r
221    $this->associateVariablesWithBlocks();\r
222    return true; }\r
223 \r
224 /**\r
225 * @access private\r
226 */\r
227 function initParsing() {\r
228    $this->varTab = array();\r
229    $this->varTabCnt = 0;\r
230    $this->varNameToNoMap = array();\r
231    $this->varRefTab = array();\r
232    $this->varRefTabCnt = 0;\r
233    $this->blockTab = array();\r
234    $this->blockTabCnt = 0;\r
235    $this->blockNameToNoMap = array();\r
236    $this->openBlocksTab = array(); }\r
237 \r
238 /**\r
239 * Registers the main block.\r
240 * The main block is an implicitly defined block that covers the whole template.\r
241 * @access private\r
242 */\r
243 function beginMainBlock() {\r
244    $blockNo = 0;\r
245    $this->registerBlock('@@InternalMainBlock@@', $blockNo);\r
246    $bte =& $this->blockTab[$blockNo];\r
247    $bte['tPosBegin'] = 0;\r
248    $bte['tPosContentsBegin'] = 0;\r
249    $bte['nestingLevel'] = 0;\r
250    $bte['parentBlockNo'] = -1;\r
251    $bte['definitionIsOpen'] = true;\r
252    $this->openBlocksTab[0] = $blockNo;\r
253    $this->currentNestingLevel = 1; }\r
254 \r
255 /**\r
256 * Completes the main block registration.\r
257 * @access private\r
258 */\r
259 function endMainBlock() {\r
260    $bte =& $this->blockTab[0];\r
261    $bte['tPosContentsEnd'] = strlen($this->template);\r
262    $bte['tPosEnd'] = strlen($this->template);\r
263    $bte['definitionIsOpen'] = false;\r
264    $this->currentNestingLevel -= 1; }\r
265 \r
266 /**\r
267 * Parses commands within the template in the format "<!-- $command parameters -->".\r
268 * @return boolean  true on success, false on error.\r
269 * @access private\r
270 */\r
271 function parseTemplateCommands() {\r
272    $p = 0;\r
273    while (true) {\r
274       $p0 = strpos($this->template,'<!--',$p);\r
275       if ($p0 === false) break;\r
276       $p = strpos($this->template,'-->',$p0);\r
277       if ($p === false) {\r
278          $this->triggerError ("Invalid HTML comment in template at offset $p0.");\r
279          return false; }\r
280       $p += 3;\r
281       $cmdL = substr($this->template,$p0+4,$p-$p0-7);\r
282       if (!$this->processTemplateCommand($cmdL,$p0,$p,$resumeFromStart))\r
283          return false;\r
284       if ($resumeFromStart) $p = $p0; }\r
285    return true; }\r
286 \r
287 /**\r
288 * @return boolean  true on success, false on error.\r
289 * @access private\r
290 */\r
291 function processTemplateCommand ($cmdL, $cmdTPosBegin, $cmdTPosEnd, &$resumeFromStart) {\r
292    $resumeFromStart = false;\r
293    $p = 0;\r
294    $cmd = '';\r
295    if (!$this->parseWord($cmdL,$p,$cmd)) return true;\r
296    $parms = substr($cmdL,$p);\r
297    switch (strtoupper($cmd)) {\r
298       case '$BEGINBLOCK':\r
299          if (!$this->processBeginBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd))\r
300             return false;\r
301          break;\r
302       case '$ENDBLOCK':\r
303          if (!$this->processEndBlockCmd($parms,$cmdTPosBegin,$cmdTPosEnd))\r
304             return false;\r
305          break;\r
306       case '$INCLUDE':\r
307          if (!$this->processincludeCmd($parms,$cmdTPosBegin,$cmdTPosEnd))\r
308             return false;\r
309          $resumeFromStart = true;\r
310          break;\r
311       default:\r
312          if ($cmd{0} == '$' && !(strlen($cmd) >= 2 && $cmd{1} == '{')) {\r
313             $this->triggerError ("Unknown command \"$cmd\" in template at offset $cmdTPosBegin.");\r
314             return false; }}\r
315     return true; }\r
316 \r
317 /**\r
318 * Processes the $BeginBlock command.\r
319 * @return boolean  true on success, false on error.\r
320 * @access private\r
321 */\r
322 function processBeginBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) {\r
323    $p = 0;\r
324    if (!$this->parseWord($parms,$p,$blockName)) {\r
325       $this->triggerError ("Missing block name in \$BeginBlock command in template at offset $cmdTPosBegin.");\r
326       return false; }\r
327    if (trim(substr($parms,$p)) != '') {\r
328       $this->triggerError ("Extra parameter in \$BeginBlock command in template at offset $cmdTPosBegin.");\r
329       return false; }\r
330    $this->registerBlock ($blockName, $blockNo);\r
331    $btr =& $this->blockTab[$blockNo];\r
332    $btr['tPosBegin'] = $cmdTPosBegin;\r
333    $btr['tPosContentsBegin'] = $cmdTPosEnd;\r
334    $btr['nestingLevel'] = $this->currentNestingLevel;\r
335    $btr['parentBlockNo'] = $this->openBlocksTab[$this->currentNestingLevel-1];\r
336    $this->openBlocksTab[$this->currentNestingLevel] = $blockNo;\r
337    $this->currentNestingLevel += 1;\r
338    if ($this->currentNestingLevel > $this->maxNestingLevel) {\r
339       $this->triggerError ("Block nesting overflow in template at offset $cmdTPosBegin.");\r
340       return false; }\r
341    return true; }\r
342 \r
343 /**\r
344 * Processes the $EndBlock command.\r
345 * @return boolean  true on success, false on error.\r
346 * @access private\r
347 */\r
348 function processEndBlockCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) {\r
349    $p = 0;\r
350    if (!$this->parseWord($parms,$p,$blockName)) {\r
351       $this->triggerError ("Missing block name in \$EndBlock command in template at offset $cmdTPosBegin.");\r
352       return false; }\r
353    if (trim(substr($parms,$p)) != '') {\r
354       $this->triggerError ("Extra parameter in \$EndBlock command in template at offset $cmdTPosBegin.");\r
355       return false; }\r
356    if (!$this->lookupBlockName($blockName,$blockNo)) {\r
357       $this->triggerError ("Undefined block name \"$blockName\" in \$EndBlock command in template at offset $cmdTPosBegin.");\r
358       return false; }\r
359    $this->currentNestingLevel -= 1;\r
360    $btr =& $this->blockTab[$blockNo];\r
361    if (!$btr['definitionIsOpen']) {\r
362       $this->triggerError ("Multiple \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin.");\r
363       return false; }\r
364    if ($btr['nestingLevel'] != $this->currentNestingLevel) {\r
365       $this->triggerError ("Block nesting level mismatch at \$EndBlock command for block \"$blockName\" in template at offset $cmdTPosBegin.");\r
366       return false; }\r
367    $btr['tPosContentsEnd'] = $cmdTPosBegin;\r
368    $btr['tPosEnd'] = $cmdTPosEnd;\r
369    $btr['definitionIsOpen'] = false;\r
370    return true; }\r
371 \r
372 /**\r
373 * @access private\r
374 */\r
375 function registerBlock($blockName, &$blockNo) {\r
376    $blockNo = $this->blockTabCnt++;\r
377    $btr =& $this->blockTab[$blockNo];\r
378    $btr = array();\r
379    $btr['blockName'] = $blockName;\r
380    if (!$this->lookupBlockName($blockName,$btr['nextWithSameName']))\r
381       $btr['nextWithSameName'] = -1;\r
382    $btr['definitionIsOpen'] = true;\r
383    $btr['instances'] = 0;\r
384    $btr['firstBlockInstNo'] = -1;\r
385    $btr['lastBlockInstNo'] = -1;\r
386    $btr['blockVarCnt'] = 0;\r
387    $btr['firstVarRefNo'] = -1;\r
388    $btr['blockVarNoToVarNoMap'] = array();\r
389    $this->blockNameToNoMap[strtoupper($blockName)] = $blockNo; }\r
390 \r
391 /**\r
392 * Checks that all block definitions are closed.\r
393 * @return boolean  true on success, false on error.\r
394 * @access private\r
395 */\r
396 function checkBlockDefinitionsComplete() {\r
397    for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) {\r
398       $btr =& $this->blockTab[$blockNo];\r
399       if ($btr['definitionIsOpen']) {\r
400          $this->triggerError ("Missing \$EndBlock command in template for block " . $btr['blockName'] . ".");\r
401          return false; }}\r
402    if ($this->currentNestingLevel != 0) {\r
403       $this->triggerError ("Block nesting level error at end of template.");\r
404       return false; }\r
405    return true; }\r
406 \r
407 /**\r
408 * Processes the $Include command.\r
409 * @return boolean  true on success, false on error.\r
410 * @access private\r
411 */\r
412 function processIncludeCmd ($parms, $cmdTPosBegin, $cmdTPosEnd) {\r
413    $p = 0;\r
414    if (!$this->parseWordOrQuotedString($parms,$p,$subtemplateName)) {\r
415       $this->triggerError ("Missing or invalid subtemplate name in \$Include command in template at offset $cmdTPosBegin.");\r
416       return false; }\r
417    if (trim(substr($parms,$p)) != '') {\r
418       $this->triggerError ("Extra parameter in \$include command in template at offset $cmdTPosBegin.");\r
419       return false; }\r
420    return $this->insertSubtemplate($subtemplateName,$cmdTPosBegin,$cmdTPosEnd); }\r
421 \r
422 /**\r
423 * Processes the $Include command.\r
424 * @return boolean  true on success, false on error.\r
425 * @access private\r
426 */\r
427 function insertSubtemplate ($subtemplateName, $tPos1, $tPos2) {\r
428    if (strlen($this->template) > $this->maxInclTemplateSize) {\r
429       $this->triggerError ("Subtemplate include aborted because the internal template string is longer than $this->maxInclTemplateSize characters.");\r
430       return false; }\r
431    if (!$this->loadSubtemplate($subtemplateName,$subtemplate)) return false;\r
432    // (Copying the template to insert a subtemplate is a bit slow. In a future implementation of MiniTemplator,\r
433    // a table could be used that contains references to the string fragments.)\r
434    $this->template = substr($this->template,0,$tPos1) . $subtemplate . substr($this->template,$tPos2);\r
435    return true; }\r
436 \r
437 /**\r
438 * Parses variable references within the template in the format "${VarName}".\r
439 * @return boolean  true on success, false on error.\r
440 * @access private\r
441 */\r
442 function parseTemplateVariables() {\r
443    $p = 0;\r
444    while (true) {\r
445       $p = strpos($this->template, '${', $p);\r
446       if ($p === false) break;\r
447       $p0 = $p;\r
448       $p = strpos($this->template, '}', $p);\r
449       if ($p === false) {\r
450          $this->triggerError ("Invalid variable reference in template at offset $p0.");\r
451          return false; }\r
452       $p += 1;\r
453       $varName = trim(substr($this->template, $p0+2, $p-$p0-3));\r
454       if (strlen($varName) == 0) {\r
455          $this->triggerError ("Empty variable name in template at offset $p0.");\r
456          return false; }\r
457       $this->registerVariableReference ($varName, $p0, $p); }\r
458    return true; }\r
459 \r
460 /**\r
461 * @access private\r
462 */\r
463 function registerVariableReference ($varName, $tPosBegin, $tPosEnd) {\r
464    if (!$this->lookupVariableName($varName,$varNo))\r
465       $this->registerVariable($varName,$varNo);\r
466    $varRefNo = $this->varRefTabCnt++;\r
467    $vrtr =& $this->varRefTab[$varRefNo];\r
468    $vrtr = array();\r
469    $vrtr['tPosBegin'] = $tPosBegin;\r
470    $vrtr['tPosEnd'] = $tPosEnd;\r
471    $vrtr['varNo'] = $varNo; }\r
472 \r
473 /**\r
474 * @access private\r
475 */\r
476 function registerVariable ($varName, &$varNo) {\r
477    $varNo = $this->varTabCnt++;\r
478    $vtr =& $this->varTab[$varNo];\r
479    $vtr = array();\r
480    $vtr['varName'] = $varName;\r
481    $vtr['varValue'] = '';\r
482    $this->varNameToNoMap[strtoupper($varName)] = $varNo; }\r
483 \r
484 /**\r
485 * Associates variable references with blocks.\r
486 * @access private\r
487 */\r
488 function associateVariablesWithBlocks() {\r
489    $varRefNo = 0;\r
490    $activeBlockNo = 0;\r
491    $nextBlockNo = 1;\r
492    while ($varRefNo < $this->varRefTabCnt) {\r
493       $vrtr =& $this->varRefTab[$varRefNo];\r
494       $varRefTPos = $vrtr['tPosBegin'];\r
495       $varNo = $vrtr['varNo'];\r
496       if ($varRefTPos >= $this->blockTab[$activeBlockNo]['tPosEnd']) {\r
497          $activeBlockNo = $this->blockTab[$activeBlockNo]['parentBlockNo'];\r
498          continue; }\r
499       if ($nextBlockNo < $this->blockTabCnt) {\r
500          if ($varRefTPos >= $this->blockTab[$nextBlockNo]['tPosBegin']) {\r
501             $activeBlockNo = $nextBlockNo;\r
502             $nextBlockNo += 1;\r
503             continue; }}\r
504       $btr =& $this->blockTab[$activeBlockNo];\r
505       if ($varRefTPos < $btr['tPosBegin'])\r
506          $this->programLogicError(1);\r
507       $blockVarNo = $btr['blockVarCnt']++;\r
508       $btr['blockVarNoToVarNoMap'][$blockVarNo] = $varNo;\r
509       if ($btr['firstVarRefNo'] == -1)\r
510          $btr['firstVarRefNo'] = $varRefNo;\r
511       $vrtr['blockNo'] = $activeBlockNo;\r
512       $vrtr['blockVarNo'] = $blockVarNo;\r
513       $varRefNo += 1; }}\r
514 \r
515 //--- build up (template variables and blocks) ----------------------------------------------------------------------\r
516 \r
517 /**\r
518 * Clears all variables and blocks.\r
519 * This method can be used to produce another HTML page with the same\r
520 * template. It is faster than creating another MiniTemplator object,\r
521 * because the template does not have to be parsed again.\r
522 * All variable values are cleared and all added block instances are deleted.\r
523 * @access public\r
524 */\r
525 function reset() {\r
526    for ($varNo=0; $varNo<$this->varTabCnt; $varNo++)\r
527       $this->varTab[$varNo]['varValue'] = '';\r
528    for ($blockNo=0; $blockNo<$this->blockTabCnt; $blockNo++) {\r
529       $btr =& $this->blockTab[$blockNo];\r
530       $btr['instances'] = 0;\r
531       $btr['firstBlockInstNo'] = -1;\r
532       $btr['lastBlockInstNo'] = -1; }\r
533    $this->blockInstTab = array();\r
534    $this->blockInstTabCnt = 0; }\r
535 \r
536 /**\r
537 * Sets a template variable.\r
538 * For variables that are used in blocks, the variable value\r
539 * must be set before {@link addBlock} is called.\r
540 * @param  string  $variableName   the name of the variable to be set.\r
541 * @param  string  $variableValue  the new value of the variable.\r
542 * @param  boolean $isOptional     Specifies whether an error should be\r
543 *    generated when the variable does not exist in the template. If\r
544 *    $isOptional is false and the variable does not exist, an error is\r
545 *    generated.\r
546 * @return boolean true on success, or false on error (e.g. when no\r
547 *    variable with the specified name exists in the template and\r
548 *    $isOptional is false).\r
549 * @access public\r
550 */\r
551 function setVariable ($variableName, $variableValue, $isOptional=false) {\r
552    if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; }\r
553    if (!$this->lookupVariableName($variableName,$varNo)) {\r
554       if ($isOptional) return true;\r
555       $this->triggerError ("Variable \"$variableName\" not defined in template.");\r
556       return false; }\r
557    $this->varTab[$varNo]['varValue'] = $variableValue;\r
558    return true; }\r
559 \r
560 /**\r
561 * Sets a template variable to an escaped string.\r
562 * This method is identical to (@link setVariable), except that\r
563 * the characters &lt;, &gt;, &amp;, ' and " of variableValue are\r
564 * replaced by their corresponding HTML/XML character entity codes.\r
565 * For variables that are used in blocks, the variable value\r
566 * must be set before {@link addBlock} is called.\r
567 * @param  string  $variableName   the name of the variable to be set.\r
568 * @param  string  $variableValue  the new value of the variable. Special HTML/XML characters are escaped.\r
569 * @param  boolean $isOptional     Specifies whether an error should be\r
570 *    generated when the variable does not exist in the template. If\r
571 *    $isOptional is false and the variable does not exist, an error is\r
572 *    generated.\r
573 * @return boolean true on success, or false on error (e.g. when no\r
574 *    variable with the specified name exists in the template and\r
575 *    $isOptional is false).\r
576 * @access public\r
577 */\r
578 function setVariableEsc ($variableName, $variableValue, $isOptional=false) {\r
579    return $this->setVariable($variableName,htmlspecialchars($variableValue,ENT_QUOTES),$isOptional); }\r
580 \r
581 /**\r
582 * Checks whether a variable with the specified name exists within the template.\r
583 * @param  string  $variableName   the name of the variable.\r
584 * @return boolean true if the variable exists, or false when no\r
585 *    variable with the specified name exists in the template.\r
586 * @access public\r
587 */\r
588 function variableExists ($variableName) {\r
589    if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; }\r
590    return $this->lookupVariableName($variableName,$varNo); }\r
591 \r
592 /**\r
593 * Adds an instance of a template block.\r
594 * If the block contains variables, these variables must be set\r
595 * before the block is added.\r
596 * If the block contains subblocks (nested blocks), the subblocks\r
597 * must be added before this block is added.\r
598 * If multiple blocks exist with the specified name, an instance\r
599 * is added for each block occurence.\r
600 * @param  string   blockName the name of the block to be added.\r
601 * @return boolean  true on success, false on error (e.g. when no\r
602 *    block with the specified name exists in the template).\r
603 * @access public\r
604 */\r
605 function addBlock($blockName) {\r
606    if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; }\r
607    if (!$this->lookupBlockName($blockName,$blockNo)) {\r
608       $this->triggerError ("Block \"$blockName\" not defined in template.");\r
609       return false; }\r
610    while ($blockNo != -1) {\r
611       $this->addBlockByNo($blockNo);\r
612       $blockNo = $this->blockTab[$blockNo]['nextWithSameName']; }\r
613    return true; }\r
614 \r
615 /**\r
616 * @access private\r
617 */\r
618 function addBlockByNo ($blockNo) {\r
619    $btr =& $this->blockTab[$blockNo];\r
620    $this->registerBlockInstance ($blockInstNo);\r
621    $bitr =& $this->blockInstTab[$blockInstNo];\r
622    if ($btr['firstBlockInstNo'] == -1)\r
623       $btr['firstBlockInstNo'] = $blockInstNo;\r
624    if ($btr['lastBlockInstNo'] != -1)\r
625       $this->blockInstTab[$btr['lastBlockInstNo']]['nextBlockInstNo'] = $blockInstNo;\r
626          // set forward pointer of chain\r
627    $btr['lastBlockInstNo'] = $blockInstNo;\r
628    $parentBlockNo = $btr['parentBlockNo'];\r
629    $blockVarCnt = $btr['blockVarCnt'];\r
630    $bitr['blockNo'] = $blockNo;\r
631    $bitr['instanceLevel'] = $btr['instances']++;\r
632    if ($parentBlockNo == -1)\r
633       $bitr['parentInstLevel'] = -1;\r
634     else\r
635       $bitr['parentInstLevel'] = $this->blockTab[$parentBlockNo]['instances'];\r
636    $bitr['nextBlockInstNo'] = -1;\r
637    $bitr['blockVarTab'] = array();\r
638    // copy instance variables for this block\r
639    for ($blockVarNo=0; $blockVarNo<$blockVarCnt; $blockVarNo++) {\r
640       $varNo = $btr['blockVarNoToVarNoMap'][$blockVarNo];\r
641       $bitr['blockVarTab'][$blockVarNo] = $this->varTab[$varNo]['varValue']; }}\r
642 \r
643 /**\r
644 * @access private\r
645 */\r
646 function registerBlockInstance (&$blockInstNo) {\r
647    $blockInstNo = $this->blockInstTabCnt++; }\r
648 \r
649 /**\r
650 * Checks whether a block with the specified name exists within the template.\r
651 * @param  string  $blockName   the name of the block.\r
652 * @return boolean true if the block exists, or false when no\r
653 *    block with the specified name exists in the template.\r
654 * @access public\r
655 */\r
656 function blockExists ($blockName) {\r
657    if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; }\r
658    return $this->lookupBlockName($blockName,$blockNo); }\r
659 \r
660 //--- output generation ---------------------------------------------------------------------------------------------\r
661 \r
662 /**\r
663 * Generates the HTML page and writes it to the PHP output stream.\r
664 * @return boolean  true on success, false on error.\r
665 * @access public\r
666 */\r
667 function generateOutput () {\r
668    $this->outputMode = 0;\r
669    if (!$this->generateOutputPage()) return false;\r
670    return true; }\r
671 \r
672 /**\r
673 * Generates the HTML page and writes it to a file.\r
674 * @param  string   $fileName  name of the output file.\r
675 * @return boolean  true on success, false on error.\r
676 * @access public\r
677 */\r
678 function generateOutputToFile ($fileName) {\r
679    $fh = fopen($fileName,"wb");\r
680    if ($fh === false) return false;\r
681    $this->outputMode = 1;\r
682    $this->outputFileHandle = $fh;\r
683    $ok = $this->generateOutputPage();\r
684    fclose ($fh);\r
685    return $ok; }\r
686 \r
687 /**\r
688 * Generates the HTML page and writes it to a string.\r
689 * @param  string   $outputString  variable that receives\r
690 *                  the contents of the generated HTML page.\r
691 * @return boolean  true on success, false on error.\r
692 * @access public\r
693 */\r
694 function generateOutputToString (&$outputString) {\r
695    $outputString = "Error";\r
696    $this->outputMode = 2;\r
697    $this->outputString = "";\r
698    if (!$this->generateOutputPage()) return false;\r
699    $outputString = $this->outputString;\r
700    return true; }\r
701 \r
702 /**\r
703 * @access private\r
704 * @return boolean  true on success, false on error.\r
705 */\r
706 function generateOutputPage() {\r
707    if (!$this->templateValid) {$this->triggerError ("Template not valid."); return false; }\r
708    if ($this->blockTab[0]['instances'] == 0)\r
709       $this->addBlockByNo (0);        // add main block\r
710    for ($blockNo=0; $blockNo < $this->blockTabCnt; $blockNo++) {\r
711        $btr =& $this->blockTab[$blockNo];\r
712        $btr['currBlockInstNo'] = $btr['firstBlockInstNo']; }\r
713    $this->outputError = false;\r
714    $this->writeBlockInstances (0, -1);\r
715    if ($this->outputError) return false;\r
716    return true; }\r
717 \r
718 /**\r
719 * Writes all instances of a block that are contained within a specific\r
720 * parent block instance.\r
721 * Called recursively.\r
722 * @access private\r
723 */\r
724 function writeBlockInstances ($blockNo, $parentInstLevel) {\r
725    $btr =& $this->blockTab[$blockNo];\r
726    while (!$this->outputError) {\r
727       $blockInstNo = $btr['currBlockInstNo'];\r
728       if ($blockInstNo == -1) break;\r
729       $bitr =& $this->blockInstTab[$blockInstNo];\r
730       if ($bitr['parentInstLevel'] < $parentInstLevel)\r
731          $this->programLogicError (2);\r
732       if ($bitr['parentInstLevel'] > $parentInstLevel) break;\r
733       $this->writeBlockInstance ($blockInstNo);\r
734       $btr['currBlockInstNo'] = $bitr['nextBlockInstNo']; }}\r
735 \r
736 /**\r
737 * @access private\r
738 */\r
739 function writeBlockInstance($blockInstNo) {\r
740    $bitr =& $this->blockInstTab[$blockInstNo];\r
741    $blockNo = $bitr['blockNo'];\r
742    $btr =& $this->blockTab[$blockNo];\r
743    $tPos = $btr['tPosContentsBegin'];\r
744    $subBlockNo = $blockNo + 1;\r
745    $varRefNo = $btr['firstVarRefNo'];\r
746    while (!$this->outputError) {\r
747       $tPos2 = $btr['tPosContentsEnd'];\r
748       $kind = 0;                                // assume end-of-block\r
749       if ($varRefNo != -1 && $varRefNo < $this->varRefTabCnt) {  // check for variable reference\r
750          $vrtr =& $this->varRefTab[$varRefNo];\r
751          if ($vrtr['tPosBegin'] < $tPos) {\r
752             $varRefNo += 1;\r
753             continue; }\r
754          if ($vrtr['tPosBegin'] < $tPos2) {\r
755             $tPos2 = $vrtr['tPosBegin'];\r
756             $kind = 1; }}\r
757       if ($subBlockNo < $this->blockTabCnt) {   // check for subblock\r
758          $subBtr =& $this->blockTab[$subBlockNo];\r
759          if ($subBtr['tPosBegin'] < $tPos) {\r
760             $subBlockNo += 1;\r
761             continue; }\r
762          if ($subBtr['tPosBegin'] < $tPos2) {\r
763             $tPos2 = $subBtr['tPosBegin'];\r
764             $kind = 2; }}\r
765       if ($tPos2 > $tPos)\r
766          $this->writeString (substr($this->template,$tPos,$tPos2-$tPos));\r
767       switch ($kind) {\r
768          case 0:         // end of block\r
769             return;\r
770          case 1:         // variable\r
771             $vrtr =& $this->varRefTab[$varRefNo];\r
772             if ($vrtr['blockNo'] != $blockNo)\r
773                $this->programLogicError (4);\r
774             $variableValue = $bitr['blockVarTab'][$vrtr['blockVarNo']];\r
775             $this->writeString ($variableValue);\r
776             $tPos = $vrtr['tPosEnd'];\r
777             $varRefNo += 1;\r
778             break;\r
779          case 2:         // sub block\r
780             $subBtr =& $this->blockTab[$subBlockNo];\r
781             if ($subBtr['parentBlockNo'] != $blockNo)\r
782                $this->programLogicError (3);\r
783             $this->writeBlockInstances ($subBlockNo, $bitr['instanceLevel']);  // recursive call\r
784             $tPos = $subBtr['tPosEnd'];\r
785             $subBlockNo += 1;\r
786             break; }}}\r
787 \r
788 /**\r
789 * @access private\r
790 */\r
791 function writeString ($s) {\r
792    if ($this->outputError) return;\r
793    switch ($this->outputMode) {\r
794       case 0:            // output to PHP output stream\r
795          if (!print($s))\r
796             $this->outputError = true;\r
797          break;\r
798       case 1:            // output to file\r
799          $rc = fwrite($this->outputFileHandle, $s);\r
800          if ($rc === false) $this->outputError = true;\r
801          break;\r
802       case 2:            // output to string\r
803          $this->outputString .= $s;\r
804          break; }}\r
805 \r
806 //--- name lookup routines ------------------------------------------------------------------------------------------\r
807 \r
808 /**\r
809 * Maps variable name to variable number.\r
810 * @return boolean  true on success, false if the variable is not found.\r
811 * @access private\r
812 */\r
813 function lookupVariableName ($varName, &$varNo) {\r
814    $x =& $this->varNameToNoMap[strtoupper($varName)];\r
815    if (!isset($x)) return false;\r
816    $varNo = $x;\r
817    return true; }\r
818 \r
819 /**\r
820 * Maps block name to block number.\r
821 * If there are multiple blocks with the same name, the block number of the last\r
822 * registered block with that name is returned.\r
823 * @return boolean  true on success, false when the block is not found.\r
824 * @access private\r
825 */\r
826 function lookupBlockName ($blockName, &$blockNo) {\r
827    $x =& $this->blockNameToNoMap[strtoupper($blockName)];\r
828    if (!isset($x)) return false;\r
829    $blockNo = $x;\r
830    return true; }\r
831 \r
832 //--- general utility routines -----------------------------------------------------------------------------------------\r
833 \r
834 /**\r
835 * Reads a file into a string.\r
836 * @return boolean  true on success, false on error.\r
837 * @access private\r
838 */\r
839 function readFileIntoString ($fileName, &$s) {\r
840    if (function_exists('version_compare') && version_compare(phpversion(),"4.3.0",">=")) {\r
841       $s = file_get_contents($fileName);\r
842       if ($s === false) return false;\r
843       return true; }\r
844    $fh = fopen($fileName,"rb");\r
845    if ($fh === false) return false;\r
846    $fileSize = filesize($fileName);\r
847    if ($fileSize === false) {fclose ($fh); return false; }\r
848    $s = fread($fh,$fileSize);\r
849    fclose ($fh);\r
850    if (strlen($s) != $fileSize) return false;\r
851    return true; }\r
852 \r
853 /**\r
854 * @access private\r
855 * @return boolean  true on success, false when the end of the string is reached.\r
856 */\r
857 function parseWord ($s, &$p, &$w) {\r
858    $sLen = strlen($s);\r
859    while ($p < $sLen && ord($s{$p}) <= 32) $p++;\r
860    if ($p >= $sLen) return false;\r
861    $p0 = $p;\r
862    while ($p < $sLen && ord($s{$p}) > 32) $p++;\r
863    $w = substr($s, $p0, $p - $p0);\r
864    return true; }\r
865 \r
866 /**\r
867 * @access private\r
868 * @return boolean  true on success, false on error.\r
869 */\r
870 function parseQuotedString ($s, &$p, &$w) {\r
871    $sLen = strlen($s);\r
872    while ($p < $sLen && ord($s{$p}) <= 32) $p++;\r
873    if ($p >= $sLen) return false;\r
874    if (substr($s,$p,1) != '"') return false;\r
875    $p++; $p0 = $p;\r
876    while ($p < $sLen && $s{$p} != '"') $p++;\r
877    if ($p >= $sLen) return false;\r
878    $w = substr($s, $p0, $p - $p0);\r
879    $p++;\r
880    return true; }\r
881 \r
882 /**\r
883 * @access private\r
884 * @return boolean  true on success, false on error.\r
885 */\r
886 function parseWordOrQuotedString ($s, &$p, &$w) {\r
887    $sLen = strlen($s);\r
888    while ($p < $sLen && ord($s{$p}) <= 32) $p++;\r
889    if ($p >= $sLen) return false;\r
890    if (substr($s,$p,1) == '"')\r
891       return $this->parseQuotedString($s,$p,$w);\r
892     else\r
893       return $this->parseWord($s,$p,$w); }\r
894 \r
895 /**\r
896 * Combine two file system paths.\r
897 * @access private\r
898 */\r
899 function combineFileSystemPath ($path1, $path2) {\r
900    if ($path1 == '' || $path2 == '') return $path2;\r
901    $s = $path1;\r
902    if (substr($s,-1) != '\\' && substr($s,-1) != '/') $s = $s . "/";\r
903    if (substr($path2,0,1) == '\\' || substr($path2,0,1) == '/')\r
904       $s = $s . substr($path2,1);\r
905     else\r
906       $s = $s . $path2;\r
907    return $s; }\r
908 \r
909 /**\r
910 * @access private\r
911 */\r
912 function triggerError ($msg) {\r
913    trigger_error ("MiniTemplator error: $msg", E_USER_ERROR); }\r
914 \r
915 /**\r
916 * @access private\r
917 */\r
918 function programLogicError ($errorId) {\r
919    die ("MiniTemplator: Program logic error $errorId.\n"); }\r
920 \r
921 }\r
922 ?>\r