Final post in a row. This is meant to illustrate the tag creation process so that you can (hopefully) see it is not too terribly complicated to parse a new tag.
In this example I settled on how I wanted to handle escaping basic html attribute values such as <tagname attribute = "attribute value"/>. The problem is that we cannot allow new lines or quotes to appear in these areas, and the solution must be clear, but short as it should be easy to type and yet flexible enough to meet the problem.
I settled on the tag-name: "quote" because it describes exactly where the tag should be used. Its function should be to replace " with " and ' with ' which allows javascript to execute correctly still and correctly escapes those characters.
Newline handling posed to be the most difficult problem. There may be cases where you want to replace them with \n, or cases where you want to replace them with <br/> or even cases where you simply want to ignore them entirely.
I decided that this tag should take one optional parameter which is the string to replace newlines with. If this parameter is not present the default behavior will be to simply ignore newlines.
So I wrote up two very simple unit tests which cover the features I'm aiming for:
Code: Select all
function TEST_QuoteTest1(){
$engine = new MutedTemplate(false);
$result = $engine->parse('1{quote|\'Hello'."\n".' World"}2');
$expected = '1'Hello World"2';
return $this->getResult($result, $expected);
}
function TEST_QuoteTest2(){
$engine = new MutedTemplate(false);
$result = $engine->parse('1{quote|<br/>|\'Hello'."\n".' World"}2');
$expected = '1'Hello<br/> World"2';
return $this->getResult($result, $expected);
}
Another practical situation to use this may be in assigning a string to a variable in javascript.
Code: Select all
var value = "{quote|\n|A duck
And a truck
Are out of luck}";
So I have a plan, I have some good use-cases and unit tests which I want to pass. I'll do a rough description of how the control flow of the template language works:
For every character from a source we parse to see if it follows the {tagname| syntax, as soon as something like that is encountered it detects if the tag has been registered to a class, and if it has been then the template engine creates an object of that type. Now, every time it loops over a character the template function calls the "parentCallback" function which the tag class inherited from the default tag director class called "MutedTag". The parentCallback function has a few purposes, it basically handles escape character sequences, new tags being opened, and then in the case of an unhandled character (not escaped, not a new tag) it passes that along to the childCallback function (the specific function that it calls can be changed by the child class.) When the tag has determined it is done parsing it wraps up and signals that it is complete and the template engine destroys the object and returns to its basic parsing routine.
Output is put into a multi-level buffer via the $this->output function which allows for arbitrarily deep tag nesting (output from one tag is passed down to the next buffer). The top level of the buffer is what is eventually displayed.
On to the actual tag. I've added a number of comments detailing the purpose of each line.
Code: Select all
<?PHP
if(!defined('_M2tM_QUOTETAG_PHP_')){
define('_M2tM_QUOTETAG_PHP_', true);
//Use Examples:
//<a onclick = "{quote|alert("Hello World");}">Execute</a>
//or
//<a onclick = "{quote|\n|alert("Hello
//World");}">Execute</a>
//The first parameter in the quote tag is what to replace new lines with.
//The first parameter is optional.
class MutedTag_Quote extends MutedTag{
function MutedTag_Quote(&$mutedTemplate, $tagName, $returnCallback = NULL){
parent::MutedTag($mutedTemplate, $tagName, $returnCallback);
$this->startEscape(array('{', '\\', '|', '}')); //Here we set up the escape-able characters.
$this->startBuffer(); //Initiate a buffer space so we can store output for processing
$this->newlineReplace = '';
$this->needEndEscape = true;
}
//This is the tag parse entry point, and characters are passed through this callback by default
function childCallback(&$stream){
if($stream->getCurrent() === '|'){ //Detect the optional parameter
$this->newlineReplace = $this->endBuffer(); //get the newline replace string
$this->endEscape(); //We no longer need to worry about '|' so we can go to default escape chars
$this->startBuffer(); //set up a new buffer for our output.
$this->needEndEscape = false;
}else if($stream->getCurrent() === '}'){ //this case means there was no newline replacement
//here we take the contents of the buffer and replace the desired characters.
$this->output(str_replace(array('"',"'","\n"), array('"',''',$this->newlineReplace), $this->endBuffer()));
if($this->needEndEscape){ //if we had an optional parameter we don't want to end the escape
$this->endEscape();
}
parent::closeTag(); //clean up and return control
}else{
$this->output($stream->getCurrent()); //default case, dump contents into buffer.
}
}
var $newlineReplace;
var $needEndEscape;
}
}
?>