PDA

Click to See Complete Forum and Search --> : [RESOLVED] PHP Templating Engine Semantics


tomcatexodus
Jun 12th, 2010, 06:59 PM
I'm working on a template engine in PHP to streamline dynamic web development. Technical details aren't really necessary, but I'm just wondering what anyone thinks about the syntax of my "language" and how I could improve it.

There are 3 kinds of tags that can be incorporated into HTML files to use the template system, they are:

Variables


@@var name(myVar) default(Default Value)@@


Components


@@component name(myComponent)@@


and Regions (the one I'm trying to figure out the syntax for, this is what I have so far)


@@regionblock name(myRegionBlock)@@
@@region name(myFirstRegion)@@
.
.
.
@@end_region name(myFirstRegion)@@
@@region name(mySecondRegion)@@
.
.
.
@@end_region name(mySecondRegion)@@
@@end_regionblock name(myRegionBlock)@@


Just a bit of background on the tags;

The var tag takes data that you generate (from a database or text file or whatever) and replaces the instances of it in the HTML file with said data.

The component tag takes another full page, and places it at that location in the HTML file containing the tag (basically an advanced include();)

The regionblock/region tags allow you to define part of a document in different ways, and regions can be duplicated multiple times with different data in each copy. You can have any number of region tags in a regionblock, and they all define different things the page could display at that point.

A good example for regionblock/region usage would be search engine results. One regionblock could contain a region for the results with titles, authors and publish dates for books; another region that says there were no search results; and another one that says there was an error or other issue. Based on what happens in the actual script that calls the database, etc., you define what to show, whether its results or an error or whatever.

Here's an example:


@@regionblock name(searchResults)@@

@@region name(result)@@
<h3>@@var name(bookTitle)@@</h3>
<p>@@var name(bookAuthor)@@</p>
<p>@@var name(bookPublished)@@</p>
@@end_region name(result)@@

@@region name(no_results)@@
<h3>There were no search results for your search. Try something else</h3>
@@end_region name(no_results)@@

@@region name(error)@@
<h3>There was an error connecting to the database.</h3>
<p>@@var name(errorMessage)@@</p>
@@end_region name(error)@@

@@end_regionblock name(searchResults)@@


Anyways, (hopefully that was kinda straight forward) the engine works for the most part, but I'm just concerned about the naming convention.

Should I keep using the @@ characters to delimit the template tags?

What should I call what is currently being called regionblock? (I don't like regionblock) Is there a more.. semantic name? Regionlist? Regionset?

Should I change the name from 'region' to something else altogether? Should I change any of the template tag names?

The regionblock tag uses the name() argument, but so does the regions inside. I think that may be confusing... Should I change any of the argument names?

I greatly appreciate all advice in advance!

TheBigB
Jun 12th, 2010, 07:27 PM
That looks like a solid structure.
I'm working on a template engine too.
It's pretty much the same, but I have a different naming convention.
The default value is a great idea. I might include that in the engine too.

As for the delimiter characters question; it should be fine, but you'll need to consider the engine.
If you want to run it recursively (so that you can include more template mark-up in a variable or something), you might want to add a @@noparse@@ tag for user input.

Of course mind I'm still in designing stages, so I could overlook something here.

Oh, and by the way, would you mind sharing the outlines of the template engine?
I'm happy to share what I have so far.

TheBigB
Jun 12th, 2010, 07:38 PM
Oh, one thing I spotted is that you close the regions with the name as parameter.
I don't know if you allow nested regions.
If you do, I discourage this as it might cause crossing of regions and with that very unexpected results.
Take this example:

// This can come out really badly
@@region name(myFirstRegion)@@
@@region name(mySecondRegion)@@
...
@@end_region name(myFirstRegion)@@
@@end_region name(mySecondRegion)@@

// This should work fine
@@region name(myFirstRegion)@@
@@region name(mySecondRegion)@@
...
@@end_region@@
@@end_region@@

Although to a somewhat trained coder can easily spot the problem here, the latter always prevents this problem.

tomcatexodus
Jun 12th, 2010, 07:47 PM
The interpreter determines the appropriate end region based on the name argument included in the end tag. There are no anonymous (thus possibly confusable) end tags. Much like XML, nesting must be done correctly, otherwise the page will render improperly. I'm working on a logging system to detect and record possible failures like this in the event of poor region nesting.

I'd be happy to share the entire script and manual with you once it's done. Once I get a few things sorted out, it'll be good for deployment. It's purpose wasn't for public use, personal rather, but anyone who wants it can have it.

Thanks for you insight!

kows
Jun 12th, 2010, 10:58 PM
I don't really like the syntax, but that's because @ symbols are ugly. I think it could be easier to deal with (and code with) if you did something XML-esque like Flex and ASP.NET do with a namespace:

<mx:Tag />
<mx:Tag></mx:Tag>

<!-- or -->

<asp:Tag />
<asp:Tag></asp:Tag>

Regarding the "regionblock" and "region" names, I'll give you my thoughts: a region is a section of the page that can have multiple views, or templates. you could do something like:

<ns:region name="search">

<ns:view name="resultSet">
<h3><ns:var name="title" /></h3>
<ul>
<li>Author: <ns:var name="author" /></li>
<li>Year: <ns:var name="year" /></li>
<li>Description: <ns:var name="description" /></li>
</ul>
</ns:view>

<ns:view name="emptyResultSet">
<h3>There were no results</h3>
<p>Perhaps you could try another search query.</p>
</ns:view>

<ns:view name="error">
<h3>An error occurred</h3>
<p><ns:var name="error" /></p>
</ns:view>

</ns:region>

alternatively, you could name variables by using the variable name as the tag name rather than having a 'var' tag -- you would just have reserved variable names. <ns:title />, for example.

tomcatexodus
Jun 12th, 2010, 11:43 PM
...if you did something XML-esque like Flex and ASP.NET do with a namespace:

[code]<mx:Tag />
<mx:Tag></mx:Tag>


Love it. I was going to go with a markup language syntax, but for some reason strayed.. I think it was because you'd need to escape certain characters within the quotes. I'm gonna implement this.


...a region is a section of the page that can have multiple views, or templates.


Also love it. Regions contain views. Perfect.

tomcatexodus
Jun 13th, 2010, 01:05 AM
Alright, I've decided to go with the syntax of:

<zl:var name="varName" default="Default Value" />

Now I'm concerned with the parsing method I've been using. Do you think it would likely be faster to utilize the PHP DOM class to find, parse, and manipulate the template tags, or would it remain efficient to use the RegEx parsing I've used thus far?

kows
Jun 13th, 2010, 02:24 AM
it would be useful if you could use an XML parser, but only if you could tell it to ignore anything outside of the zl namespace. I'm not sure if you can do that or not.

what have you been doing to parse this? when I was doing something similar to this, I was using regular expressions to match tags. I abandoned this project in favour of another, but I have a tag parsing class that might be useful to look at, or something.

tomcatexodus
Jun 13th, 2010, 02:39 AM
(Note: For the moment, I've reverted to the @@type args@@ syntax... but altering it to markup syntax is really very simple in my implementation)

This is a snippet from the compile method, that performs the replacements:


switch($type){

case "region":

foreach($data as $name => $value){

$buffer = preg_replace("/@@ region \s name\(" . $name . "\) @@ (.*) @@ end_region \s name\(" . $name . "\) @@/xsi", $value->compile(), $buffer);

}

break;

case "component":

foreach($data as $name => $value){

$buffer = preg_replace("/@@ component \s name\(" . $name . "\) @@/xi", $value->compile(), $buffer);

}

break;

case "var":

foreach($data as $name => $value){

$buffer = preg_replace("/@@ var \s name\(" . $name . "\) @@/xi", $value, $buffer);

}

break;

}

tomcatexodus
Jun 13th, 2010, 02:43 AM
I haven't tested it, but this is the region constructor:


public function __construct($regionName, &$parentObject, $callbackFunction = false){

$this->name = $regionName;
$this->parent = $parentObject;
$this->parent->data[count($this->parent->data) - 1 > 0 ? count($this->parent->data) - 1 : 0]["region"][$regionName] =& $this;

if($callbackFunction !== false){
$this->callback = $callbackFunction;
}

if(preg_match("/@@ region \s name\(" . $this->name . "\) @@ (?P<views>.*) @@ end_region \s name\(" . $this->name . "\) @@/xsi", $this->parent->template, $region)){
preg_match_all("/@@ view \s name\((?<name>.*)\) @@ (?P<template>.*) @@ end_view \s name\((?P=name)\) @@/xsi", $region["views"], $views);
foreach($views as $view){
$this->template[$view["name"]] = $view["template"];
}
}

}


Edit: I think I need to add the PREG_SET_ORDER flag to the preg_match_all call

penagate
Jun 14th, 2010, 08:42 PM
it would be useful if you could use an XML parser, but only if you could tell it to ignore anything outside of the zl namespace. I'm not sure if you can do that or not.

I would use a XSL stylesheet to transform the template markup into HTML. The XSL and XPath expressions can be composed to target elements and attributes within a particular namespace. Regular expressions are harder to maintain and test and a badly-written expression can easily invalidate the markup. Because your input stream is (should be) well-formed XML, it is best to treat it as such and thus ensure your output is also well-formed.