protected $tag_prefix; // an associative array to handle tag names and // their compiling class names protected $tag_handlers = array(); // a timer protected $start_time = 0; protected $end_time = 0; // construct public function __construct() { // add the supported tags in $this->addTag('for'); $this->addTag('foreach'); $this->addTag('print'); $this->addTag('import'); // set the tag prefix $this->setPrefix('fa:'); // begin a timer $this->start_time = microtime(); } // set tag prefix final public function setPrefix($prefix = 'fa:') { $this->tag_prefix = preg_quote($prefix); } // add tag handlers given a tag name final public function addTag($tag_name) { // figure out the class name $class_name = ucfirst($tag_name) .'Node'; // make sure the class exists if(!class_exists($class_name)) throw new Exception("Tag handler class for [{$class_name}] does not exist or cannot be included."); // add the class to the tag handlers under the // tag name $this->tag_handlers[$tag_name] = $class_name; } // get a tag handler class name given the lower // case tag name final public function getTagHandler($tag_name) { // default to the unknown node tag handler $ret = 'UnknownNode'; // check if this tag is set if(isset($this->tag_handlers[$tag_name])) $ret = $this->tag_handlers[$tag_name]; return $ret; } // parse some text final public function parse($buffer) { $buffer = $this->parseTags($buffer); $buffer = $this->parseVariables($buffer); $this->end_time = microtime(); return $buffer; } // get how long it took to parse final public function getParseTime() { list($start_sec, $start_micro) = explode(" ", $this->start_time); list($end_sec, $end_micro) = explode(" ", $this->end_time); $start = (float)$start_sec .".". $start_micro; $end = (float)$end_sec .".". $end_micro; return (float)round(($end - $start), 4); } // parse the tags final protected function parseTags($buffer = '') { // split up the buffer into tags and text $parts = preg_split("~<(/?)". $this->tag_prefix ."([^>]*)>~i", $buffer, -1, PREG_SPLIT_DELIM_CAPTURE); // create a node stack, this will keep track of // non-text nodes, and push our root node onto // it as the first node. $stack = new NodeStack; $stack->push(new RootNode('')); // note: when the buffer is split up, it will // end up in this format: (do a print_r($parts)) // //[0] text node //[1] / or nothing //[2] tag and arguments // .. repeat this process .. // // observations we can make: // every 0th key will be a text node. // the 1st key will tell us if it's a closing tag // the 2nd key will have the extra info // store the parsed buffer $parsed_buffer = ''; // keep track of whether we are looping over a closing // tag or not $closing = FALSE; //print_r($parts); exit; // loop through the split up parts of the buffer and // also increment a tracker for($i = 0; $i < count($parts); $i++) { // get the last node added to the stack, the first // iteration will always have this as the root node $parent = $stack->last(); // the results of this will tell us what type of // node we are on $key = $i % 3; // text nodes if($key == 0) { // add this text to the parent nodes buffer $parent->addTextToBuffer($parts[$i]); // reset closing to false so that future tags // don't get popped out for no reason. $closing = FALSE; } // normal tags else { // split the tag up into its arguments $tag_parts = preg_split("~([^\W]*)(=(\"|')?([a-z0-9\s.-_]*)(\"|')?)~i", $parts[$i+1], -1, PREG_SPLIT_DELIM_CAPTURE); // try to get the tag name $tag_name = trim(strtolower($tag_parts[0])); // parse out the arguments from the split up tag parts $arguments = $this->parseTagArguments($tag_parts); // a closing tag has been found, pop it out of // the stack if($parts[$i] == '/') { // tell the next iteration that this tag was // a closing tag $closing = TRUE; // need to pop the current node off the stack // otherwise we will end up adding text to itself $node = $stack->pop(); // the new parent is the new last node in the stack $parent = $stack->last(); // whoops, we have a finishing tag without a starting // one or a malformed one! if($node->getName() != $tag_name) { // reset the stack to where it was $stack->push($node); $parent = $stack->last(); // add in a HTML error $parent->addTextToBuffer(''); } else { $parent->addTextToBuffer($node->parseBuffer()); } } // and opening tag, push it onto the stack if(!$closing) { // get the tag handler class name given the tag name $class_name = $this->getTagHandler($tag_name); // instanciate the appropriate node class $node = new $class_name($tag_name); $node->addArguments($arguments); // is this a non-closing tag? Parse it without ever // putting it on the stack if(trim($tag_parts[count($tag_parts)-1]) == '/') $parent->addTextToBuffer($node->parseBuffer()); // normal opening tag else $stack->push($node); } $i++; } } $temp_buffer = ''; while($node = $stack->pop()) // is this the root node? if($node instanceof RootNode) { // add any text back into it from malformed // tags then parse it. $node->addTextToBuffer($temp_buffer); $parsed_buffer .= $node->parseBuffer(); } // extract the buffers from the malformed tags // and put them back into the root node later. else $temp_buffer .= $node->extractBuffer(); return $parsed_buffer; } // parse variables final protected function parseVariables($buffer) { $buffer = preg_replace('~{\$([a-zA-Z_-][a-zA-Z0-9_-]*)}~', 'pullVar("$1"); ?>', $buffer); $buffer = preg_replace('~{\@([a-zA-Z_-][a-zA-Z0-9_-]*)}~', '$scope->pullVar("$1")', $buffer); return $buffer; } // parse the tag arguments from a split up array final public function parseTagArguments($parts = array()) { unset($parts[0]); $parts = array_values($parts); $arguments = array(); for($i = 0; $i < count($parts); $i++) { if(isset($parts[$i+3])) $arguments[$parts[$i]] = $parts[$i+3]; $i += 5; } return $arguments; } } // a stack to keep track of non-text nodes class NodeStack { protected $stack = array(); protected $key = -1; public function push(ClosingNode $node) { $this->stack[] = $node; $this->key++; } public function pop() { $ret = FALSE; $this->key--; return array_pop($this->stack); } public function last() { return $this->seek($this->key); } public function seek($index = 0) { $ret = FALSE; if(isset($this->stack[$index])) { $ret = $this->stack[$index]; $this->key = $index; } return $ret; } } // our base node class, all node compiling classes // must extend this class. abstract class ClosingNode { protected $buffer = ''; protected $name = ''; protected $arguments = array(); public function __construct($name = '') { $this->name = $name; } final public function addArguments($array) { $this->arguments = array_merge($this->arguments, $array); } final public function addTextToBuffer($buffer = '') { $this->buffer .= $buffer; } final public function parseBuffer() { return $this->parseOpen($this->arguments) . $this->buffer . $this->parseClose(); } final public function extractBuffer() { return $this->buffer; } final public function getName() { return $this->name; } abstract public function parseOpen($args = array()); abstract public function parseClose(); } // non-closing tags only require parseOpen functions abstract class NonClosingNode extends ClosingNode { final public function parseClose() { return ''; } } // the root node class RootNode extends ClosingNode { public function parseOpen($args = array()) { return ''; } public function parseClose() { return ''; } } // an unknown node class UnknownNode extends ClosingNode { public function parseOpen($args = array()) { return ''; } public function parseClose() { return ''; } } ?>