small fixes from nye
[yatt.git] / php / YATT.class.php
1 <?php
2 /*
3  * Simple text template class. Does not handle caching.
4  *
5  * Version 1.1 
6  *
7  */
8
9 # here is whats in each array node
10 define('YATT_NAME',     0);  # node name
11 define('YATT_OUTPUT',   1);  # output buffer for this node
12 define('YATT_CONTEXTS', 2);  # not impl. in php: node context
13 define('YATT_DSTART',   3);  # start of data/chilren nodes
14
15 # This sucks, but array_pop doesn't deal well with references
16 function &my_pop(&$array) {
17     if (!count($array)) return null;
18     end($array);
19     $var =& $array[key($array)];
20     array_pop($array);
21     return $var;
22 }
23
24 class YATT {
25
26     # Holds the template tree
27     var $obj;
28
29     # Holds variables
30     var $vars = array();
31
32     # Holds information on errors
33     var $errors;
34
35     # INTERNAL: Push an error onto the error stack!
36     function error() {
37         $args = func_get_args ();
38         $format = array_shift($args);
39         array_push($this->errors, vsprintf($format, $args));
40         return count($this->errors);
41     }
42
43     # INTERNAL: act as a callback for preg_replace_callback below
44     function pp_callback($matches) {
45         return $this->preprocess($matches[1]);
46     }
47
48     # INTERNAL: read file, preprocess for includes, strip out comments
49     function preprocess($fname) {
50         if (! ($data = file_get_contents($fname))) {
51             $this->error('INCLUDE(%s): can not open file!', $fname);
52             return '';
53         }
54
55         # strip all comments
56         $data = preg_replace('/[ \t]*\%\[#\].*$/m', '', $data);
57
58         # fetch all includes (recursive!)
59         return preg_replace_callback(
60                     '/^[ \t]*\%include[ \t]+\[([A-Za-z-_.]+)\][ \t]*$/im',
61                     array(&$this, 'pp_callback'),
62                     $data);
63     }
64
65     # load template file on class creation 
66     # You can load multiple files
67     function load($fname) {
68         $data = $this->preprocess($fname);
69         $stack = array();
70
71         $cur = &$this->obj;
72         $nchunks = preg_match_all(
73                 '/(.*?)[ \t]*\%(begin|end)[\t ]+\[([A-Za-z-_]+)\][\t ]*$/ism', 
74                 $data, $matches, PREG_SET_ORDER);
75
76         # array[1] == text, array[2] == (begin|end), array[3] == NAME
77         for ($i = 0; $i < $nchunks; $i++) {
78             $text = $matches[$i][1];
79             $type = strtolower($matches[$i][2]);
80             $name = $matches[$i][3];
81
82             if ($text && (strlen($text) > 0)) {
83                 array_push($cur, $text);
84             }
85
86             if (strcasecmp($type, 'begin') == 0) {
87                 $new = array($name, '', '');
88                 $cur[] =& $new;
89                 $stack[] =& $cur;
90                 $cur = &$new;
91                 unset($new);
92             } else if (strcasecmp($type, 'end') == 0) {
93                 if (strcmp($cur[YATT_NAME], $name)) {
94                     return $this->error('LOAD(%s): Mismatched begin/end: got %s, wanted %s! aborting!',
95                                                 $fname, $name, $cur[YATT_NAME]);
96                 }
97                 if (! ($cur =& my_pop($stack))) {
98                     $cur = &$this->obj;
99                 }
100             } else {
101                 return $this->error('LOAD(%s): unknown tag type %s, aborting!', $fname, $type);
102             }
103         }
104         if (count($stack)) {
105             return $this->error('LOAD(%s): mismatched begin/end pairs at EOF!', $fname);
106         }
107         return count($this->errors);
108     }
109
110     # INTERNAL: Find the node that corrosponds to an OID
111     function &find_node($path) {
112         $oid = explode('.', $path);
113         $node = &$this->obj;
114
115         while ($cmp = array_shift($oid)) {
116             $old = &$node;
117             for ($i = YATT_DSTART; $i < count($node); $i++) {
118                 if (is_array($node[$i]) && (strcmp($node[$i][YATT_NAME], $cmp) == 0)) {
119                         $node = &$node[$i];
120                         break;
121                 }
122             }
123             if ($old == $node) {
124                 $this->error('FIND(%s): Could not find node %s', $path, $cmp);
125                 return FALSE;
126             }
127         }
128         return $node;
129     }
130
131     # INTERNAL: Substitute some stuff! 
132     function subst($matches) {
133         if (!isset($this->vars[$matches[1]])) {
134            $this->error('PARSE(): unbound variable %s', $matches[1]);
135            return '';
136         }
137         return $this->vars[$matches[1]];
138     }
139
140     # INTERNAL: Build the output for this node
141     function build_output(&$root, &$node) {
142         $out = '';
143
144         for ($i = YATT_DSTART; $i < count($node); $i++) {
145             if (is_array($node[$i])) {
146                 $out .= $this->return_output($node[$i]);
147             } else {
148                 $buf = $node[$i];
149                 $pass = 0;
150
151                 while (preg_match('/\%\[([^][%]+)\]/', $buf)) {
152                         if ($pass++ > 10) {
153                             # TODO: give the user more info when this happens.
154                             $this->error('PARSE(): recursive subst in node %s?', $node[YATT_NAME]);
155                             break;
156                         }
157                         $buf = preg_replace_callback('/\%\[([^][%]+)\]/', array(&$root, 'subst'), $buf);
158                 }
159                 $out .= $buf;
160             }
161         }
162         return $out;
163     }
164  
165     # INTERNAL: Return all of the generated output
166     function return_output(&$node) {
167         $out = $node[YATT_OUTPUT];
168         $node[YATT_OUTPUT] = '';
169
170         for ($i = YATT_DSTART; $i < count($node); $i++) {
171             if (is_array($node[$i])) {
172                 $out .= $this->return_output($node[$i]);
173             }
174         }
175         return $out;
176     }
177
178     # Create a new YATT instance.
179     function YATT() {
180         $this->errors = array();
181         $this->obj = array('ROOT', '', '');
182     }
183
184     # Return output, starting at a given node
185     function output($path=NULL) {
186         $obj =& $this->find_node($path);
187         return $obj ? $this->return_output($obj) : FALSE;
188     }
189
190     # Generate text from an object tree
191     function parse($path) {
192         if ($obj =& $this->find_node($path)) {
193             $obj[YATT_OUTPUT] .= $this->build_output($this, $obj);
194         }
195     }
196
197     # Set a variable to some value 
198     function set($var, $value=NULL) {
199         if (is_array($var)) {
200             $this->vars = array_merge($this->vars, $var);
201         } else {
202             $this->vars[$var] = $value;
203         }
204     }
205
206     # Get errors, or return FALSE if there are none. 
207     # Resets error list to zero!
208     function get_errors() {
209         $err = $this->errors;
210         $this->errors = array();
211         return count($err) ? $err : FALSE;
212     }
213 }
214
215 ?>