Might be useful ? Though I don't use PEAR at the moment
http://www.devshed.com/Server_Side/P...ees/page1.html
Also this has some interesting ideas; Part One covers SAX btw
http://www.devshed.com/Server_Side/X...HP2/page6.html
| SitePoint Sponsor |





Might be useful ? Though I don't use PEAR at the moment
http://www.devshed.com/Server_Side/P...ees/page1.html
Also this has some interesting ideas; Part One covers SAX btw
http://www.devshed.com/Server_Side/X...HP2/page6.html
I just posted my last message and i thought on sotring all tags instances as a Tree, i need to finish some work today, if i have energies and time this night i will look at it![]()
The CVS repository is up at http://www.sourceforge.net/projects/phpmarker
It is just the initial import, it doesnt support nested tags.
I will upload there some samples for other, you already know how it works![]()
Hi again folks,
what about using xml_parse_into_struct() function to get an tree like structure? I still don't want to use DOMXML.



Nice topic. A while ago, I wanted to create a SAX-based template engine which takes XHTML and XML-compliant tags as input and when it encouters a tag it executes a particular tag handler. I wanted to limit my tags to the following:
<tpl:attr name="attribute"/>
<tpl:foreach name="someList" as="element"></tpl:foreach>
<tpl:if exists="attribute"></tpl:if>
So I decided to take this approach:
XMLTemplate (in Python) by Patrick Lioi:
http://patrick.lioi.net/archive/2003/09/13/162936
"Dynamic XML Conversion Using the SAX Parser and a Stack":
http://www.phpbuilder.com/columns/sc...r20030325.php3
I also ran into the problem of nested tags and how to handle loops. I decided that maybe I could recursively apply the template to the body of the tag when I encounter a loop so that it handles the variables within the loop. I think this would take care of the nested problem for me.
JT





Hi...
This is something of a problem with language parsing. If all you are doing is descending into nested tags then a state machine will do the job (a state machine is used in regular expression matching). The trouble is that in a realistic job you will want to gather information as you descend into the tags. You will want to match up this information as you back out of the nesting. A state machine just doesn't cut it.Originally Posted by seratonin
I have only done this a couple of times, so what follows should be treated as still experimental.
You will need a stack machine as you have surmised. You could descend a tag stack recursively, but I found that this mixed up the parsing and semantic interpretation too much. My preferred solution these days is to have an explicit stack in the listener and keep all of the control code there. This leaves the tag objects free to handle the operations. I had better show some interfaces...
(edit: Fixed a code glitch)PHP Code:class SaxParser {
function SaxParser(&$listener) { ... }
function parse($html) { ... }
}
class SaxToTemplateFilter {
function SaxToTemplateFilter(&$listener) { ... }
function handleTagStart($name, $attributes) { ... }
function handleTagEnd($name) { ... }
function handleWholeTag($name, $attributes) { ... }
function handleContent($text) { ... }
}
class TemplateListener {
function TemplateListener(&$writer) { ... }
function passThrough($content) { ... }
function createSubstitution($key, $value) { ... }
function substitute($key) { ... }
function loopStart($variable) { ... }
function loopEnd() { ... }
}
The TemplateListener knows nothing about the incoming language, it could be HTML, XML or plain text. The SaxParser is generic and so you could use it anywhere. The filter converts incoming tags to semantic calls. This is still a state machine, if indeed it needs any state at all, unless you are doing syntax checking. If you are affirming that loop tags match, then you would need to keep a stack. Personally I wouldn't bother, as it's only really needed for nicer error messages.
The tricky bits are all in the language listener, especially the loop events. Each one need to push a "stack frame" on the stack. If you are adding new substitutions (macros) within the language, then it will have to keep track of these in the frame. Otherwise all you really need is the loop index. When in the first loop we had the command history recorded both in this stack frame and those below it (this can probably be made more efficient). The loop end then replays the stack frame's command history until it finishes and the stack frame is discarded. This approach is much more efficient than reparsing the raw text stream.
In the template engine we built we also had included files and aliasing of variables and macros, but that's another story.
Anyway, now the bad news. We never used it. Looping in particular was too slow, even after optimisation. We changed plan.
A much more effective method is to compile the template into PHP. This way loop tags simply translate into PHP loops, and special tags translate in CustomTag calls. This is actually easier than building a full blown parser and way easier to debug. Deployment (and testing) is more complicated and we needed to learn another language. I don't think I would recommend generating PHP with PHP unless you are very brave. We used XSLT as a quick and dirty prototype. For anything this complicated I would use Haskellor Ruby
for production. It still runs as PHP and if you don't have any other languages on the server then build the templates offline and then deploy them.
Anyway, just my experience so far.
yours, Marcus
Last edited by lastcraft; Jan 12, 2004 at 08:51.
Marcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
Looks great, I'll post again when I got something working
If you want to see the code just check the cvs repository: http://www.sourceforge.net/projects/phpmarker




