]> git.wh0rd.org - tt-rss.git/blame - lib/MiniTemplator.class.php
pngcrush.sh
[tt-rss.git] / lib / MiniTemplator.class.php
CommitLineData
c62a2c21
AD
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
74class 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
84var $subtemplateBasePath;\r
85\r
86//--- private member variables --------------------------------------------------------------------------------------\r
87\r
88/**#@+\r
89* @access private\r
90*/\r
91\r
92var $maxNestingLevel = 50; // maximum number of block nestings\r
93var $maxInclTemplateSize = 1000000; // maximum length of template string when including subtemplates\r
94var $template; // Template file data\r
95var $varTab; // variables table, array index is variable no\r
96 // Fields:\r
97 // varName // variable name\r
98 // varValue // variable value\r
99var $varTabCnt; // no of entries used in VarTab\r
100var $varNameToNoMap; // maps variable names to variable numbers\r
101var $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
109var $varRefTabCnt; // no of entries used in VarRefTab\r
110var $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
129var $blockTabCnt; // no of entries used in BlockTab\r
130var $blockNameToNoMap; // maps block names to block numbers\r
131var $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
134var $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
146var $blockInstTabCnt; // no of entries used in BlockInstTab\r
147\r
148var $currentNestingLevel; // Current block nesting level during parsing.\r
149var $templateValid; // true if a valid template is prepared\r
150var $outputMode; // 0 = to PHP output stream, 1 = to file, 2 = to string\r
151var $outputFileHandle; // file handle during writing of output file\r
152var $outputError; // true when an output error occurred\r
153var $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
1003f71e 163function __construct() {\r
c62a2c21
AD
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
174function 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
187function 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
200function 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
214function 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
227function 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
243function 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
259function 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
271function 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
291function 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
322function 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
6f7798b6 339 $this->triggerError ("Block nesting overflow in template at offset $cmdTPosBegin.");\r
c62a2c21
AD
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
348function 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
375function 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
396function 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
412function 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
427function 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
442function 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
463function 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
476function 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
488function 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
525function 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
551function 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
578function 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
588function 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
605function 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
618function 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
646function 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
656function 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
667function 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
678function 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
694function 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
706function 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
724function 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
739function 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
791function 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
813function 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
826function 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
839function 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
6f7798b6 847 if ($fileSize === false) {fclose ($fh); return false; }\r
c62a2c21
AD
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
857function 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
870function 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
886function 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
899function 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
912function triggerError ($msg) {\r
913 trigger_error ("MiniTemplator error: $msg", E_USER_ERROR); }\r
914\r
915/**\r
916* @access private\r
917*/\r
918function programLogicError ($errorId) {\r
919 die ("MiniTemplator: Program logic error $errorId.\n"); }\r
920\r
921}\r
922?>\r