Subversion Repositories phpLibraryV2

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 rodolico 1
<?php
2
 
3
/*
4
   Very basic, but uncomplicated, templating system for PHP.
5
 
6
   Template class takes a template and merges it with data. Templates can be any 
7
   format; PostScript, text, HTML, whatever. The bottom line is, Template will look for
8
   special tag in the file and replace them with values passed to it in a has (called $data here).
9
 
10
   The following template illustrates all current tags:
11
 
12
   <table>
13
         <loop owners>
14
            <tr>
15
               <td>
16
                 <ifdatafield selected><b></ifdatafield>
17
                  <datafield name>
18
                 <ifdatafield selected></b></ifdatafield>
19
               </td>
20
               <td><datafield address></td>
21
            </tr>
22
         </loop>
23
   </table>
24
   <p>This is version <datafield Version> of the file</p>
25
 
26
   The special tags above are <loop>, <ifdatafiled>, <datafield> (the first two with their
27
   closing tags indicated by a / as in HTML).
28
 
29
   The following PHP code snippet illustrates a data file that could be passed to the above:
30
 
31
      $data = array( 
32
                     'owners' => array(
33
                         array( 'name' => 'Rod', 'address' => 'Dallas' ),
34
                         array( 'name' => 'Nancy', 'address' => 'Dallas' ),
35
                         array( 'name' => 'Chick', 'address' => 'Davao', 'selected' => true ),
36
                         array( 'name' => 'Rhonda', 'address' => 'Houston' )
37
                      ),
38
                      'Version' => '2.0.5',
39
                      'unused' = 'some arbitrary thing'
40
                   );
41
 
42
   Note that in the above template, a table is defined, then the special tag <loop owners> is
43
   introduced. This will process everything up to the </loop> using the data found in 
44
   $data['owners']. For each loop, if the key 'selected' is defined a 'bold' (<b> .. </b>) is
45
   placed around the first data field. After that, the element whose key is 'name' and 
46
   'address' are inserted. After the loop, the rest of the hash is processed, with the value of
47
   $data['Version'] being used in the last paragrap. Note that $data['unused'] is not used in
48
   the template.
49
*/
50
 
51
 
