Subversion Repositories phpLibraryV2

Rev

Rev 10 | Details | Compare with Previous | 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
47 rodolico 62
 
63
   // this is a key inside the data hash that holds the value. If empty, the value is treated as the value
64
   // for example, if $hashKey is set to 'value', data used in processTemplate is assumed to be $data[$key]['value']
65
   // instead of just $data[$key]
66
   protected $hashKey = '';
1 rodolico 67
 
68
   public static $templatePath; // path to search for templates
69
   public $template; // holds entire template for processing
70
 
71
   /*
72
    * name:        constructor
73
    * parameters:  $filename -- optional file name for template
74
    *              $templatePath -- optional template path (full path on disk)
75
    * returns:     nothing
76
    * description: If filename is given, will prepend $templatePath to it and attempt to load
77
    *              from disk.
78
    *              if $templatePath is defined, will set the path FOR ALL INSTANCES. 
79
    */
80
 
47 rodolico 81
   public function __construct ( $filename = null, $templatePath = null, $hashKey = '' ) {
1 rodolico 82
      if ( isset( $templatePath ) ) {
83
         self::$templatePath = $templatePath;
84
      } // if
85
      if ( isset( $filename ) ) {
86
         $this->FileName( $filename );
87
         $this->loadTemplate();
88
      } // if
47 rodolico 89
      $this->hashKey = $hashKey;
1 rodolico 90
   } // construct
91
 
92
   /*
93
    * name:        FileName
94
    * parameters:  $filename -- optional override of filename stored in instance
95
    * returns:     $this->filename prior to (optional) replacing it with $filename param
96
    * description: Helper function which ensures path prepended to file name, then stores it
97
    */
98
 
99
   public function FileName ( $filename = null ) {
100
      $save = $this->filename;
101
      if ( isset ( $filename ) ) {
102
         $this->filename = self::$templatePath . $filename;
103
      }
104
      return $save;
105
   } // setFileName
106
 
107
   /*
108
    * name:       Template
109
    * parameters: $template -- a template to be stored
110
    * retuns:     The template before it was (optionally) replaced
111
    * description: Helper function to set/get $this->template. Called with no parameters, will
112
    *             simply return the contents for the current template. Called with a paramter
113
    *             will set the template to that value (and return the old template)
114
    */
115
 
116
   public function Template( $template = null ) {
117
      $save = $this->template;
118
      if ( isset( $template ) ) {
119
         $this->template = $template;
120
      }
121
      return $save;
122
   }
123
 
124
   /*
125
    * name:        loadTemplate
126
    * parameters:  $filename -- optional override of filename stored in instance
127
    * returns:     nothing
128
    * description: Loads the template file from disk
129
    */
130
 
131
   public function loadTemplate ( $filename = null ) {
132
      if ( isset( $filename ) ) {
133
         $this->FileName( $filename );
134
      }
135
      if ( is_readable( $this->filename ) ) {
136
         $this->template = file_get_contents( $this->filename );
137
      } else {
138
         throw new Exception( "File [$this->filename] does not exist or is unreadable", 15 );
139
      }
140
   } // loadTemplate
141
 
142
   /*
143
    * name:        process
144
    * parameters:  $dataset   -- array of hash containing values to replace
145
    * returns:     the processed template
146
    * description: Simply ensures the template is loaded, then calls processTemplate with a 
147
    *              copy. we will work on a copy of the template. Once loaded, the template can
148
    *              only be modified by another load. Thus, we could use the same instantiation
149
    *              with multiple calls to process using differnt data sets.
150
    */
151
 
152
   public function process ( $dataset ) {
153
      if ( ! isset( $this->template ) ) {
154
         $this->loadTemplate();
155
      } // if
156
      $template = $this->template;
157
      return $this->processTemplate( $dataset, $template );
158
   } // process
159
 
160
   /*
161
    * name:        processLoop
162
    * parameters:  $template -- The contents to be processed (exclusive of the <loop> construct)
163
    *              $data     -- array of hash containing values to replace
164
    * returns:     A copy of $template for each row, with all data fields filled in
165
    * description: For each row in $data, a copy of $template is processed by passing it back to
166
    *              processTemplate.
167
    * NOTE: This is a recursive function. It is called by processTemplate, then calls
168
    *       processTemplate. However, since (currently) loops can not be nested, the memory
169
    *       usage should be minimal
170
    */
171
 
172
   function processLoop ( $data, $template ) {
173
      // since we will repeat this over and over, make a copy of the template segment
174
      $original_code = $template;
175
      $returnValue = ''; // we will add to this for each loop
176
 
177
      foreach ( $data as $value ) { 
178
         $template = $original_code;
179
         $returnValue .= $this->processTemplate( $value, $template );
180
      } // foreach
181
      if ( ! isset( $returnValue ) ) {
182
            $returnValue = '<p>No Values Found</p>';
183
      }
184
      return $returnValue;
185
   }
186
 
187
 
