After quite some experimentation with XSLT as well as several template packages, I have come to the conclusion that XSLT has several drawbacks. For one, it makes simple things like variable replacement in HTML more complicated, but makes complex things like conversion of a HTML table into a pie chart close to impossible.
Also, XSLTs Xpath is a kind of database language for hierarchical data stores (DOM trees), with all the drawbacks of a nonrelational and incomplete (no join, no aggregation) query language. And as a functional language it sucks, too (http://www.kuro5hin.org/story/2002/1/15/1562/95011). Finally, I always liked the ability of the Roxen webserver to bind code to tags. So I wrote a proof of concept class Transform in PHP, with does just that: It binds opening and closing functions to XML tags. With a version of TIDY linked into PHP as a library, one could even do this stunt with improperly nested HTML. What is this? Attached you find a class Transform, which contains some very rough proof-of-concept code. This code defines an output buffer to capture the XML generated by the page. That XML must be nested properly, as no attempt on capturing any errors is being made. I then use expat to parse that XML and look for function callbacks named just like the tags that are being processed: If a function called tag_open_h1 exists, then it is being called whenever a H1 tag is being seen. Same goes for closing tags. The opening tags are fed the Transform object and the attribuites of the tag. Closing tags are fed the content of the tag. Also, closing tags have access to a variable level, the nesting level, and to an element and attribute stack, as well as the output of their opening tag. Closing tags may even generate tags which need further processing, that is, you may define <b> to be replaced with <font> and <font> to be replaced with <em> and the result of <b/> will be an <em/>. How can this be used for templates? One could use this to define cold fusion and Roxen like tags such as <gtext>bla</gtext>, which will replace itself with an <img/> tag to an image containing the word "bla". A menu will be inserted where a <menu/> tag is shown and so on. Overloading <html/> or <body/> will define the template for a page. The following code is slow and very ad-hoc. Still, I'd like you to have a look at it and think about it - I'll gather reviews and opinions and probably turn it into C at some later time. Kristian
<?php class Transform { var $level = 0; var $elstack = array(); var $atstack = array(); var $cdata = array(); var $style = array(); var $case_folding = true; function Transform($style = "") { if (is_array($style)) $this->style = $style; } function set_style($tag, $open, $close) { $this->style[$this->canon($tag)]["open"] = $open; $this->style[$this->canon($tag)]["close"] = $close; } # expat callbacks function startElement($x, $n, &$a) { $this->level += 1; $this->elstack[$this->level] = $n; $this->atstack[$this->level] = $a; if (isset($this->style[$this->canon($n)]["open"])) { $fn = $this->style[$this->canon($n)]["open"]; $this->cdata[$this->level] = $fn($this, $a); } else { $this->cdata[$this->level] = "<$n" . $this->attr2str($a) . ">"; } } function endElement($x, $n) { $c = $this->cdata[$this->level]; if (isset($this->style[$this->canon($n)]["close"])) { $fn = $this->style[$this->canon($n)]["close"]; $c = $fn($this, $c); # Rekursion: # Wende die xml_transformation noch einmal auf # das Resultat an, um generierte Spezialtags # ebenfalls zu ersetzen. $t = new Transform($this->style); $c = $t->handle_output($c); } else { $c .= "</$n>"; } $this->level -= 1; # bubble transformation result upwards on stack $this->cdata[$this->level] .= $c; } function characterData($x, $data) { $this->cdata[$this->level] .= $data; } # output buffering handlers function handle_output($str) { if (strpos($str, "<") === false) return $str; $this->x = xml_parser_create(); xml_parser_set_option($this->x, XML_OPTION_CASE_FOLDING, $this->case_folding); xml_set_object($this->x, $this); xml_set_element_handler($this->x, "startElement", "endElement"); xml_set_character_data_handler($this->x, "characterData"); if (!xml_parse($this->x, $str, true)) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->x)), xml_get_current_line_number($this->x))); } xml_parser_free($this->x); return $this->cdata[0]; } # helper functions function canon($tag) { if ($this->case_folding) return strtoupper($tag); else return $tag; } function attr2str($a) { $r = ""; reset($a); while(list($k, $v) = each($a)) { $r .= " $k=\"$v\""; } return $r; } } function tag_open_h1(&$t, $attrs) { return "<h2" . $t->attr2str($attrs) . ">"; } function tag_close_h1(&$t, $content) { return "$content</h2>"; } function tag_open_b(&$t, $attrs) { return "<font color=\"#ff0000\">"; } function tag_close_b(&$t, $content) { return "$content</font>"; } function tag_open_font(&$t, $attrs) { return "<em>"; } function tag_close_font(&$t, $content) { return "$content</em>"; } function handle_output($str) { global $t; return $t->handle_output($str); } $t = new Transform( array("H1" => array("open" => "tag_open_h1", "close" => "tag_close_h1"), "B" => array("open" => "tag_open_b", "close" => "tag_close_b"), "FONT" => array("open" => "tag_open_font", "close" => "tag_close_font") ) ); ob_start("handle_output"); ?> <html> <h1 align="center">Huhu</h1> <p>Bla <b>Fasel</b> Lall</p> </html> <?php ob_end_flush(); ?>
-- PHP Development Mailing List <http://www.php.net/> To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] To contact the list administrators, e-mail: [EMAIL PROTECTED]