«
class.rFastTemplate

时间:2008-5-31    作者:Deri    分类: 分享


   <p>  <code><?php<br />//<br />// Copyright ?2000-2001, Roland Roberts <roland@astrofoto.org><br />//      &#160;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 />// &#160;@(#) $Id: class.rFastTemplate.php,v 1.22 2001/10/18 21:36:53 roland Exp $<br />// &#160;$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 />//   &#160;Same as clear. In fact, it was the same as clear in FastTemplate.<br />//  clear_all<br />//   &#160;If you really think you need this, try<br />//     unset $t;<br />//     $t = new Template ($path);<br />//   &#160;which gives the same effect.<br />//  clear_tpl<br />//   &#160;Use unload instead. This has the side effect of unloading all parent<br />//   &#160;and sibling templates which may be more drastic than you expect and<br />//   &#160;is different from class.FastTemplate.php3. This difference is<br />//   &#160;necessary since the only way we can force the reload of an embedded<br />//   &#160;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   &#160;--> setkey<br />//   clear_href &#160;--> unsetkey<br />//   clear_assign --> unsetkey<br />//   FastPrint  --> xprint<br />//<br />class rFastTemplate {<br /> &#160;// File name to be used for debugging output. Needs to be set prior to<br /> &#160;// calling anything other than option setting commands (debug, debugall,<br /> &#160;// strict, dynamic) because once the file has been opened, this is ignored.<br /> &#160;var $DEBUGFILE = '/tmp/class.rFastTemplate.php.dbg';<br /> &#160;// File descriptor for debugging output.<br /> &#160;var $DEBUGFD = -1;<br /> &#160;// Array for individual member functions. You can turn on debugging for a<br /> &#160;// particular member function by calling $this->debug(FUNCTION_NAME)<br /> &#160;var $DEBUG = array ();<br /> &#160;// Turn this on to turn on debugging in all member functions via<br /> &#160;// $this->debugall(). Turn if off via $this->debugall(false);<br /> &#160;var $DEBUGALL = false;<br /> &#160;// Names of actual templates. Each element will be an array with template<br /> &#160;// information including is originating file, file load status, parent<br /> &#160;// template, variable list, and actual template contents.<br /> &#160;var $TEMPLATE = array();<br /> &#160;// Holds paths-to-templates (See: set_root and FindTemplate)<br /> &#160;var $ROOT  &#160;= array();<br /> &#160;// Holds the HANDLE to the last template parsed by parse()<br /> &#160;var $LAST  &#160;= '';<br /> &#160;// Strict template checking. Unresolved variables in templates will generate a<br /> &#160;// warning.<br /> &#160;var $STRICT &#160;= true;<br /> &#160;// If true, this suppresses the warning generated by $STRICT=true.<br /> &#160;var $QUIET  = false;<br /> &#160;// Holds handles assigned by a call to parse().<br /> &#160;var $HANDLE &#160;= array();<br /> &#160;// Holds all assigned variable names and values.<br /> &#160;var $VAR   = array();<br /> &#160;// Set to true is this is a WIN32 server. This was part of the<br /> &#160;// class.FastTemplate.php3 implementation and the only real place it kicks<br /> &#160;// in is in setting the terminating character on the value of $ROOT, the<br /> &#160;// path where all the templates live.<br /> &#160;var $WIN32  = false;<br /> &#160;// Automatically scan template for dynamic templates and assign new values<br /> &#160;// to TEMPLATE based on whatever names the HTML comments use. This can be<br /> &#160;// changed up until the time the first parse() is called. Well, you can<br /> &#160;// change it anytime, but it will have no effect on already loaded<br /> &#160;// templates. Also, if you have dynamic templates, the first call to parse<br /> &#160;// will load ALL of your templates, so changing it after that point will<br /> &#160;// have no effect on any defined templates.<br /> &#160;var $DYNAMIC &#160;= true;<br /> &#160;// Grrr. Don't try to break these extra long regular expressions into<br /> &#160;// multiple lines for readability. PHP 4.03pl1 chokes on them if you do.<br /> &#160;// I'm guessing the reason is something obscure with the parenthesis<br /> &#160;// matching, the same sort of thing Tcl might have, but I'm not sure.<br /> &#160;// Regular expression which matches the beginning of a dynamic/inferior<br /> &#160;// template. The critical bit is that we need two parts: (1) the entire<br /> &#160;// match, and (2) the name of the dynamic template. The first part is<br /> &#160;// required because will do a strstr() to split the buffer into two<br /> &#160;// pieces: everything before the dynamic template declaration and<br /> &#160;// everything after. The second is needed because after finding a BEGIN<br /> &#160;// we will search for an END and they both have to have the same name of<br /> &#160;// we consider the template malformed and throw and error.<br /> &#160;// Both of these are written with PCRE (Perl-Compatible Regular<br /> &#160;// Expressions) because we need the non-greedy operators to insure that<br /> &#160;// we don't read past the end of the HTML comment marker in the case that<br /> &#160;// the BEGIN/END block have trailing comments after the tag name.<br /> &#160;var $REGEX_DYNBEG = '/(<!--\s*BEGIN\s+DYNAMIC\s+BLOCK:\s*([A-Za-z][-_A-Za-z0-9.]+)(\s*|\s+.*?)-->)/';<br /> &#160;// Regular expression which matches the end of a dynamic/inferior<br /> &#160;// template; see the comment about on the BEGIN match.<br /> &#160;var $REGEX_DYNEND = '/(<!--\s*END\s+DYNAMIC\s+BLOCK:\s*([A-Za-z][-_A-Za-z0-9.]+)(\s*|\s+.*?)-->)/';<br /> &#160;// Regular expression which matches a variable in the template.<br /> &#160;var $REGEX_VAR = '/\{[A-Za-z][-_A-Za-z0-9]*\}/';<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Constructor.<br /> &#160;//<br /> &#160;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 />    &#160;$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 /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Set the name to be used for debugging output. If another file has<br /> &#160;//  already been opened, close it so the next call to logwrite will<br /> &#160;//  reopen under this name.<br /> &#160;//<br /> &#160;function debugfile ($name) {<br />   $this->DEBUGFILE = $name;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Turn on/off debugging output of an individual member function.<br /> &#160;//<br /> &#160;function debug ($what, $on = true) {<br />   $this->DEBUG[$what] = $on;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Turn on/off debugging output of all member functions.<br /> &#160;//<br /> &#160;function debugall ($on = true) {<br />   $this->DEBUGALL = $on;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Turn on/off automatic dynamic template expansion. Note that a<br /> &#160;//  template with an inferior dynamic template embedded will still<br /> &#160;//  parse but only as if it were part of the main template. When this<br /> &#160;//  is turned on, it will be parsed out as as if it were a full-blown<br /> &#160;//  template and can thus be both parsed and appended to as a separate<br /> &#160;//  entity.<br /> &#160;//<br /> &#160;function dynamic ($on = true) {<br />   $this->DYNAMIC = $on;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Turn on/off strict template checking. When on, all template tags<br /> &#160;//  must be assigned or we throw an error (but stilll parse the<br /> &#160;//  template).<br /> &#160;//<br /> &#160;function strict ($on = true) {<br />   $this->STRICT = $on;<br /> &#160;}<br /> &#160;function quiet ($on = true) {<br />   $this->QUIET = true;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  For compatibility with class.FastTemplate.php3.<br /> &#160;//<br /> &#160;function no_strict () {<br />   $this->STRICT = false;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Utility function for debugging.<br /> &#160;//<br /> &#160;function logwrite ($msg) {<br />   if ($this->DEBUGFD < 0) {<br />    &#160;$this->DEBUGFD = fopen ($this->DEBUGFILE, 'a');<br />   }<br />   fputs ($this->DEBUGFD,<br />      &#160;strftime ('%Y/%m/%d %H:%M:%S ') . $msg . "\n");<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  This was lifted as-is from class.FastTemplate.php3. Based on what<br /> &#160;//  platform is in use, it makes sure the path specification ends with<br /> &#160;//  the proper path separator; i.e., a slash on unix systems and a<br /> &#160;//  back-slash on WIN32 systems. When we can run on Mac or VMS I guess<br /> &#160;//  we'll worry about other characters....<br /> &#160;//<br /> &#160;//  $root can now be an array of template roots which will be searched to<br /> &#160;//  find the first matching name.<br /> &#160;function set_root ($root) {<br />   if (!is_array($root)) {<br />    &#160;$trailer = substr ($root, -1);<br />    &#160;if ($trailer != ($this->WIN32 ? '\\' : '/'))<br />      $root .= ($this->WIN32 ? '\\' : '/');<br />    &#160;if (!is_dir($root)) {<br />      $this->error ("Specified ROOT dir [$root] is not a directory", true);<br />      return false;<br />    &#160;}<br />    &#160;$this->ROOT[] = $root;<br />   } else {<br />    &#160;reset($root);<br />    &#160;while(list($k, $v) = each($root)) {<br />      if (is_dir($v)) {<br />       &#160;$trailer = substr ($v,-1);<br />       &#160;if ($trailer != ($this->WIN32 ? '\\' : '/'))<br />         $v .= ($this->WIN32 ? '\\' : '/');<br />       &#160;$this->ROOT[] = $v;<br />      } else<br />       &#160;$this->error ("Specified ROOT dir [$v] is not a directory", true);<br />    &#160;}<br />   }<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Associate files with a template names.<br /> &#160;//<br /> &#160;// Sigh. At least with the CVS version of PHP, $dynamic = false sets it<br /> &#160;// to true.<br /> &#160;//<br /> &#160;function define ($fileList, $dynamic = 0) {<br />   reset ($fileList);<br />   while (list ($tpl, $file) = each ($fileList)) {<br />    &#160;$this->TEMPLATE[$tpl] = array ('file' => $file, 'dynamic' => $dynamic);<br />   }<br />   return true;<br /> &#160;}<br /> &#160;function define_dynamic ($tplList, $parent='') {<br />   if (is_array($tplList)) {<br />    &#160;reset ($tplList);<br />    &#160;while (list ($tpl, $parent) = each ($tplList)) {<br />      $this->TEMPLATE[$tpl]['parent'] = $parent;<br />      $this->TEMPLATE[$tpl]['dynamic'] = true;<br />    &#160;}<br />   } else {<br />    &#160;// $tplList is not an array, but a single child/parent pair.<br />    &#160;$this->TEMPLATE[$tplList]['parent'] = $parent;<br />    &#160;$this->TEMPLATE[$tplList]['dynamic'] = true;<br />   }<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Defines a template from a string (not a file). This function has<br /> &#160;//  not been ported from original PERL module to CDI's<br /> &#160;//  class.FastTemplate.php3, and it comebacks in rFastTemplate<br /> &#160;//  class. You can find it useful if you want to use templates, stored<br /> &#160;//  in database or shared memory.<br /> &#160;//<br /> &#160;function define_raw ($stringList, $dynamic = 0) {<br />   reset ($stringList);<br />   while (list ($tpl, $string) = each ($stringList)) {<br />    &#160;$this->TEMPLATE[$tpl] = array ('string' => $string, 'dynamic' => $dynamic, 'loaded' => 1);<br />   }<br />   return true;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  &#160;Try each directory in our list of possible roots in turn until we<br /> &#160;//  &#160;find a matching template<br /> &#160;//<br /> &#160;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 />    &#160;if (file_exists($file)) {<br />      return $file;<br />    &#160;}<br />   }<br />   // search path for a matching file<br />   reset($this->ROOT);<br />   while(list($k, $v) = each($this->ROOT)) {<br />    &#160;$f = $v . $file;<br />    &#160;if (file_exists($f)) {<br />      return $f;<br />    &#160;}<br />   }<br />   $this->error ("FindTemplate: file $file does not exist anywhere in " . implode(' ', $this->ROOT), true);<br />   return false;<br /> &#160;}<br /> &#160;//<br /> &#160;// Description<br /> &#160;//  Load a template into memory from the underlying file.<br /> &#160;//<br /> &#160;function &load ($file) {<br />   $debug = $this->DEBUGALL || $this->DEBUG['load'];<br />   if (! count($this->ROOT)) {<br />    &#160;if ($debug)<br />      $this->logwrite ("load: cannot open template $file, template base directory not set");<br />    &#160;$this->error ("cannot open template $file, template base directory not set", true);<br />    &#160;return false;<br />   } else {<br />    &#160;$contents = '';<br />    &#160;$filename = $this->FindTemplate ($file);<br />    &#160;if ($filename)<br />      $contents = implode ('', (@file($filename)));<br />    &#160;if (!($contents) or (empty($contents)) or (! $filename)) {<br />      if ($debug)<br />       &#160;$this->logwrite ("load: failed to load $file, $php_errormsg");<br />      $this->error ("load($file) failure: $php_errormsg", true);<br />    &#160;} else {<br />      if ($debug)<br />       &#160;$this->logwrite ("load: found $filename");<br />      return $contents;<br />    &#160;}<br />   }<br /> &#160;}<br /> //<br /> &#160;// Description<br /> &#160;//  Recursive internal parse routine. This will recursively parse a<br /> &#160;//  template containing dynamic inferior templates. Each of these<br /> &#160;//  inferior templates gets their own entry in the TEMPLATE array.<br /> &#160;//<br /> &#160;function &parse_internal_1 ($tag, $rest = '') {<br />   $debug = $this->DEBUGALL || $this->DEBUG['parse_internal_1'];<br />   if (empty($tag)) {<br />    &#160;$this->error ("parse_internal_1: empty tag invalid", true);<br />   }<br />   if ($debug)<br />    &#160;$this->logwrite ("parse_internal_1 (tag=$tag, rest=$rest)");<br />   while (!empty($rest)) {<br />    &#160;if ($debug)<br />      $this->logwrite ('parse_internal_1: REGEX_DYNBEG search: rest => ' . $rest);<br />    &#160;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 />       &#160;$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 />       &#160;$c = $rest{$offbeg};<br />       &#160;if ($c == "\n") {<br />         $okay = true;<br />         $offbeg++;<br />         break;<br />       &#160;}<br />       &#160;if (($c != ' ') && ($c != "\t")) {<br />         $offbeg = $pos;<br />         break;<br />       &#160;}<br />      }<br />      if (! $okay) {<br />       &#160;$offend = $pos + strlen($dynbeg[1]);<br />      } else {<br />       &#160;$l = strlen ($rest);<br />       &#160;for ($offend = $pos + strlen($dynbeg[1]); $offend < $l; $offend++) {<br />         $c = $rest{$offend};<br />         if ($c == "\n") {<br />          &#160;$offend++;<br />          &#160;break;<br />         }<br />         if (($c != ' ') && ($c != "\t")) {<br />          &#160;$offend = $pos + strlen($dynbeg[1]);<br />          &#160;break;<br />         }<br />       &#160;}<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 />       &#160;$part[] = substr ($rest, 0, $offbeg);<br />      $rest = substr ($rest, $offend);<br />      $sub = '';<br />      if ($debug)<br />       &#160;$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 />       &#160;if ($debug)<br />         $this->logwrite ('parse_internal_1: REGEX_DYNEND search: rest => ' . $rest);<br />       &#160;if ($debug)<br />         $this->logwrite ('parse_internal_1: match beg => ' . $dynend[1]);<br />       &#160;$pos = strpos ($rest, $dynend[1]);<br />       &#160;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 />          &#160;$c = $rest{$offbeg};<br />          &#160;if ($c == "\n") {<br />            $offbeg++;<br />            $okay = true;<br />            break;<br />          &#160;}<br />          &#160;if (($c != ' ') && ($c != "\t")) {<br />            $offbeg = $pos;<br />            break;<br />          &#160;}<br />         }<br />         if (! $okay) {<br />          &#160;$offend = $pos + strlen($dynend[1]);<br />         } else {<br />          &#160;$l = strlen ($rest);</code></p>