viTxo,Originally Posted by viTxo
Could you post some examples on how to use this?
Matt
Yes, sorry, I'm writing some docs but it will take a few weeks to finish them, i'm studying for my exams
BTW, here is how to use the template engine:
index.php
And the template:PHP Code:include('lib/TemplateManager.class.php');
$manager =& TemplateManager::getInstance();
$manager -> setDirectory('presentation/');
$view =& $manager -> getTemplate('layout.html', 'layout');
$page['title'] = 'Just a title';
$page['body'] = 'Lorem Ipsum Dolor...';
$obj =& new DummyClass();
$friend['site'] = '...';
$friend['description'] = ...;
$friend['name'] = '...';
...
$friends[0] = $friend;
....
$numbers[0] = 0;
$numbers[1] = 1;
$numbers[2] = 2;
$numbers[3] = 3;
$numbers[4] = 4;
$view -> expose('numbers', $numbers );
$view -> expose('page', $page );
$view -> expose('object', $obj );
$view -> expose('friends', $friends );
$view -> process();
presentation/portal/index.html
You can iterate over scalar, hashmaps and objects lists. Of course, the template engine is not finished hihiHTML Code:<html> <head><title>${page.title}</title></head> <body> <p>${page.body}</p> <list name="numbers" as="number"> <img src="/gfx/counter/${number}.png" alt="${number}"/> </list> <ul> <list name="friends" as="friend"> <li><a href="${friend.site}" title="${friend.description}">${friend.name}</a></li> </list> </ul> <p>${object.attribute}</p> </body> </html>
I still want to code a conditional tag and a text transformation tag (cut, highlight and date formatting)
I'll post new stuff here.
read you soon
vitxo.
Last edited by viTxo; Jan 25, 2004 at 04:04. Reason: fixed template code



Here is the PHP implementation of XMLTemplate from my link above. It uses the standard SAX parser and a stack to handle tag nesting.
The usage is quite simple. Basically, you would create a class where the names of the class's methods are the same as the names of the tags you wanted to replace.PHP Code:<?php
// PHP Implementation of Patrick Lioi's "XMLTemplate"
class XMLTemplate {
var $tagHandler;
var $parser;
var $stack = array();
function XMLTemplate($tagHandler) {
$this->tagHandler = $tagHandler;
}
function parse($xml) {
array_push($this->stack, array("contents" => ""));
$this->parser = xml_parser_create("ISO-8859-1");
xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
xml_set_object($this->parser, &$this);
xml_set_element_handler($this->parser, "startElement", "endElement");
xml_set_character_data_handler($this->parser, "characterData");
xml_set_default_handler($this->parser, "characterData");
if (!xml_parse($this->parser, $xml)) {
die("Error parsing XML.");
}
xml_parser_free($this->parser);
$first = array_pop($this->stack);
return $first['contents'];
}
function createTag($name, $attrs, $contents) {
$buffer = "<$name";
foreach ($attrs as $attr => $value) {
$buffer .= ' ' . $attr . '="' . $value . '"';
}
if (strlen($contents) == 0) {
$buffer .= " />";
} else {
$buffer .= ">" . $contents . "</" . $name . ">";
}
return $buffer;
}
function startElement($parser, $name, $attrs) {
array_push($this->stack, array("attrs" => $attrs, "contents" => ""));
}
function characterData($parser, $cdata) {
$data = array_pop($this->stack);
$data['contents'] .= $cdata;
array_push($this->stack, $data);
}
function endElement($parser, $name) {
$data = array_pop($this->stack);
if (method_exists($this->tagHandler, $name)) {
$buffer = call_user_func(array(&$this->tagHandler, $name), $data['attrs'], $data['contents']);
} else {
$buffer = $this->createTag($name, $data['attrs'], $data['contents']);
}
$sublevel = array_pop($this->stack);
$sublevel['contents'] .= $buffer;
array_push($this->stack, $sublevel);
}
}
?>
For example, say you wanted simple variable replacement with a tag such as <variable name="title"/>. You could do something like this:
sometemplate.html:PHP Code:<?php
require_once("XMLTemplate.class.php");
class TagHandler {
var $vars;
function set($name, $value) {
$this->vars[$name] = $value;
}
function variable($attrs, $contents) {
return $this->vars[$attrs['name']];
}
}
$xml = file_get_contents("sometemplate.html");
$handler = new TagHandler;
$handler->set("title", "Insert Title Here");
$handler->set("message", "Hello, world!");
$template = new XMLTemplate($handler);
$result = $template->parse($xml);
echo $result;
?>
My desire is to limit the tags to simple variables, conditional (which tests for existence of a variable only), and foreach (include and transform might also be handy). I would, of course, also extend it to handle namespaces <tpl:variable name="message"/>.Code:<html> <head> <title><variable name="title"/></title> </head> <body> <variable name="message"/> </body> </html>
In order to implement the foreach tag, I would treat the contents of the foreach tag as a separate "template" which would call $template->parse for each element. Implementing a conditional, would be relatively easy, because I am limiting it to testing presence of a variable only. I am also tempted to implement some sort of attribute value template system similar to what phpmarker is doing.
JT
Last edited by seratonin; Jan 12, 2004 at 16:55.




