<p> <code><?php<br />//<br />// Copyright ?2000-2001, Roland Roberts <roland@astrofoto.org><br />//  2001 Alister Bulman <alister@minotaur.nu> Re-Port multi template-roots + more<br />// PHP3 Port: Copyright ?1999 CDI <cdi@thewebmasters.net>, All Rights Reserved.<br />// Perl Version: Copyright ?1998 Jason Moore <jmoore@sober.com>, All Rights Reserved.<br />//<br />// RCS Revision<br />//  @(#) $Id: class.rFastTemplate.php,v 1.22 2001/10/18 21:36:53 roland Exp $<br />//  $Source: /home/cvs/projects/php/tools/class.rFastTemplate.php,v $<br />//<br />// Copyright Notice<br />//<br />// This program is free software; you can redistribute it and/or modify<br />// it under the terms of the GNU General Public License as published by<br />// the Free Software Foundation; either version 2, or (at your option)<br />// any later version.<br />//<br />// class.rFastTemplate.php is distributed in the hope that it will be<br />// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of<br />// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br />// General Public License for more details.<br />//<br />// Comments<br />//<br />// I would like to thank CDI <cdi@thewebmasters.net> for pointing out the<br />// copyright notice attached to his PHP3 port which I had blindly missed<br />// in my first release of this code.<br />//<br />// This work is derived from class.FastTemplate.php3 version 1.1.0 as<br />// available from http://www.thewebmasters.net/. That work makes<br />// reference to the "GNU General Artistic License". In correspondence<br />// with the author, the intent was to use the GNU General Public License;<br />// this work does the same.<br />//<br />// Authors<br />//<br />// Roland Roberts <roland@astrofoto.org><br />// Alister Bulman <alister@minotaur.nu> (multi template-roots)<br />// Michal Rybarik <michal@rybarik.sk> (define_raw())<br />// CDI <cdi@thewebmasters.net>, PHP3 port<br />// Jason Moore <jmoore@sober.com>, original Perl version<br />//<br />// Synopsis<br />//<br />// require ("PATH-TO-TEMPLATE-CODE/class.Template.php");<br />// $t = new Template("PATH-TO-TEMPLATE-DIRECTORY");<br />// $t->define (array(MAIN => "diary.html"));<br />// $t->setkey (VAR1, "some text");<br />// $t->subst (INNER, "inner")<br />// $t->setkey (VAR1, "some more text");<br />// $t->subst (INNER, ".inner")<br />// $t->setkey (VAR2, "var2 text");<br />// $t->subst (CONTENT, "main");<br />// $t->print (CONTENT);<br />//<br />// Description<br />//<br />// This is a class.FastTemplate.php3 replacement that provides most of the<br />// same interface but has the ability to do nested dynamic templates. The<br />// default is to do dynamic template expansion and no special action is<br />// required for this to happen.<br />//<br />// class.FastTemplate.php3 Methods Not Implemented<br />//<br />// clear_parse<br />//  Same as clear. In fact, it was the same as clear in FastTemplate.<br />// clear_all<br />//  If you really think you need this, try<br />// unset $t;<br />// $t = new Template ($path);<br />//  which gives the same effect.<br />// clear_tpl<br />//  Use unload instead. This has the side effect of unloading all parent<br />//  and sibling templates which may be more drastic than you expect and<br />//  is different from class.FastTemplate.php3. This difference is<br />//  necessary since the only way we can force the reload of an embedded<br />//  template is to force the reload of the parent and sibling templates.<br />//<br />// class.FastTemplate.php3 Methods by Another Name<br />//<br />// The existence of these functions is a historical artifact. I<br />// originally had in mind to write a functional equivalent from scratch.<br />// Then I came my senses and just grabbed class.FastTemplate.php3 and<br />// started hacking it. So, you can use the names on the right, but the<br />// ones on the left are equivalent and are the names used in the original<br />// class.FastTemplate.php3.<br />//<br />// parse --> subst<br />// get_assiged --> getkey<br />// assign  --> setkey<br />// clear_href  --> unsetkey<br />// clear_assign --> unsetkey<br />// FastPrint --> xprint<br />//<br />class rFastTemplate {<br />  // File name to be used for debugging output. Needs to be set prior to<br />  // calling anything other than option setting commands (debug, debugall,<br />  // strict, dynamic) because once the file has been opened, this is ignored.<br />  var $DEBUGFILE = '/tmp/class.rFastTemplate.php.dbg';<br />  // File descriptor for debugging output.<br />  var $DEBUGFD = -1;<br />  // Array for individual member functions. You can turn on debugging for a<br />  // particular member function by calling $this->debug(FUNCTION_NAME)<br />  var $DEBUG = array ();<br />  // Turn this on to turn on debugging in all member functions via<br />  // $this->debugall(). Turn if off via $this->debugall(false);<br />  var $DEBUGALL = false;<br />  // Names of actual templates. Each element will be an array with template<br />  // information including is originating file, file load status, parent<br />  // template, variable list, and actual template contents.<br />  var $TEMPLATE = array();<br />  // Holds paths-to-templates (See: set_root and FindTemplate)<br />  var $ROOT  = array();<br />  // Holds the HANDLE to the last template parsed by parse()<br />  var $LAST  = '';<br />  // Strict template checking. Unresolved variables in templates will generate a<br />  // warning.<br />  var $STRICT  = true;<br />  // If true, this suppresses the warning generated by $STRICT=true.<br />  var $QUIET = false;<br />  // Holds handles assigned by a call to parse().<br />  var $HANDLE  = array();<br />  // Holds all assigned variable names and values.<br />  var $VAR = array();<br />  // Set to true is this is a WIN32 server. This was part of the<br />  // class.FastTemplate.php3 implementation and the only real place it kicks<br />  // in is in setting the terminating character on the value of $ROOT, the<br />  // path where all the templates live.<br />  var $WIN32 = false;<br />  // Automatically scan template for dynamic templates and assign new values<br />  // to TEMPLATE based on whatever names the HTML comments use. This can be<br />  // changed up until the time the first parse() is called. Well, you can<br />  // change it anytime, but it will have no effect on already loaded<br />  // templates. Also, if you have dynamic templates, the first call to parse<br />  // will load ALL of your templates, so changing it after that point will<br />  // have no effect on any defined templates.<br />  var $DYNAMIC  = true;<br />  // Grrr. Don't try to break these extra long regular expressions into<br />  // multiple lines for readability. PHP 4.03pl1 chokes on them if you do.<br />  // I'm guessing the reason is something obscure with the parenthesis<br />  // matching, the same sort of thing Tcl might have, but I'm not sure.<br />  // Regular expression which matches the beginning of a dynamic/inferior<br />  // template. The critical bit is that we need two parts: (1) the entire<br />  // match, and (2) the name of the dynamic template. The first part is<br />  // required because will do a strstr() to split the buffer into two<br />  // pieces: everything before the dynamic template declaration and<br />  // everything after. The second is needed because after finding a BEGIN<br />  // we will search for an END and they both have to have the same name of<br />  // we consider the template malformed and throw and error.<br />  // Both of these are written with PCRE (Perl-Compatible Regular<br />  // Expressions) because we need the non-greedy operators to insure that<br />  // we don't read past the end of the HTML comment marker in the case that<br />  // the BEGIN/END block have trailing comments after the tag name.<br />  var $REGEX_DYNBEG = '/(<!--\s*BEGIN\s+DYNAMIC\s+BLOCK:\s*([A-Za-z][-_A-Za-z0-9.]+)(\s*|\s+.*?)-->)/';<br />  // Regular expression which matches the end of a dynamic/inferior<br />  // template; see the comment about on the BEGIN match.<br />  var $REGEX_DYNEND = '/(<!--\s*END\s+DYNAMIC\s+BLOCK:\s*([A-Za-z][-_A-Za-z0-9.]+)(\s*|\s+.*?)-->)/';<br />  // Regular expression which matches a variable in the template.<br />  var $REGEX_VAR = '/\{[A-Za-z][-_A-Za-z0-9]*\}/';<br />  //<br />  // Description<br />  // Constructor.<br />  //<br />  function rFastTemplate ($pathToTemplates = '') {<br /> // $pathToTemplates can also be an array of template roots, handled in set_root<br /> global $php_errormsg;<br /> if (!empty($pathToTemplates)) {<br />  $this->set_root ($pathToTemplates);<br /> }<br /> $this->DEBUG = array ('subst' => false,<br /> 'parse_internal' => false,<br /> 'parse_internal_1' => false,<br /> 'parsed' => false,<br /> 'clear' => false,<br /> 'clear_dynamic' => false,<br /> 'load' => false);<br /> return $this;<br />  }<br />  //<br />  // Description<br />  // Set the name to be used for debugging output. If another file has<br />  // already been opened, close it so the next call to logwrite will<br />  // reopen under this name.<br />  //<br />  function debugfile ($name) {<br /> $this->DEBUGFILE = $name;<br />  }<br />  //<br />  // Description<br />  // Turn on/off debugging output of an individual member function.<br />  //<br />  function debug ($what, $on = true) {<br /> $this->DEBUG[$what] = $on;<br />  }<br />  //<br />  // Description<br />  // Turn on/off debugging output of all member functions.<br />  //<br />  function debugall ($on = true) {<br /> $this->DEBUGALL = $on;<br />  }<br />  //<br />  // Description<br />  // Turn on/off automatic dynamic template expansion. Note that a<br />  // template with an inferior dynamic template embedded will still<br />  // parse but only as if it were part of the main template. When this<br />  // is turned on, it will be parsed out as as if it were a full-blown<br />  // template and can thus be both parsed and appended to as a separate<br />  // entity.<br />  //<br />  function dynamic ($on = true) {<br /> $this->DYNAMIC = $on;<br />  }<br />  //<br />  // Description<br />  // Turn on/off strict template checking. When on, all template tags<br />  // must be assigned or we throw an error (but stilll parse the<br />  // template).<br />  //<br />  function strict ($on = true) {<br /> $this->STRICT = $on;<br />  }<br />  function quiet ($on = true) {<br /> $this->QUIET = true;<br />  }<br />  //<br />  // Description<br />  // For compatibility with class.FastTemplate.php3.<br />  //<br />  function no_strict () {<br /> $this->STRICT = false;<br />  }<br />  //<br />  // Description<br />  // Utility function for debugging.<br />  //<br />  function logwrite ($msg) {<br /> if ($this->DEBUGFD < 0) {<br />  $this->DEBUGFD = fopen ($this->DEBUGFILE, 'a');<br /> }<br /> fputs ($this->DEBUGFD,<br />  strftime ('%Y/%m/%d %H:%M:%S ') . $msg . "\n");<br />  }<br />  //<br />  // Description<br />  // This was lifted as-is from class.FastTemplate.php3. Based on what<br />  // platform is in use, it makes sure the path specification ends with<br />  // the proper path separator; i.e., a slash on unix systems and a<br />  // back-slash on WIN32 systems. When we can run on Mac or VMS I guess<br />  // we'll worry about other characters....<br />  //<br />  // $root can now be an array of template roots which will be searched to<br />  // find the first matching name.<br />  function set_root ($root) {<br /> if (!is_array($root)) {<br />  $trailer = substr ($root, -1);<br />  if ($trailer != ($this->WIN32 ? '\\' : '/'))<br /> $root .= ($this->WIN32 ? '\\' : '/');<br />  if (!is_dir($root)) {<br /> $this->error ("Specified ROOT dir [$root] is not a directory", true);<br /> return false;<br />  }<br />  $this->ROOT[] = $root;<br /> } else {<br />  reset($root);<br />  while(list($k, $v) = each($root)) {<br /> if (is_dir($v)) {<br />  $trailer = substr ($v,-1);<br />  if ($trailer != ($this->WIN32 ? '\\' : '/'))<br /> $v .= ($this->WIN32 ? '\\' : '/');<br />  $this->ROOT[] = $v;<br /> } else<br />  $this->error ("Specified ROOT dir [$v] is not a directory", true);<br />  }<br /> }<br />  }<br />  //<br />  // Description<br />  // Associate files with a template names.<br />  //<br />  // Sigh. At least with the CVS version of PHP, $dynamic = false sets it<br />  // to true.<br />  //<br />  function define ($fileList, $dynamic = 0) {<br /> reset ($fileList);<br /> while (list ($tpl, $file) = each ($fileList)) {<br />  $this->TEMPLATE[$tpl] = array ('file' => $file, 'dynamic' => $dynamic);<br /> }<br /> return true;<br />  }<br />  function define_dynamic ($tplList, $parent='') {<br /> if (is_array($tplList)) {<br />  reset ($tplList);<br />  while (list ($tpl, $parent) = each ($tplList)) {<br /> $this->TEMPLATE[$tpl]['parent'] = $parent;<br /> $this->TEMPLATE[$tpl]['dynamic'] = true;<br />  }<br /> } else {<br />  // $tplList is not an array, but a single child/parent pair.<br />  $this->TEMPLATE[$tplList]['parent'] = $parent;<br />  $this->TEMPLATE[$tplList]['dynamic'] = true;<br /> }<br />  }<br />  //<br />  // Description<br />  // Defines a template from a string (not a file). This function has<br />  // not been ported from original PERL module to CDI's<br />  // class.FastTemplate.php3, and it comebacks in rFastTemplate<br />  // class. You can find it useful if you want to use templates, stored<br />  // in database or shared memory.<br />  //<br />  function define_raw ($stringList, $dynamic = 0) {<br /> reset ($stringList);<br /> while (list ($tpl, $string) = each ($stringList)) {<br />  $this->TEMPLATE[$tpl] = array ('string' => $string, 'dynamic' => $dynamic, 'loaded' => 1);<br /> }<br /> return true;<br />  }<br />  //<br />  // Description<br />  //  Try each directory in our list of possible roots in turn until we<br />  //  find a matching template<br />  //<br />  function FindTemplate ($file) {<br /> // first try for a template in the current directory short path for<br /> // absolute filenames<br /> if (substr($file, 0, 1) == '/') {<br />  if (file_exists($file)) {<br /> return $file;<br />  }<br /> }<br /> // search path for a matching file<br /> reset($this->ROOT);<br /> while(list($k, $v) = each($this->ROOT)) {<br />  $f = $v . $file;<br />  if (file_exists($f)) {<br /> return $f;<br />  }<br /> }<br /> $this->error ("FindTemplate: file $file does not exist anywhere in " . implode(' ', $this->ROOT), true);<br /> return false;<br />  }<br />  //<br />  // Description<br />  // Load a template into memory from the underlying file.<br />  //<br />  function &load ($file) {<br /> $debug = $this->DEBUGALL || $this->DEBUG['load'];<br /> if (! count($this->ROOT)) {<br />  if ($debug)<br /> $this->logwrite ("load: cannot open template $file, template base directory not set");<br />  $this->error ("cannot open template $file, template base directory not set", true);<br />  return false;<br /> } else {<br />  $contents = '';<br />  $filename = $this->FindTemplate ($file);<br />  if ($filename)<br /> $contents = implode ('', (@file($filename)));<br />  if (!($contents) or (empty($contents)) or (! $filename)) {<br /> if ($debug)<br />  $this->logwrite ("load: failed to load $file, $php_errormsg");<br /> $this->error ("load($file) failure: $php_errormsg", true);<br />  } else {<br /> if ($debug)<br />  $this->logwrite ("load: found $filename");<br /> return $contents;<br />  }<br /> }<br />  }<br /> //<br />  // Description<br />  // Recursive internal parse routine. This will recursively parse a<br />  // template containing dynamic inferior templates. Each of these<br />  // inferior templates gets their own entry in the TEMPLATE array.<br />  //<br />  function &parse_internal_1 ($tag, $rest = '') {<br /> $debug = $this->DEBUGALL || $this->DEBUG['parse_internal_1'];<br /> if (empty($tag)) {<br />  $this->error ("parse_internal_1: empty tag invalid", true);<br /> }<br /> if ($debug)<br />  $this->logwrite ("parse_internal_1 (tag=$tag, rest=$rest)");<br /> while (!empty($rest)) {<br />  if ($debug)<br /> $this->logwrite ('parse_internal_1: REGEX_DYNBEG search: rest => ' . $rest);<br />  if (preg_match ($this->REGEX_DYNBEG, $rest, $dynbeg)) {<br /> // Found match, now split into two pieces and search the second<br /> // half for the matching END. The string which goes into the<br /> // next element includes the HTML comment which forms the BEGIN<br /> // block.<br /> if ($debug)<br />  $this->logwrite ('parse_internal_1: match beg => ' . $dynbeg[1]);<br /> $pos = strpos ($rest, $dynbeg[1]);<br /> // See if the text on either side of the BEGIN comment is only<br /> // whitespace. If so, we delete the entire line.<br /> $okay = false;<br /> for ($offbeg = $pos - 1; $offbeg >= 0; $offbeg--) {<br />  $c = $rest{$offbeg};<br />  if ($c == "\n") {<br /> $okay = true;<br /> $offbeg++;<br /> break;<br />  }<br />  if (($c != ' ') && ($c != "\t")) {<br /> $offbeg = $pos;<br /> break;<br />  }<br /> }<br /> if (! $okay) {<br />  $offend = $pos + strlen($dynbeg[1]);<br /> } else {<br />  $l = strlen ($rest);<br />  for ($offend = $pos + strlen($dynbeg[1]); $offend < $l; $offend++) {<br /> $c = $rest{$offend};<br /> if ($c == "\n") {<br />  $offend++;<br />  break;<br /> }<br /> if (($c != ' ') && ($c != "\t")) {<br />  $offend = $pos + strlen($dynbeg[1]);<br />  break;<br /> }<br />  }<br /> }<br /> // This includes the contents of the REGEX_DYNBEG in the output<br /> // $part[] = substr ($rest, 0, $pos);<br /> // This preserves whitespace on the END block line(s).<br /> // $part[] = substr ($rest, 0, $pos+strlen($dynbeg[1]));<br /> // $rest = substr ($rest, $pos+strlen($dynbeg[1]));<br /> // Catch case where BEGIN block is at position 0.<br /> if ($offbeg > 0)<br />  $part[] = substr ($rest, 0, $offbeg);<br /> $rest = substr ($rest, $offend);<br /> $sub = '';<br /> if ($debug)<br />  $this->logwrite ("parse_internal_1: found at pos = $pos");<br /> // Okay, here we are actually NOT interested in just the next<br /> // END block. We are only interested in the next END block that<br /> // matches this BEGIN block. This is not the most efficient<br /> // because we really could do this in one pass through the<br /> // string just marking BEGIN and END blocks. But the recursion<br /> // makes for a simple algorithm (if there was a reverse<br /> // preg...).<br /> $found = false;<br /> while (preg_match ($this->REGEX_DYNEND, $rest, $dynend)) {<br />  if ($debug)<br /> $this->logwrite ('parse_internal_1: REGEX_DYNEND search: rest => ' . $rest);<br />  if ($debug)<br /> $this->logwrite ('parse_internal_1: match beg => ' . $dynend[1]);<br />  $pos = strpos ($rest, $dynend[1]);<br />  if ($dynbeg[2] == $dynend[2]) {<br /> $found = true;<br /> // See if the text on either side of the END comment is<br /> // only whitespace. If so, we delete the entire line.<br /> $okay = false;<br /> for ($offbeg = $pos - 1; $offbeg >= 0; $offbeg--) {<br />  $c = $rest{$offbeg};<br />  if ($c == "\n") {<br /> $offbeg++;<br /> $okay = true;<br /> break;<br />  }<br />  if (($c != ' ') && ($c != "\t")) {<br /> $offbeg = $pos;<br /> break;<br />  }<br /> }<br /> if (! $okay) {<br />  $offend = $pos + strlen($dynend[1]);<br /> } else {<br />  $l = strlen ($rest);</code></p>