]>
Commit | Line | Data |
---|---|---|
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 | * <!-- $BeginBlock BlockName -->\r | |
20 | * ... block content ...\r | |
21 | * <!-- $EndBlock BlockName -->\r | |
22 | *\r | |
23 | * Include a subtemplate:\r | |
24 | * <!-- $Include RelativeFileName -->\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 MiniTemplator() {\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 | $trhis->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 <, >, &, ' 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) {close ($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 |