codepad
[
create a new paste
]
login
|
about
Language:
C
C++
D
Haskell
Lua
OCaml
PHP
Perl
Plain Text
Python
Ruby
Scheme
Tcl
<?php /** * $header * @copyright Copyright 2007, Peter Goodman * @package valkyrie * @subpackage Template.parser * @author Peter Goodman */ define ('VK_TPL_TAGS_DIR', dirname(__FILE__) .'/tags/'); /** * Parse a document for specific XML tags. * @see Response */ class TemplateParser { /** * Get a tag handler class name. If the tag handler doesn't exist, then * use the UnknownNode tag handler. * @param string $tag_name The tag name of the tag handler to look for. * @return string The class name of the tag handler to use. * @see UnknownNode, ClosingNode, NonClosingNode * @internal */ private function getTagHandler($prefix = '', $tag_name) { // default to the unknown node tag handler $ret = 'UnknownNode'; $file = VK_TPL_TAGS_DIR .'/'. $prefix .'/'. $tag_name .'.php'; if(file_exists($file)) { require_once $file; $class = ucfirst($prefix) . camelize($tag_name) .'Node'; if(class_exists($class, FALSE)) { $ret = $class; } } return new $ret($prefix, $tag_name); } /** * Parse the contents of a file and return the parsed text. * @param string $file_name The absolute path to the file to parse. * @return string The parsed file contents. * @see TemplateParser::parseTags(), TemplateParser::parseVariables() */ public function parse($file_name = '') { $buffer = ''; if(file_exists($file_name)) { $buffer = file_get_contents($file_name); $buffer = $this->parseTags($buffer); $buffer = $this->parseVariables($buffer); } return $buffer; } /** * Parse out the template tags. * @param string $buffer The text to parse. * @return string The parsed text. * @internal */ private function parseTags($buffer = '') { // split up the buffer into tags and text $parts = preg_split("~<(/?)([a-z0-9_]+)\:([a-z0-9_]+)((?: [^>]*)?)>~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 = array(); $stack[] = new RootNode(''); // loop through the split up parts of the buffer and // also increment a tracker $i = -1; while(isset($parts[++$i])) { // get the last node added to the stack $parent = end($stack); // 0 for text, the 1-4 for tag info $key = $i % 5; // stuff before if($key == 0) { $parent->buffer($parts[$i]); } // do we have a tag? if(isset($parts[$i+4])) { // get the tag info $closing = trim($parts[$i+1]) == '/'; $prefix = trim($parts[$i+2]); $tag_name = trim($parts[$i+3]); $attribs = trim($parts[$i+4]); $non_closing = FALSE; // this tag is non-closing if($attribs != '' && $attribs{strlen($attribs)-1} == '/') { $non_closing = TRUE; $attribs = trim(substr($attribs, 0, -1)); } // closing tag if($closing) { $tag = array_pop($stack); $parent = end($stack); // right closing tag if($tag_name == $tag->getName()) { $parent->buffer($tag->parseTag()); // wrong closing tag } else { $parent->buffer('<!-- BAD CLOSING TAG FOR ['. $prefix .':'. $tag_name .'] -->'); } // opening tag } else { // get the tag handler $tag = $this->getTagHandler($prefix, $tag_name); // parse out the attributes if($attribs != '') { preg_match_all('~(?P<attrib>[a-z]+)="(?P<value>[^"]*)"~i', $attribs, $attributes); $attributes = $this->parseTagAttributes($attributes); if(!empty($attributes)) { $tag->addAttributes($attributes); } } // non-closing node, parse it right away and add it to the parent // nodes buffer if($non_closing) { $parent->buffer($tag->parseTag()); // opening tag of a closing node, add it to the stack } else { $stack[] = $tag; } } } $i+= 4; } // parse out any tags that somehow weren't properly parsed. If there were // any tags that weren't properly closed, this will catch them. $parsed_buffer = ''; $temp_buffer = ''; while($node = array_pop($stack)) { // is this the root node? if($node instanceof RootNode) { // add any text back into it from malformed // tags then parse it. $node->buffer($temp_buffer); // parse the root node $parsed_buffer = $node->parseTag(); } // extract the buffers from the malformed tags // to put them back into the root node later. else { $temp_buffer .= $node->buffer(); } } return $parsed_buffer; } /** * Parse the tag attributes. * @param array $parts The tag attributes. * @return array An associative array of attributes => value * @internal */ private function parseTagAttributes(array $parts = array()) { $attributes = array(); foreach($parts['attrib'] as $i => $attrib) { $attributes[strtolower($attrib)] = $parts['value'][$i]; } return $attributes; } /** * Handle matched template variables. * @param array $matches An array returned from a preg_replace_callback * @return string A compiled version of the template variable. * @see TemplateParser::parseVariables, preg_replace_callback * @internal */ private function handleVariable($matches) { $matches[2] = trim($matches[2]); $buffer = ''; // template variable if($matches[1] == '$') { if($matches[2] != '') { $parts = explode('|', $matches[2]); $varname = array_shift($parts); $buffer = '$scope["'. $varname .'"]'; foreach($parts as $callback) { // call a callback if it exists if($callback !== NULL) { $callback_args = explode(":", $callback); $callback = array_shift($callback_args); if(function_exists($callback)) { $callback_args = !empty($callback_args) ? ','. implode(",", $callback_args) : ''; $buffer = $callback .'('. $buffer . $callback_args .')'; } } } if($matches[1] == '$') { $buffer = '<?='. $buffer .';?>'; } } } // url / path variable else if($matches[1] == '/') { if($matches[0] == '{/}') { $matches[2] = '/'; } $buffer = vk_url(vk_path($matches[2])); // need to put $ in subpattern so that handleVariable accepts it properly :P $buffer = preg_replace_callback("~(\\$)([^/]+)~", array($this, 'handleVariable'), $buffer); } // get text else { $buffer = ''; } return $buffer; } /** * Find and parse variables in the text. * @param string $buffer The text to parse. * @return string The text with all matched variables compiled. * @see TemplateParser::parseVariables, preg_replace_callback * @internal */ private function parseVariables($buffer) { $buffer = preg_replace_callback('~{(\$|\@|\/)([^}]*)}~', array($this, 'handleVariable'), $buffer); return $buffer; } } /** * Exception for template nodes, gets called for missing attributes. */ class TemplateNodeException extends Exception { } /** * A template node, this is any xml tag that uses a namespace, * for example: <tpl:something> */ abstract class TemplateNode implements ArrayAccess { /** * The text in-between the opening and closing nodes od this tag. * @internal */ private $buffer = ''; /** * This tags name. * @internal */ protected $name = '', $prefix = ''; /** * An associative array of this tags attributes. * @internal */ private $attributes = array(); /** * Constructor, pass in this tags name. * @param string $name The tag name. */ public function __construct($prefix = '', $name = '') { $this->prefix = $prefix; $this->name = $name; } /** * Add the tags attributes to the attributes array. * @param array $array An associative array of attribute=>value. */ public function addAttributes(array $array = array()) { $this->attributes = array_merge($this->attributes, $array); } /** * Add text to this tags buffer. * @param string $buffer The text to append to the buffer. * @see ClosingNode::$buffer * @internal */ public function buffer($buffer = FALSE) { if($buffer !== FALSE) { $this->buffer .= $buffer; } else { return $this->buffer; } } /** * Get the name of this tag. * @return string The tag name. */ public function getName() { return $this->name; } /** * Require that this tag be passed certain arguments. * <code> * $this->requireAttributes('href', 'title', ...); * </code> */ public function requireAttributes() { foreach(func_get_args() as $attrib) { if(!array_key_exists($attrib, $this->attributes)) { throw new TemplateNodeException("Missing attribute [$attrib] for tag [{$this->name}]."); } } } /** * Require at least one out of a number of attributes to exist. * <code> * $this->requireOne('href', 'src'); * </code> * @return boolean The first present attribute from the set found. */ public function requireOne() { $ret = FALSE; $attribs = func_get_args(); foreach($attribs as $attrib) { if(array_key_exists($attrib, $this->attributes)) { $ret = $attrib; break; } } if($ret === FALSE) { throw new TemplateNodeException("Missing at least one required attribute of set [". implode(',', $attribs) ."] for tag [{$this->name}]."); } return $ret; } /** * Get an attribute. * @param string $key The attribute name. * @return mixed The value of the attribute. * @see ArrayAccess */ public function offsetGet($key) { return $this->offsetExists($key) ? $this->attributes[$key] : NULL; } /** * Set an attribute. * @param string $key The attribute name. * @param mixed $val The value of the attribute to set. * @see ArrayAccess */ public function offsetSet($key, $val) { $this->attributes[$key] = $val; } /** * Check if an attribute exists. * @param string $key The attribute name. * @return boolean Whether or not the attribute exists. * @see ArrayAccess */ public function offsetExists($key) { return isset($this->attributes[$key]); } /** * Unset an attribute. * @param string $key The attribute name. * @see ArrayAccess */ public function offsetUnset($key) { unset($this->attributes[$key]); } /** * Parse the tag. */ abstract public function parseTag(); } /** * Class to handle the root node in the template parser stack. */ class RootNode extends TemplateNode { public function parseTag() { return $this->buffer(); } } /** * Class to handle nodes that the parser doesn't recognize. */ class UnknownNode extends TemplateNode { public function parseTag() { $buffer = "<". $this->prefix .":". $this->name .">\n"; $buffer .= $this->buffer(); $buffer .= "\n</". $this->prefix .':'. $this->name .'>'; return $buffer; } }
Private
[
?
]
Run code