MiniTemplator.class.php 36 KB

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