You may ask: Why a template language if we have PHP? Isn't it a template language itself? Good question! Of course, once PHP started as a template language and was designed just to be something like executable HTML. Years are gone and nowadays PHP is a powerful scripting language with a huge standard library and an even greater number of extensions. Today it is bad practice to mix PHP and (X)HTML since web development is all about separating code, design and content. In a project the designer and the developer may be separate persons and the designer may not know enough about PHP. In another project it may be a considderation of security not to let those who write a template also have complete access to the internals of the application or to the backend. PHP is still not supporting sandboxes. This is why we need a special template language.
T24 is simple and straightforward and is part of the Ister PHP
Framework. It is the main goal to keep the language simple. T24
must not grow over the time to become a new scripting language
implemented in a scripting language. This is why the current state
of the language is frozen. New functions will only be added if it
is possible to get them nearly for nothing (like :replace or :dump, added in 0.5.0).
The parser is implemented as a recursive descent parser. The grammar is kept simple and in a way customizable.
Here we write the T24 grammar in EBNF notation. We use character classes to keep the notation small.
STREAM = TEXT*, TAG*, TEXT* TEXT = ASCII* TAG = ( OPEN, CLOSE ) | ( OPEN, ( FUNC | VAR ), CLOSE ) OPEN = NONSPACE+ CLOSE = NONSPACE+ FUNC = SPACE*, FUNCSEP, FUNCNAME, SPACE+, PARAMS*, SPACE* FUNCSEP = PUNCT FUNCNAME = FCHARS+ VAR = VARNAME, SPACE+, PARAMS, SPACE* VARNAME = FCHARS, VCHARS* PARAMS = PARAM | ( PARAM, SPACE+, PARAMS ) PARAM = STRING | NAME; NAME = NCHARS STRING = STRINGSEP, NCHARS*, STRINGSEP STRINGSEP = "'" | '"' NCHARS = "[^'"]" FCHARS = "[A-Za-z]" VCHARS = "[_A-Za-z1-9]" SPACE = "[:space:]" NONSPACE = "[:^space:]" PUNCT = "[:punct:]" ASCII = "[:ascii:]"
The values for OPEN, CLOSE and FUNCSEP are not given by the
grammar but may be customized with the setProperty() method of IsterTemplate. This is very useful if the
parser is used to parse templates written in another format as
XHTML or XML, such as plain text, TEX or uncompressed PDF. The
default values are given below.
OPEN = "<?t24" CLOSE = "?>" FUNCSEP = ":"
The scope of variables is set to block level. This means that an enclosing block will disclose all of its assignments to the enclosed blocks. For example, variables outside of a repeat block will be known inside of this block, but the variables of the repeat block itself will not be known outside of this block.
The T24 parser has a number of buildin functions.
Assign a value to a variable from within the template.
<?t24 :set scalar 1 ?> <?t24 :set array 1 2 3 4 ?> <?t24 :set string1 '1 2 "3" 4' ?> <?t24 :set string2 "1 2 '3' 4" ?>
Include and parse another template from file or string.
<?t24 :include <path> [file] ?> <?t24 :include <methodname> string [<arg1> [<arg2> [...]]] ?>
Include and do not parse another template.
<?t24 :replace <path> [file] ?>
<?t24 :replace <methodname> string [<arg1> [<arg2> [...]]] ?>
There are different tests for strings and integers.
<?t24 :if <var> ?> // var is true in respect to PHP rules <?t24 :if <var> def ?> // var is defined <?t24 :if <var> z ?> // var has strlen zero <?t24 :if <var> nz ?> // var has a non zero strlen <?t24 :if <var> eq <string> ?> // var eaquels string <?t24 :if <var> neq <string> ?> // var not equals string <?t24 :if <var> == <integer> ?> // var eaquels integer <?t24 :if <var> != <integer> ?> // var not eaquels integer <?t24 :if <var> > <integer> ?> // var is greater than integer <?t24 :if <var> < <integer> ?> // var is less than integer <?t24 :if <var> >= <integer> ?> // var is greater or eaqual integer <?t24 :if <var> <= <integer> ?> // var is less or eaqual integer
Define an alternative.
<?t24 :if THIS == 1 ?> <?t24 DO_THIS ?> <?t24 :else ?> <?t24 DO_THAT ?> <?t24 :end ?>
Define a repeat block.
<?t24 :repeat repeater ?> <?t24 COUNTER ?> <?t24 :end ?>
In the example above, "repeater" must be a registered callback method returning an array of name value pairs. Each array then represents one row of a data set. The callback must return null if no more rows are left in the data set.
class Repeat extends IsterObject {
var $counter = 10;
function Repeat {
parent::IsterObject();
}
function repeater {
if(! $counter)
return null;
return array('COUNTER' => --$this->counter);
}
}
End the definition of a block like a conditio or a repeat.
<?t24 :if VAR == 1 ?> <h1>Yippie Yeah!</h1> <?t24 :end ?> <table> <?t24 :repeat list ?> <tr><td><?t24 FOO ?></td><td><?t24 BAR ?></td></tr> <?t24 :end ?> </table>
Invoke a registered method.
<?t24 :invoke example arg1 arg2 arg3 ?> <?t24 :invoke example "a string" ?> <?t24 :invoke example arg1=value "arg2=a string" ?>
The method must be defined and registered as a method of an
extension of IsterObject. The
parameters will be passed to this method in a single array. You may
realize assignments by yourself as shown in the third example but
note the use of quotes to make assignment of strings possible.
class Example extends IsterObject {
function Example {
parent::IsterObject();
}
function example($params) {
foreach( $params as $param ) {
if( preg_match('^(.+)=(.+)$', $param, $matches) )
list($name, $value) = $matches;
else
print $param;
}
}
}
The :dump function prints a
representation of all variables currently defined in the template
object. This representation is formatted as an associative PHP
array. The function may be useful for debugging or during
development when the template author has not yet finished work and
the PHP developer needs to test a form or something.
<?t24 :dump ?>
Note: currently this only works outside of repeat blocks.
As a very simple example we use the following html template and save it as simple.tmpl.
<html> <head> <title><?t24 TITLE ?></title> </head> <body> <h1><?t24 TITLE ?></h1> </body> </html>
To parse this template and send the resulting document to the client all you need are the following lines.
<?php
require_once('IsterTemplate.php');
require_once('IsterTemplateFactory.php');
$t = new IsterTemplate( new IsterTemplateFactory );
$t->setLanguage('t24');
$t->setProperty('/template', array('TITLE' => 'Test Template');
$t->parse('simple.tmpl');
print $t->toString();
?>
And this is what the client sees.
<html> <head> <title>Test Template</title> </head> <body> <h1>Test Template</h1> </body> </html>
Not only files but also strings may be used as templates. That way it is possible to store templates in databases. To tell the template object that the string is not the name of a file, the constant ISTER_TEMPLATE_STRING must be added to the method call.
$t->setProperty('/template', array('WHO' => 'World'));
$t->parse('Hello <?t24 WHO ?>!', ISTER_TEMPLATE_STRING);
print $t->toString();
To assign one or more values to a template variable the setProperty method of IsterTemplate is used. The path must be
written as "/template" and an additional array with name value
pairs must be passed. It is very easy to populate a whole IsterAttributeSet or an IsterSqlDataObject (which extends IsterAttributeSet) in one line.
<?php
$data = new IsterSqlDataObject;
// do some more setup for this object here
$data->select();
$t = new IsterTemplate( new IsterTemplateFactory );
$t->setLanguage('t24');
$t->setProperty('/template', $data->getAttributesArray());
$t->parse('form.tmpl');
print $t->toString();
?>
This is an example of a callback method and will simply print out "Hello World" twice.
<?php
require_once('IsterTemplate.php');
require_once('IsterTemplateFactory.php');
class Example extends IsterObject {
function Example {
parent::IsterObject();
}
function example($params) {
print $params[0];
}
}
$e = new Example;
$t = new IsterTemplate( new IsterTemplateFactory );
$t->setLanguage('t24');
$t->setProperty('/register/method', array('example' => &$e);
$t->setProperty('/register/alias', array('example' => 'print');
$t->parse('<?t24 :invoke example "Hello World"?>
<?t24 :invoke print "Hello World"?>',
ISTER_TEMPLATE_STRING);
print $t->toString();
?>
There are two different buildin functions to include other
templates: :include and :replace. This may be done up to any
nesting level. The only difference between these two functions is
that an included template is parsed by the template parser and
variables are resolved while a replaced template is not parsed and
variables are not resolved. This may be used to tune
performance.
==== t1.tmpl <?t24 HELLO ?> ==== ==== t2.tmpl World ==== ==== t3.tmpl <?t24 :include t1.tmpl ?> <?t24 :replace t2.tmpl ?> ====
To :include or :replace string templates a callback
method must be used, followd by the keyword "string" and any number
of arguments.
==== t3.tmpl <?t24 :include getString string hello ?> <?t24 :replace getString string who ?> ====
Now we can write a more complex template like this.
<html>
<head>
<?t24 :include head.tmpl ?>
</head>
<body>
<?t24 :replace javascript.tmpl ?>
<h1><?t24 title ?></h1>
<?t24 :if datacount > 0 ?>
<table>
<tr>
<th>name</th>
<th>city</th>
<th>birthday</th>
</tr>
<?t24 :repeat datarows ?>
<tr>
<td><?t24 name ?></td>
<td><?t24 city ?></td>
<td><?t24 birthday ?></td>
</tr>
<?t24 :end ?>
</table>
<?t24 :else ?>
<p>No data found.</p>
<?t24 :end ?>
</body>
</html>
The following example shows the default setup of the parser.
<?php
require_once('IsterTemplate.php');
require_once('IsterTemplateFactory.php');
$t = new IsterTemplate( new IsterTemplateFactory );
$t->setLanguage('t24');
$t->setProperty('/parser',
array(// include path of templates
'include_path' => '.',
// function marker sign
'function_separator' => ':',
// open a template tag here
'tag_open' => '<?t24',
// close a template tag here
'tag_close' => '?>',
// start comment in target format
'comment_open' => '<!--',
// end comment in target format
'comment_close' => '-->',
// what to do with unregistered variables
'policy_unregistered' => t24SKIP
)
);
?>
The include_path may be used to
setup an environment even with different locations of template
files (string templates are not searched in a path). The path may
be written as usual for PHP include paths as a colon separated list
on Unix/Linux and as a semicolon separated list on Windows.
If you are about to write XHTML templates and you are using a
WYSIWYG editor you may decide to change the values for tag_open and tag_close.
<?php
$t->SetProperty('/parser',
array('tag_open' => '{',
'tag_close' => '}'
)
);
?>
Now the template tags will show up even in WYSIWYG mode.
<h1>{ HELLO_WORLD }</h1>
But, of course, this is not that useful for TEX/LATEX - and Javascript.
Unregistered variables are variables occuring in a template but
were not been registered previously to the template parser. With
the policy_unregistered property you
can decide what to do with such variables. The default is set to
t24SKIP which will silently ignore
such variables. Other possible values are t24WARN (trigger a warning for unregistered
variables), t24KEEP (keep the
variable name unchanged in the output string) and t24COMMENT (comment the variable according
to the target format). For the latter you can adjust the comments
with the properties comment_open and
comment_close
The main template class IsterTemplate of the Ister PHP Framework
is not restricted to use T24 as the template language. It may use
any other language as long as this language is accessible through a
class implementing the interface iIsterParser. The parserSetProperty() method of the parser
should understand the paths as described above. In PHP4 the new
parser must additionally extend IsterParser. The interface is declared in
the following way.
interface iIsterParser {
function getIncluded();
function getName();
function getParseResult();
function parseFile($path);
function parseString($string, $name);
function parserSetProperty($path, $array);
function setParseResult($result);
function toString();
}