quickfix for bug that caused
[mediawiki.git] / PHPTAL-NP-0.7.0 / libs / PHPTAL / Parser.php
blobf113c5d2d53f3ab241cb746f0d97d4df6ffa3670
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 //
4 // Copyright (c) 2003 Laurent Bedubourg
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 //
20 // Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
21 //
23 $__d = dirname(__FILE__);
24 require_once _phptal_os_path_join($__d, 'XML_Parser.php');
25 require_once _phptal_os_path_join($__d, 'Attribute.php');
26 require_once _phptal_os_path_join($__d, 'Expression.php');
27 require_once _phptal_os_path_join($__d, 'ExpressionFunctions.php');
28 require_once _phptal_os_path_join($__d, 'Generator.php');
29 require_once _phptal_os_path_join($__d, 'Tag.php');
31 $_t = _phptal_os_path_join($__d, 'Attribute', 'TAL');
32 require_once _phptal_os_path_join($_t, 'Attributes.php');
33 require_once _phptal_os_path_join($_t, 'Comment.php');
34 require_once _phptal_os_path_join($_t, 'Content.php');
35 require_once _phptal_os_path_join($_t, 'Condition.php');
36 require_once _phptal_os_path_join($_t, 'Define.php');
37 require_once _phptal_os_path_join($_t, 'Omit_tag.php');
38 require_once _phptal_os_path_join($_t, 'Replace.php');
39 require_once _phptal_os_path_join($_t, 'On_error.php');
40 require_once _phptal_os_path_join($_t, 'Repeat.php');
42 $_t = _phptal_os_path_join($__d, 'Attribute', 'PHPTAL');
43 require_once _phptal_os_path_join($_t, 'Include.php');
44 require_once _phptal_os_path_join($_t, 'Src_include.php');
46 $_t = _phptal_os_path_join($__d, 'Attribute', 'METAL');
47 require_once _phptal_os_path_join($_t, 'Define_macro.php');
48 require_once _phptal_os_path_join($_t, 'Use_macro.php');
49 require_once _phptal_os_path_join($_t, 'Define_slot.php');
50 require_once _phptal_os_path_join($_t, 'Fill_slot.php');
52 $_t = _phptal_os_path_join($__d, 'Attribute', 'I18N');
53 require_once _phptal_os_path_join($_t, 'Translate.php');
54 require_once _phptal_os_path_join($_t, 'Name.php');
55 require_once _phptal_os_path_join($_t, 'Attributes.php');
58 /**
59 * PHPTAL template parser.
61 * This object implements PHPTAL_XML_Parser interface and will accept only
62 * well formed xml templates.
65 * Parser object has two aims :
67 * - generate the template structure tree
68 * - generate the php source code this structure represents
70 * Once this job is accomplished, the parser object should be destroyed and
71 * MUST NOT be used to parse another template. It's a one time and drop
72 * object.
75 * Note about code generation :
77 * The final source code is ready to write into a php file.
79 * The code generation process requires a function name which should represent
80 * the template unique id (Template class makes an md5 over the source file
81 * path to create this id).
83 * @version 0.1
84 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
86 class PHPTAL_Parser extends PHPTAL_XML_Parser
88 // root gat
89 var $_root;
90 // document headers (string)
91 var $_headers = false;
92 var $_started = false;
94 var $_docType;
95 var $_inDocType;
97 // current tag
98 var $_current;
100 // source file name
101 var $_file;
103 // activate xhtml empty tags lookup
104 var $_outputFormat = PHPTAL_XHTML;
106 // keep xmlns:* attributes ?
107 var $_keepXMLNS = false;
110 * Parse a template string.
112 * @param string $file
113 * The template source file.
114 * @param string $src
115 * The template source.
117 * @throws PHPTAL_ParseError
119 function parse($file, $src)
121 $this->_file = $file;
122 $this->_initRoot();
123 return $this->parseString($src, true);
127 * Generate php code.
129 * After template parsing, this method must be called to generate php code
130 * from the template tree.
132 * @param string func_name
133 * The template function name
135 * @access private
136 * @return string
138 function generateCode($func_name)
140 $gen = new PHPTAL_Generator($this->_file, $func_name);
141 $gen->setHeaders($this->_headers);
142 $err = $this->_root->generateCode($gen);
143 if (PEAR::isError($err)) {
144 return $err;
146 return $gen->getCode();
150 * Return a string representation of the underlying xhtml tree.
152 * This method is for debug purpose.
154 * @access private
155 * @return string
157 function toString()
159 $buf = new StringBuffer();
160 $buf->appendln('Template tree [\'', $this->_file, '\']');
161 $buf->append($this->_root->toString());
162 return $buf->toString();
166 // ----------------------------------------------------------------------
167 // Private methods
168 // ----------------------------------------------------------------------
171 * Initialize root node.
173 * @access private
175 function _initRoot()
177 $this->_headers = "";
178 $this->_started = false;
179 $this->_root = new PHPTAL_Tag($this, "#root", array());
180 $this->_current =& $this->_root;
184 * Push a node as the current one.
186 * @param PHPTAL_Node tag
187 * @access private
189 function _push(&$tag)
191 $this->_current->addChild($tag);
192 unset($this->_current);
193 $this->_current =& $tag;
197 * Push a node into the current one.
199 * @param PHPTAL_Node tag
200 * @access private
202 function _pushChild(&$tag)
204 $this->_current->addChild($tag);
208 * Pop the last node (go up a level in tree).
210 * @access private
212 function _pop()
214 $temp =& $this->_current;
215 unset($this->_current);
216 if ($temp != $this->_root) {
217 $this->_current =& $temp->getParent();
218 } else {
219 $this->_current =& $this->_root;
224 * getter/setter for the output mode.
226 * @param int $mode optional
227 * PHPTAL_XML or PHPTAL_XHTML
229 function _outputMode($mode=false)
231 if ($mode !== false) {
232 $this->_outputFormat = $mode;
234 return $this->_outputFormat;
237 // ----------------------------------------------------------------------
238 // XML callbacks methods
239 // ----------------------------------------------------------------------
242 * xml callback
244 * @access private
246 function onElementStart($name, $attributes)
248 global $_phptal_namespaces;
250 $this->_started = true;
252 if (strpos($name, ':') !== false) {
253 list($domain, $extend) = split(':', strtoupper($name));
254 if (($extend == 'BLOCK') && (in_array($domain, $_phptal_namespaces))) {
255 $attributes = PHPTAL_Parser::extendZptBlockAttributes($domain, $attributes);
256 $attributes['tal:omit-tag'] = '';
260 // separate phptal attributes from xhtml ones
261 // if an attribute is not found, an error is raised.
262 $split = PHPTAL_Parser::TemplateAttributes($attributes);
263 if (PEAR::isError($split)) {
264 return $split;
267 // no error, the returned value is a tuple
268 list($phptal, $attributes) = $split;
270 // sort phptal attributes
271 $phptal = PHPTAL_Parser::OrderTemplateAttributes($phptal);
272 if (PEAR::isError($phptal)) {
273 return $phptal;
276 // create the tag and add its template attributes
277 $tag = new PHPTAL_Tag($this, $name, $attributes);
278 foreach ($phptal as $t) {
279 $tag->appendTemplateAttribute($t);
280 unset($t); // $t is appended by reference
283 $tag->line = $this->getLineNumber();
284 $this->_push($tag);
288 * Extends ZPT attributes withing a *:block element so these attributes can
289 * be used by phptal parser.
291 * @access private
293 function extendZptBlockAttributes($domain, $attributes)
295 global $_phptal_dictionary;
296 $result = array();
297 foreach ($attributes as $key => $value) {
298 $expected = strtoupper("$domain:$key");
299 if (array_key_exists($expected, $_phptal_dictionary)) {
300 $result[$expected] = $value;
303 return $result;
307 * xml callback
309 * @access private
311 function onElementData($data)
313 // ${xxxx} variables are evaluated during code
314 // generation whithin the CodeGenerator under the
315 // printString() method.
316 $tag = new PHPTAL_Tag($this, "#cdata", array());
317 $tag->setContent($data);
318 $this->_pushChild($tag);
322 * xml callback
324 * @access private
326 function onSpecific($data)
328 // fix xml parser '&' => '&amp;' automatic conversion
329 $data = str_replace('&amp;', '&', $data);
331 if ($this->_current->name() == "#root" && !$this->_started) {
332 $this->_headers .= $data;
333 return;
335 $tag = new PHPTAL_Tag($this, "#cdata", array());
336 $tag->setContent($data);
337 $this->_pushChild($tag);
341 * xml callback
343 * @access private
345 function onElementClose($name)
347 if ($this->_current == null) {
348 return $this->_raiseNoTagExpected($name);
350 if ($this->_current->name() != $name) {
351 return $this->_raiseUnexpectedTagClosure($name);
353 $this->_pop();
356 // ----------------------------------------------------------------------
357 // Static methods
358 // ----------------------------------------------------------------------
361 * Lookup template attributes in given hashtable.
363 * This method separate xml attributes from template attributes
364 * and return an array composed of the array of formers and the array of
365 * laters.
367 * @access private
368 * @static 1
370 * @param hashtable attrs
371 * Attributes hash
373 * @return array
375 function TemplateAttributes($attrs)
377 global $_phptal_dictionary, $_phptal_aliases, $_phptal_namespaces;
378 $phptal = array();
379 $att = array();
381 foreach ($attrs as $key=>$exp) {
383 $test_key = strtoupper($key);
384 $ns = preg_replace('/(:.*?)$/', '', $test_key);
385 $sns = preg_replace('/^(.*?:)/', '', $test_key);
386 // dictionary lookup
387 if (array_key_exists($test_key, $_phptal_dictionary)) {
388 $phptal[$test_key] = $exp;
390 // alias lookup
391 elseif (array_key_exists($test_key, $_phptal_aliases)) {
392 $phptal[ $_phptal_aliases[$test_key] ] = $exp;
394 // the namespace is known but the the attribute is not
395 elseif (in_array($ns, $_phptal_namespaces)) {
396 return $this->_raiseUnknownAttribute($test_key);
398 // regular xml/xhtml attribute (skip namespaces declaration)
399 elseif ($ns !== 'XMLNS' || $this->_keepXMLNS
400 || !in_array($sns, $_phptal_namespaces)) {
401 $att[$key] = $exp;
404 return array($phptal, $att);
408 * Order phptal attributes array using $_phptal_rules_order array.
410 * @static 1
411 * @access private
413 * @param array phptal
414 * Array of phptal attributes (will be modified)
416 function OrderTemplateAttributes(&$phptal)
418 global $_phptal_rules_order, $_phptal_dictionary;
420 // order elements by their name using the rule table
421 $result = array();
422 foreach ($phptal as $akey=>$exp) {
424 // retrieve attribute handler class
425 $class = "PHPTAL_ATTRIBUTE_" . str_replace(":", "_", $akey);
426 $class = str_replace("-", "_", $class);
427 if (!class_exists($class)) {
428 return $this->_raiseAttributeNotFound($akey, $class);
431 $hdl = new $class($exp);
432 $hdl->name = $akey;
433 $hdl->_phptal_type = $_phptal_dictionary[$akey];
435 // resolve attributes conflict
436 $pos = $_phptal_rules_order[$akey];
437 if (array_key_exists($pos, $result)) {
438 return $this->_raiseAttConflict($akey, $result[$pos]->name);
441 // order elements by their order rule
442 $result[$_phptal_rules_order[$akey]] = $hdl;
443 unset($hdl);
445 return $result;
448 // ----------------------------------------------------------------------
449 // Errors raising methods
450 // ----------------------------------------------------------------------
452 function _raiseAttributeNotFound($att, $class)
454 $str = sprintf("Attribute '%s' exists in dictionary but class '%s' ".
455 "was not found",
456 $att, $class);
457 $err = new PHPTAL_ParseError($str);
458 return PEAR::raiseError($err);
461 function _raiseUnknownAttribute($att)
463 $str = sprintf("Unknown PHPTAL attribute '%s' in %s at line %d",
464 $att, $this->_file, $this->getLineNumber());
465 $err = new PHPTAL_UnknownAttribute($str);
466 return PEAR::raiseError($err);
469 function _raiseUnexpectedTagClosure($name)
471 $str = sprintf("Non matching tag '%s' error in xml file '%s' at line %d"
472 . endl . "waiting for end of tag '%s' declared at line %d.",
473 $name, $this->_file, $this->getLineNumber(),
474 $this->_current->name(), $this->_current->line);
475 $err = new PHPTAL_ParseError($str);
476 return PEAR::raiseError($err);
479 function _raiseNoTagExpected($name)
481 $str = sprintf("Bad xml error in xml file '%s' at line %d". endl
482 ."Found closing tag '%s' while no current tag is waited.",
483 $this->_file, $this->getLineNumber(), $name);
484 $err = new PHPTAL_ParseError($str);
485 return PEAR::raiseError($err);
488 function _raiseAttConflict($a2, $a1)
490 $str = sprintf('Template Attribute conflict in \'%s\' at line %d'. endl
491 . ' %s must not be used in the same tag as %s',
492 $this->_file, $this->getLineNumber(), $a1, $a2);
493 $err = new PHPTAL_AttributeConflict($str);
494 return PEAR::raiseError($err);
499 * Error raised at parse time when some bad xml is found or when some tag
500 * is unknown or is badly used.
502 class PHPTAL_ParseError extends PEAR_Error {}
505 * Error raised when an unknown PHPTAL tag is requested by the template.
507 class PHPTAL_UnknownAttribute extends PHPTAL_ParseError {}
510 * Error raised when a template attribute conflicts with another one.
512 class PHPTAL_AttributeConflict extends PHPTAL_ParseError {}