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]

Reply via email to