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 |
?>
|