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