Hey! Awesome. I going to check this out. But is there a way to take care of the "Call-time pass-by-reference" warnings without messing with error_reporting()?Originally Posted by seratonin
Matt



Where are you seeing these warnings? Please note that I had to change "var" to "variable" because it is a reserved word. I may change the function calling scheme to handleVariableTag so for example <include file="somefile.php" eval="true"/> would be handled by a class with a method name "handleIncludeTag".Originally Posted by mwmitchell
Another note on handling namespaces -- I was thinking about allowing for multiple TagHandlers that get "registered" with the template each one is associated with a particular namespace. The XMLTemplate class would have an associative array of tag handlers where the namespace is the key a reference to the tag handler class is the value.
JT




From some version of php 4 on up, "Call-time pass-by-reference" has been depreciated. So I get warnings when trying to use it.Originally Posted by seratonin
matt



I am using version 4.3.4 without any warnings. What lines are the warnings on? You might be able to suppress these warnings by putting an "@" in front of the "call_user_func" in the endElement method.Originally Posted by mwmitchell
JT




Hey! It's line 19 and 64. I tried using @ but it didn't stop the errors from popping up.Originally Posted by seratonin
Matt


I tried to make an SAX/DOM XHTML templating engine a while ago to mimic JSP. The farthest I got was a half-implementation because the SAX stacking was not working properly (nested tags were'nt always being accounted for at all) so i'd be interested to checkout some of these experimental systems.
Great implementation, but I got some questionsOriginally Posted by seratonin
how would you deal with a template like this? -Supposing that we got a multi-dimensional array (4 rows, 7 columns) representing a month-
The nested tag problem is solved, but would the ListTagHandler class have to instatiate a TagHandler class (or VariableTagHandler class) to replace the value in the template? Now, the XMLTemplate constructor would receive a $tagHandlers array instead of only a handler per template?HTML Code:<html> <body> <table> [b]<list name="month" as="week">[/b] <tr> [b]<list name="week" as="day">[/b] <td><variable name="day"/></td> [b]</list>[/b] </tr> [b]</list>[/b] </table> </body> </html>
vitxo.




I think the Composite design pattern may have its place here.
In a ListTagHandler class you need to be able to treat 'regular' variable blocks (<variable name="day"/>) the same way as list blocks, if/else blocks, etc. That's in any *TagHandler class for that matter.
I'm not sure how exactly your classes work, but perhaps you can get some inspiration from [url=http://www.google.com/search?q=composite+design+pattern&ie=UTF-8&oe=UTF-8&hl=nl&lr=]Google[/google] on the subject.
I've been thinking (again) that I've planned to "compile" my XML language to PHP, to avoid parsing at runtime. So, I just need to care about translating the template special tags to PHP, the compiled template would contain the php presentation logic and i wouldn't need TagHandlers?!



You could use the XMLTemplate class implementation to do the custom tag -> PHP replacement during the "compilation" process.Originally Posted by viTxo
As far as handling a list as above, I believe you would have a ListTagHandler as you said which gets called when the end "list" tag is encountered by the parser. The ListTagHandler (which extends TagHandler) would then take the name attribute ($attrs['name']) and find the appropriate array/list from the vars associative array ($this->vars[$attrs['name']]). Then it would loop through each element and create a template for each element of the list you would recursively call template->parse on the contents of the list tag and concatenate the result together. Or you could do as lastcraft suggested and push the work onto a stack and post-process it to avoid recursion.
JT
Last edited by seratonin; Jan 16, 2004 at 18:15.

Basically we're "there" in WACT (I've just sent what I hope will be the first release to "the board" for approval so there may even be that release finally).
@lastcraft and @captain proton as they're comments pretty much summarized the approach WACT uses (and it works well I think, Jeff having come up with an excellent design).
We used XML_HTMLSax as the SAX parser on top of which is an XML_Pull implementation which basically means instead of having seperate SAX callback methods, you can handle all XML "events" in a single while loop, allowing you to handle the template as a single steam.
On encountering each tag, the parser looks up a class to which it delegates the job of "what to do" with the tag, while adding the instance as a child to the tree of objects being built from the template. Each of these objects writes PHP to a compiled template meaning you end up with fast, mainly procedural PHP at runtime.
The parser loop only has to deal with the current "Tag Object" and it's parent which, as lastcraft mentions, is probably the best way to go (I've tried this kind of thing with SaxFilters before and XUL, but you end up having to "pass around" the current point in the tree which ends up painful).
Anyway - have a look. Once the release is out, we're looking for developers...
Bookmarks