188
   /*
189
    * name:        processTemplate
190
    * parameters:  $template -- The filename containing the template to be opened and processed
191
    *              $data     -- an array of hashes to populate the template with
192
    * returns:     A copy of the contents of the template with all fields filled in
193
    * description: First, all tag pair of the form <loop var=VARNAME> . . . </loop> are looked for
194
    *              and processed. The information between <loop>...</loop> and the hash entry
195
    *              VARNAME are passed to processLoop. See that code for information. Upon return,
196
    *              then entire block is replaced with the results of processLoop
197
    *              NOTE: Nested loops not currently supported
198
    *
199
    *             Next, all tag pairs of the form <ifdatafield fieldname> . . . </ifdatafield> are 
200
    *             searched and, if fieldname is not an element of the hash, (is null or blank), 
201
    *             the inner area is removed. Otherwise, the surrounding tags are removed and the 
202
    *             content remains.
203
    *             
204
    *             The entire template is now searched for tags of the form <datafield fieldname>, 
205
    *             and this tag is replaced by the contents of the field fieldname from $data
206
    *             
207
    *             Finally, the entire template is once more searched for tags of the form 
208
    *             <session varname>, and these tags are replaced with the session (cgi) parameters.
209
    * notes:      A lot of people don't like the conditional expression, but it makes the code
210
    *             better here. So, note the preg_replace statements use a conditional based on 
211
    *             whether the value is set and, if it is not, does the replace with null
212
    * 
213
    *             Also, when we do the preg_match, we grab the entire block that was matched
214
    *             ($matches[0]) then, after we find the correct value, we simply call preg_replace
215
    *             with that as the value that needs to be replaced in the string
216
    * 
217
    *             Finally, since the regexes are actually defined above, it almost seems as if
218
    *             we could have this whole function one big loop to go through each regex in turn
219
    *             however, they should be processed in the correct order
220
    *                loops are processed first. If not, they would be overwritten by the later ones
221
    *                conditionals may remove entire blocks, so they are processed next
222
    *                variable substitution is processed next
223
    *                session variables are dead last; generally used for screen wide things
224
    *             
225
    *             The processed template is returned by the routine.
226
    */
227
 
228
   private function processTemplate ( $data, $template ) {
47 rodolico 229
 
230
      // if they used a hashKey (the value is stored in an element of $data), replace the hash with the value
231
      // so, $data[$key][$this->hashkey] becomes just $data[$key]
232
      //print '<pre>processTemplate::data' . print_r( $data, true ) . '</pre>';
233
      if ( $this->hashKey ) {
234
         $tempData = array();
235
         //print "<pre>Processing hashKey $this->hashKey\n</pre>";
236
         foreach ( $data as $key => $value ) {
237
            //print "<pre>Checking $key\n</pre>";
238
            if ( is_array ($value ) && isset( $value[$this->hashKey] ) ) {
239
               //print "<pre>   it matches\n</pre>";
240
               $tempData[$key] = $value[$this->hashKey];
241
            } else {
242
               $tempData[$key] = $value;
243
            }
244
         }
245
         $data = $tempData;
246
      }
247
      //print "<pre>processTemplate::data\n" . print_r( $data, true ) . '</pre>'; 
248
      //print "Template is <pre>$template</pre>"; 
249
      //die;
1 rodolico 250
      // process loops first
251
      while ( preg_match( self::$regexes['loop'], $template, $matches ) ) {
252
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
253
         $dataKey = $matches[1]; // this is the name of the key into $data
254
         $segment  = $matches[2]; // and this is the template segment to be processed
255
         //return '<pre>' . print_r($dataKey, true) . print_r( $segment, true ) . '</pre>';      
256
         # do the actual replacement on the template
257
         $template = preg_replace( "/$block/", 
258
                                   isset( $data[$dataKey] ) ? 
259
                                          $this->processLoop( $data[$dataKey], $segment ) :
260
                                          '', 
261
                                   $template );
262
      }
263
      // then, conditionals
264
      while ( preg_match( self::$regexes['iffield'], $template, $matches ) ) {
265
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
266
         $field = $matches[1]; // the name of the field we are looking for
267
         $string = $matches[2]; // what to replace it with
268
         // process and replace
269
         $template = preg_replace( "/$block/", 
270
                     isset( $data[$field] ) ? $string : '', 
271
                     $template );
272
      } // while
273
      // next, any direct variable replacements
274
      while ( preg_match( self::$regexes['varname'], $template, $matches ) ) {
275
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
276
         $varname = $matches[1]; // the variable whose value we want here
47 rodolico 277
         //print "<pre>$varname\n" . print_r($data[$varname], true) . "</pre>";
1 rodolico 278
         // replace
47 rodolico 279
 
1 rodolico 280
         $template = preg_replace( "/$block/", 
281
                                 isset( $data[$varname] ) ? $data[$varname] : '',
282
                                 $template );
47 rodolico 283
 
1 rodolico 284
      } // while
285
      // and finally, session variables
286
      while ( preg_match( self::$regexes['session'], $template, $matches ) ) {
287
         $block = preg_quote($matches[0], '/'); // the full block that matches, so we can replace it
288
         $varname = $matches[1]; // the session variable name
289
         // replace
290
         $template = preg_replace( "/$block/", 
291
                                   isset( $_SESSION[$varname] ) ? $_SESSION[$varname] : '',
292
                                   $template) ;
293
      } // while
294
      return $template;
295
   }
296
 
297
 
298
} // classs Template
299
 
300
?>