<?php
/*
+------------------------------------------------------------------------+
| Logic Template Engine (V2.01) |
+------------------------------------------------------------------------+
| Filename: TemplateParser.class | Copyright ©2000-2001 Servability Ltd. |
+--------------------------------+---------------------------------------+
| Description |
| ----------- |
| A Parser Class that Parses special template files. It examines and |
| replaces text that is enclosed in a logic tag. The V2 includes |
| logic support with inline logic statements. It also includes logic |
| comparison, nestled conditional and file repitition (row) abilities. |
+------------------------------------------------------------------------+
| Contributors: James Moore <jmoore@servability.com> |
| James Kent <jkent@servability.com> |
+------------------------------------------------------------------------+
Copyright (c) 2000-2001, Servability Ltd.
http://www.servability.com/products/free/ info@servability.com.
All rights reserved.
Redistribution and use in source and binary (including compiled) forms, with
or without modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary (including compiled) form must reproduce the above
copyright notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Neither the name of the Servability Ltd. nor the names of its contributors
or maintainers may be used to endorse or promote products derived from this
software without specific prior written permission.
Use of the contact information contained for unsolicited communication (spam)
is strictly prohibited.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
Important Notice:
*
* If you make any changes to this template parser we ask that you
* send a copy of them (along with a description) to the maintainer.
* This is so we can distribute your changes and additions with the
* class itself for others to benefit from too. Credit will be given
* if requested.
*
* We also ask that you submit the URL of your site on our website -
* it's nice to know how many people are using it and where.
*
* Free support cannot be given by e-mail. Please visit our website
* for further information. http://www.servability.com/products/free/
*
* Maintainer: Free Products Team <free-products@servability.com>
* (Please put: Logic Templates in the subject).
CVS Info:
*
* $Id$
*
TODO:
*
* - Separate out the parser and the template handler to
* use callbacks.
* - Sort out whitespace handling within the template commands.
* - add foreach functionality.
* - enable escaping of [% to use \[\% which will be changed to
* - [% and left in the text by the parser itself.
* - Add support for !
* - Allow variables both sides of the operator.
* - Remove parsing inside of html comments <!-- --> if practical.
Usage:
*
* This template class provides full logical support with in templates. Template tags
* take the form [%<statement>%] the statement can be just a variable name which will
* be substituted straight off or can be in the form <IF|ELSEIF|ELSE> <VAR> which will
* turn the parser on or off depending if the variable is set or not. Finally you can
* use the form IF SOMEVAR == 5 which will turn the parser on if this is true and off
* if this is false. The possible operands are ==,>=,!=,<=,>,<.
*
* SAMPLES
* -------
* -- templates/test4.tpl --
[%IF TEST4_VAR1 == 1 %]
TEST 4 HAS PASSED PART 1
[%IF TEST4_VAR2 == 1 %]
TEST 4 HAS FAILED
[%ELSEIF TEST4_VAR2 == 2 %]
TEST 4 HAS PASSED PART 2
[%IF TEST4_VAR3 == 1 %]
TEST 4 HAS FAILED
[%ELSEIF TEST4_VAR3 == 2 %]
TEST 4 HAS FAILED
[%ELSE%]
TEST 4 HAS PASSED PART 3
[%ENDIF%]
[%ENDIF%]
[%ELSE%]
TEST 4 HAS FAILED
[%ENDIF%]
* -- test4.php --
<?php
$test4 = new TemplateParser("./templates/"); // relative path
$test4->addtemplate("template4","test4.tpl"); // add one or more templates (array)
$test4->define("TEST4_VAR1","1"); // define variables
$test4->define("TEST4_VAR2","2");
$test4->define("TEST4_VAR3","3");
$test4->define("TEST4_VAR4","4");
$test4->parse("test4out","template4"); // parse template4 into variable test4out
echo $test4->output("test4out"); // output the result
?>
*
*
*
* -- test5row.tpl --
[%TEST5_VAR1%]<br>
* -- test5.tpl --
[%TEST5_ALLROWS%]
* -- test5.php --
<?php
// in this example a simple loop produces 10 instances of test5row.tpl, each parsed
// appending into TEST5_ALLROWS. Then test5.tpl is parsed, replacing TEST5_ALLROWS
// with the full set of rows.
$test5 = new TemplateParser("./templates/");
$test5->addtemplate("template5","test5.tpl");
$test5->addtemplate("TEST5_ROW",strtolower("test5row.tpl")); // add row template
FOR ($i=0; $i < 10; $i++) { // simple example loop
$test5->define("TEST5_VAR1", $i); // simple definition making use of loop
$test5->parse("TEST5_ALLROWS","TEST5_ROW",1); // additional parameter 1 for recursion
}
$test5->parse("test5out","template5");
$output = $test5->output("test5out"); // example, make Engine output to PHP variable
echo $output; // output it
echo "<!-- " . strlen($output) . " chars -->"; // output it's length as an html comment
?>
*/
/* -- Class Entry Point -- */
class TemplateParser {
/* -- Public Variables -- */
var $version = "2.01"; /* -- Current Version -- */
var $apiversion = "25032001"; /* -- API Version -- */
/* -- Private Variables -- */
var $defined = array(); /* -- Array of defined values -- */
var $templates = array(); /* -- Array of Templates -- */
var $path = "./"; /* -- Path of template dir -- */
var $parseflag = array(); /* -- Flag if Parse is on/off -- */
var $flagset = array(); /* -- Flag if parser has been on/off -- */
var $level = 0; /* -- Current Nesting level of ifs -- */
var $line = 0; /* -- Current Line in template -- */
var $line_offset = 0; /* -- Current line offset -- */
var $template_buff = array(); /* -- Buffer for template -- */
var $output_buff = ""; /* -- Output Buffer -- */
var $opening_tag = "[%"; /* -- Opening Tag -- */
var $closing_tag = "%]"; /* -- Closing Tag -- */
var $last_char = ""; /* -- Holder for last char fetched -- */
var $last_token = ""; /* -- Holder for last token fetched -- */
var $tok_sep_char = " "; /* -- a character that separates
tokens shuch as | -- */
var $debug = FALSE; /* -- Is debugging on -- */
var $template_name = ""; /* -- holds current templatename -- */
/* -- Construction Function -- */
function TemplateParser($path)
{
/* -- Define some nice constants -- */
define("PARSER_ON", TRUE);
define("PARSER_OFF", FALSE);
define("SUCCESS",TRUE);
define("FAILED", FALSE);
define("EOF", -1);
/* -- Define the template path file -- */
$this->path = $path;
}
/* -- Public Functions -- */
/* {{{ int addtemplate(char $name[, char $file])
adds a template to the catalog, accepts either
an array of single template name */
function addtemplate($name, $file="")
{
/* -- Handle being passed an array -- */
if(is_array($name))
{
while((list($tempname, $filename) = each($name)) && ($ret == SUCCESS))
{
/* -- Call wrapping function -- */
$ret = $this->templateadd($tempname, $filename);
}
}
else {
/* -- Handle being passed a single template -- */
$ret = $this->templateadd($name, $file);
}
return $ret;
}
/* }}} */
/* {{{ void define(char name[, char contents[, int flag]]
Defines a constant to be usr in the parseing of the
script. Can be passed an array or a single value*/
function define($name, $contents="", $flag=0)
{
/* -- Handle being passed an array -- */
if(is_array($name))
{
while(list($constname, $constvalue) = each($name))
{
/* -- If final arguement is 1 then append to previous
constant that was defined -- */
if($flag)
{
/* -- Append value to defined array -- */
$this->defined[strtolower($constname)] .= $constvalue;
}
else {
/* -- Write Value to defined array -- */
$this->defined[strtolower($constname)] = $constvalue;
}
}
}
else {
/* -- If final argument is 1 then append to previous
constant that was defined -- */
if($flag)
{
/* -- Append values to defined array -- */
$this->defined[strtolower($name)] .= $contents;
}
else {
/* -- Write Value to the defined array -- */
$this->defined[strtolower($name)] = $contents;
}
}
}
/* }}} */
/* {{{ int parse(char reference, char template, int flag)
Starts the parsing process of a template */
function parse($reference, $template, $flag=0)
{
/* -- Clear all vars used and reset to original values -- */
$this->template_buff = array();
$this->line = 0;
$this->line_offset = 0;
$this->parseflag = array(PARSER_ON);
$this->flagset = array(PARSER_OFF);
$this->level = 0;
$this->output_buff = NULL;
$this->template_name = $template;
/* -- Get contents of template and assign to needed vars -- */
/* -- Put contents of template into template_buff -- */
$this->template_buff = explode("\n", $this->templates[strtolower($template)]);
/* -- Start PARSER -- */
/* -- Check for special cases where template starts with an instruction -- */
if(($this->template_buff[$this->line][$this->line_offset] == $this->opening_tag[0])
&& ($this->template_buff[$this->line][($this->line_offset + 1)] == $this->opening_tag[1]))
{
/* -- If it does then move to the begining of the instruction and call handler -- */
$this->line_offset += 2;
$this->handle_instruction();
}
else {
/* -- The template starts with text so lets send it to the correct handler -- */
$this->handle_text();
}
if($flag) {
$this->defined[strtolower($reference)] .= $this->output_buff;
}
else {
$this->defined[strtolower($reference)] = $this->output_buff;
}
return SUCCESS;
}
/* }}} */
/* {{{ char output(char reference)
Returns the value of reference to
the caller */
function output($reference)
{
return $this->defined[strtolower($reference)];
}
/* }}} */
/* -- Private Functions -- */
/* {{{ bool templateadd(char templatename, char templatefile)
Internal array wrapper to add templates */
function templateadd($templatename, $templatefile)
{
/* -- Check to see if file exists -- */
if(file_exists($this->path.$templatefile))
{
if($fp = fopen($this->path.$templatefile, "r"))
{
/* -- Lock the file -- */
flock($fp, LOCK_SH);
/* -- Read the file -- */
$contents = fread($fp, filesize($this->path.$templatefile));
/* -- Unlock the file -- */
flock($fp, LOCK_UN);
/* -- Close the file -- */
fclose($fp);
/* -- Add to template catalog -- */
$this->templates[strtolower($templatename)] = $contents;
return true;
}
else {
/* -- Could not open file -- */
if($this->debug)
{
echo ("could not open file ".$this->path.$templatefile);
}
return false;
}
}
else {
/* -- File does not exist -- */
if($this->debug)
{
echo ("could not find file ".$this->path.$templatefile);
}
return false;
}
}
/* }}} */
/* {{{ bool evaluate(char defined, char operator, char operand)
evaluates an expression passed in three parts, the
defined value is the first operand, the operator
is the logical statement and the senond operand
is the value for the first to be compared with -- */
function evaluate($defined, $rval, $operator)
{
$lval = $this->defined[strtolower($defined)];
switch($operator){
case ">":
$retval = (($lval > $rval)?TRUE:FALSE);
break;
case "<":
$retval = (($lval < $rval)?TRUE:FALSE);
break;
case ">=":
$retval = (($lval >= $rval)?TRUE:FALSE);
break;
case "<=":
$retval = (($lval <= $rval)?TRUE:FALSE);
break;
case "==":
$retval = (($lval == $rval)?TRUE:FALSE);
break;
case "!=":
$retval = (($lval != $rval)?TRUE:FALSE);
break;
default:
echo("$lval, Your using an operator I dont understand");
}
return $retval;
}
/* }}} */
/* {{{ void rewind()
rewinds the line pointers */
function rewind()
{
if($this->line_offset != 0)
{
$this->line_offset--;
$this->last_char = " ";
}
else {
$this->line--;
$this->line_offset = strlen($this->template_buff[$this->line]);
$this->last_char = " ";
}
return;
}
/* }}} */
/* {{{ void skip_blanks()
Skips all spaces/tabs until next char */
function skip_blanks()
{
$this->get_char();
/* -- Loop until we get a non whitespace char -- */
while(ereg("\s",$this->last_char))
$this->get_char();
/* -- Rewind the pointers -- */
$this->rewind();
return;
}
/* }}} */
/* {{{ char get_token()
Returns the next token in the file */
function get_token()
{
/* -- Reset variables -- */
$this->last_token = NULL;
/* -- Get Charaters -- */
while($this->get_char()) {
if(($this->last_char == $this->tok_sep_char) || (ereg("\s",$this->last_char))
|| (($this->last_char == $this->closing_tag[0])
&& ($this->template_buff[$this->line][$this->line_offset] == $this->closing_tag[1])))
{
if($this->last_char == $this->closing_tag[0])
{
$this->rewind();
}
return;
}
else {
$this->last_token .= $this->last_char;
}
}
}
/* }}} */
/* {{{ char get_char()
Gets the next char in the buffer */
function get_char()
{
/* -- Check to see if we are at the end of the file -- */
if($this->line == sizeof($this->template_buff))
{
return EOF;
}
/* -- Get char and set return value -- */
$retval = $this->last_char = $this->template_buff[$this->line][$this->line_offset];
/* -- Replace a \n and check if at the end of a line -- */
if($this->line_offset == strlen($this->template_buff[$this->line]))
{
if($this->parseflag[$this->level] == PARSER_ON)
$this->output_buff .= "\n";
$this->line++;
$this->line_offset = 0;
}
else {
$this->line_offset++;
}
return $retval;
}
/* }}} */
/* {{{ void handle_text()
Handles the parseing of text/html */
function handle_text()
{
/* -- Offset should not be at the first char of text -- */
while($this->get_char() != EOF)
{
if(($this->last_char == $this->opening_tag[0]) && (
$this->template_buff[$this->line][$this->line_offset] == $this->opening_tag[1]))
{
/* -- Get the final tag opening char -- */
$this->get_char();
/* -- Pass to handler -- */
$this->handle_instruction();
/* -- When handle instruction returns, so do we -- */
return;
}
else {
if($this->parseflag[$this->level] == PARSER_ON)
{
$this->output_buff .= $this->last_char;
}
}
} /* -- Get the next charater -- */
return; /* -- Occurs when get char returns flase -- */
}
/* }}} */
/* {{{ void handle_instruction()
Handles the processing of instructions */
function handle_instruction()
{
/* -- Offset is just after tag -- */
/* -- Skip and whitespace -- */
$this->skip_blanks();
$this->get_token();
/* -- Find which token it is -- */
switch (strtoupper($this->last_token))
{
case "IF":
++$this->level;
if(($this->parseflag[(($this->level)-1)] != PARSER_ON)) /* -- The Parser is off -- */
{
$this->parseflag[$this->level] = PARSER_OFF; /* -- Turn parser off -- */
$this->flagset[$this->level] = PARSER_ON; /* -- Ignore and other logic statements
at this level of nesting -- */
break; /* -- Jump out of switch -- */
}
else {
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$var = $this->last_token; /* -- Assign token -- */
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$operator = $this->last_token; /* -- Assign Token -- */
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$operand = $this->last_token; /* -- Assign Token -- */
$this->skip_blanks(); /* -- Jump to what should be the closing tags -- */
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
if ($operator && $operand)
{
if($this->evaluate($var, $operand, $operator)) /* -- Evalute expression -- */
{
$this->parseflag[$this->level] = PARSER_ON;
$this->flagset[$this->level] = PARSER_ON;
break; /* -- Jump out of switch -- */
}
else {
$this->parseflag[$this->level] = PARSER_OFF;
$this->flagset[$this->level] = PARSER_OFF;
break;
}
}
else {
if($this->defined[strtolower($var)])
{
$this->parseflag[$this->level] = PARSER_ON;
$this->flagset[$this->level] = PARSER_ON;
break; /* -- Jump out of switch -- */
}
else {
$this->parseflag[$this->level] = PARSER_OFF;
$this->flagset[$this->level] = PARSER_OFF;
break;
}
}
}
break; /* -- End of IF switch -- */
case "ELSEIF":
/* -- Check to see if we need to bother -- */
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$var = $this->last_token; /* -- Assign token -- */
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$operator = $this->last_token; /* -- Assign Token -- */
$this->skip_blanks(); /* -- Skip blanks before next token -- */
$this->get_token(); /* -- Get token -- */
$operand = $this->last_token; /* -- Assign Token -- */
$this->skip_blanks(); /* -- Jump to what should be the closing tags -- */
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
if($this->flagset[$this->level] == PARSER_ON)
{
$this->parseflag[$this->level] = PARSER_OFF;
}
else {
if ($operator && $operand)
{
if($this->evaluate($var, $operand, $operator)) /* -- Evalute expression -- */
{
$this->parseflag[$this->level] = PARSER_ON;
$this->flagset[$this->level] = PARSER_ON;
break; /* -- Jump out of switch -- */
}
else {
$this->parseflag[$this->level] = PARSER_OFF;
$this->flagset[$this->level] = PARSER_OFF;
break;
}
}
else {
if($this->defined[strtolower($var)])
{
$this->parseflag[$this->level] = PARSER_ON;
$this->flagset[$this->level] = PARSER_ON;
break; /* -- Jump out of switch -- */
}
else {
$this->parseflag[$this->level] = PARSER_OFF;
$this->flagset[$this->level] = PARSER_OFF;
break;
}
}
}
break; /* -- End of elseif -- */
case "ELSE":
if(!($this->flagset[$this->level] == PARSER_ON))
{
$this->parseflag[$this->level] = PARSER_ON;
//$this->parseflag[$this->level] = PARSER_ON;
} else {
$this->parseflag[$this->level] = PARSER_OFF;
}
$this->skip_blanks();
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug)
{
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
break; /* -- End of Else -- */
case "ENDIF":
$this->level--;
$this->skip_blanks();
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug)
{
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
break;
default:
if($this->parseflag[$this->level] == PARSER_ON)
{
$this->output_buff .= $this->defined[strtolower($this->last_token)];
}
$this->skip_blanks();
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug)
{
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
$this->get_char(); /* -- Jump past first end tag -- */
if($this->debug) {
if($this->last_char != $this->closing_tag[0])
{
prinf("Error: CLosing tag expected at line: %d in Template: %s <br>",$this->line, $this->template_name);
printf("Cannot recover exiting... <br>");
exit();
}
}
break; /* -- End of default handler -- */
} /* -- End of switch -- */
$this->handle_text();
return;
}
/* }}} */
} /* -- End of class -- */
?>
|