52
class Template {
53
 
54
   protected static $regexes = array( // note, using # as delimiter so don't have to escape /
55
                    'loop' => '#<loop +([^>]+)>(.*?)</loop>#si',
56
                    'iffield' => '#<ifdatafield +([^>]+)>(.*?)</ifdatafield>#si',
57
                    'varname' => '#<datafield +([^>]+)>#si',
58
                    'session' => '#<session +([^>]+)>#si'
59
                 );
60
   protected static $delimiters = array( 'open' => '<', 'close' => '>' );
61
   protected $filename; // holds the name of the template file
62
 
63
   public static $templatePath; // path to search for templates
64
   public $template; // holds entire template for processing
65
 
66
   /*
67
    * name:        constructor
68
    * parameters:  $filename -- optional file name for template
69
    *              $templatePath -- optional template path (full path on disk)
70
    * returns:     nothing
71
    * description: If filename is given, will prepend $templatePath to it and attempt to load
72
    *              from disk.
73
    *              if $templatePath is defined, will set the path FOR ALL INSTANCES. 
74
    */
75
 
76
   public function __construct ( $filename = null, $templatePath = null ) {
77
      if ( isset( $templatePath ) ) {
78
         self::$templatePath = $templatePath;
79
      } // if
80
      if ( isset( $filename ) ) {
81
         $this->FileName( $filename );
82
         $this->loadTemplate();
83
      } // if
84
   } // construct
85
 
86
   /*
87
    * name:        FileName
88
    * parameters:  $filename -- optional override of filename stored in instance
89
    * returns:     $this->filename prior to (optional) replacing it with $filename param
90
    * description: Helper function which ensures path prepended to file name, then stores it
91
    */
92
 
93
   public function FileName ( $filename = null ) {
94
      $save = $this->filename;
95
      if ( isset ( $filename ) ) {
96
         $this->filename = self::$templatePath . $filename;
97
      }
98
      return $save;
99
   } // setFileName
100
 
101
   /*
102
    * name:       Template
103
    * parameters: $template -- a template to be stored
104
    * retuns:     The template before it was (optionally) replaced
105
    * description: Helper function to set/get $this->template. Called with no parameters, will
106
    *             simply return the contents for the current template. Called with a paramter
107
    *             will set the template to that value (and return the old template)
108
    */
109
 
110
   public function Template( $template = null ) {
111
      $save = $this->template;
112
      if ( isset( $template ) ) {
113
         $this->template = $template;
114
      }
115
      return $save;
116
   }
117
 
118
   /*
119
    * name:        loadTemplate
120
    * parameters:  $filename -- optional override of filename stored in instance
121
    * returns:     nothing
122
    * description: Loads the template file from disk
123
    */
124
 
125
   public function loadTemplate ( $filename = null ) {
126
      if ( isset( $filename ) ) {
127
         $this->FileName( $filename );
128
      }
129
      if ( is_readable( $this->filename ) ) {
130
         $this->template = file_get_contents( $this->filename );
131
      } else {
132
         throw new Exception( "File [$this->filename] does not exist or is unreadable", 15 );
133
      }
134
   } // loadTemplate
135
 
136
   /*
137
    * name:        process
138
    * parameters:  $dataset   -- array of hash containing values to replace
139
    * returns:     the processed template
140
    * description: Simply ensures the template is loaded, then calls processTemplate with a 
141
    *              copy. we will work on a copy of the template. Once loaded, the template can
142
    *              only be modified by another load. Thus, we could use the same instantiation
143
    *              with multiple calls to process using differnt data sets.
144
    */
145
 
146
   public function process ( $dataset ) {
147
      if ( ! isset( $this->template ) ) {
148
         $this->loadTemplate();
149
      } // if
150
      $template = $this->template;
151
      return $this->processTemplate( $dataset, $template );
152
   } // process
153
 
154
   /*
155
    * name:        processLoop
156
    * parameters:  $template -- The contents to be processed (exclusive of the <loop> construct)
157
    *              $data     -- array of hash containing values to replace
158
    * returns:     A copy of $template for each row, with all data fields filled in
159
    * description: For each row in $data, a copy of $template is processed by passing it back to
160
    *              processTemplate.
161
    * NOTE: This is a recursive function. It is called by processTemplate, then calls
162
    *       processTemplate. However, since (currently) loops can not be nested, the memory
163
    *       usage should be minimal
164
    */
165
 
166
   function processLoop ( $data, $template ) {
167
      // since we will repeat this over and over, make a copy of the template segment
168
      $original_code = $template;
169
      $returnValue = ''; // we will add to this for each loop
170
 
171
      foreach ( $data as $value ) { 
172
         $template = $original_code;
173
         $returnValue .= $this->processTemplate( $value, $template );
174
      } // foreach
175
      if ( ! isset( $returnValue ) ) {
176
            $returnValue = '<p>No Values Found</p>';
177
      }
178
      return $returnValue;
179
   }
180
 
181
 
182
   /*
183
    * name:        processTemplate
184
    * parameters:  $template -- The filename containing the template to be opened and processed
185
    *              $data     -- an array of hashes to populate the template with
186
    * returns:     A copy of the contents of the template with all fields filled in
187
    * description: First, all tag pair of the form <loop var=VARNAME> . . . </loop> are looked for
188
    *              and processed. The information between <loop>...</loop> and the hash entry
189
    *              VARNAME are passed to processLoop. See that code for information. Upon return,
190
    *              then entire block is replaced with the results of processLoop
191
    *              NOTE: Nested loops not currently supported
192
    *
193
    *             Next, all tag pairs of the form <ifdatafield fieldname> . . . </ifdatafield> are 
194
    *             searched and, if fieldname is not an element of the hash, (is null or blank), 
195
    *             the inner area is removed. Otherwise, the surrounding tags are removed and the 
196
    *             content remains.
197
    *             
198
    *             The entire template is now searched for tags of the form <datafield fieldname>, 
199
    *             and this tag is replaced by the contents of the field fieldname from $data
200
    *             
201
    *             Finally, the entire template is once more searched for tags of the form 
202
    *             <session varname>, and these tags are replaced with the session (cgi) parameters.
203
    * notes:      A lot of people don't like the conditional expression, but it makes the code
204
    *             better here. So, note the preg_replace statements use a conditional based on 
205
    *             whether the value is set and, if it is not, does the replace with null
206
    * 
207
    *             Also, when we do the preg_match, we grab the entire block that was matched
208
    *             ($matches[0]) then, after we find the correct value, we simply call preg_replace
209
    *             with that as the value that needs to be replaced in the string
210
    * 
211
    *             Finally, since the regexes are actually defined above, it almost seems as if
212
    *             we could have this whole function one big loop to go through each regex in turn
213
    *             however, they should be processed in the correct order
214
    *                loops are processed first. If not, they would be overwritten by the later ones
215
    *                conditionals may remove entire blocks, so they are processed next
216
    *                variable substitution is processed next
217
    *                session variables are dead last; generally used for screen wide things
218
    *             
219
    *             The processed template is returned by the routine.
220
    */
221
 
222
   private function processTemplate ( $data, $template ) {
223
      // process loops first
224
      while ( preg_match( self::$regexes['loop'], $template, $matches ) ) {
225
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
226
         $dataKey = $matches[1]; // this is the name of the key into $data
227
         $segment  = $matches[2]; // and this is the template segment to be processed
228
         //return '<pre>' . print_r($dataKey, true) . print_r( $segment, true ) . '</pre>';      
229
         # do the actual replacement on the template
230
         $template = preg_replace( "/$block/", 
231
                                   isset( $data[$dataKey] ) ? 
232
                                          $this->processLoop( $data[$dataKey], $segment ) :
233
                                          '', 
234
                                   $template );
235
      }
236
      // then, conditionals
237
      while ( preg_match( self::$regexes['iffield'], $template, $matches ) ) {
238
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
239
         $field = $matches[1]; // the name of the field we are looking for
240
         $string = $matches[2]; // what to replace it with
241
         // process and replace
242
         $template = preg_replace( "/$block/", 
243
                     isset( $data[$field] ) ? $string : '', 
244
                     $template );
245
      } // while
246
      // next, any direct variable replacements
247
      while ( preg_match( self::$regexes['varname'], $template, $matches ) ) {
248
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
249
         $varname = $matches[1]; // the variable whose value we want here
250
         // replace
251
         $template = preg_replace( "/$block/", 
252
                                 isset( $data[$varname] ) ? $data[$varname] : '',
253
                                 $template );
254
      } // while
255
      // and finally, session variables
256
      while ( preg_match( self::$regexes['session'], $template, $matches ) ) {
257
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
258
         $varname = $matches[1]; // the session variable name
259
         // replace
260
         $template = preg_replace( "/$block/", 
261
                                   isset( $_SESSION[$varname] ) ? $_SESSION[$varname] : '',
262
                                   $template) ;
263
      } // while
264
      return $template;
265
   }
266
 
267
 
268
} // classs Template
269
 
270
?>