Public feed #600764

k4st

public php v1 · immutable
Raw Edit
·published 2007-07-03 02:18 UTC
rendered paste body
<?php/** * $header * @copyright Copyright 2007, Peter Goodman * @author Peter Goodman *//** * Parse a document for specific XML tags. * @see Response */class TemplateParser implements Parser {		/**	 * 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 = TPL_TAGS_DIR .'/'. $prefix .'/'. $tag_name .'.php';		if(file_exists($file)) {			require_once $file;						$class = camelize($tag_name) .'Node';						if(class_exists($class, FALSE)) {				$ret = $class;			}		}				return new $ret($tag_name);	}		/**	 * Parse the contents of a file and return the parsed text.	 * @param string $input The absolute path to the file to parse.	 * @return string The parsed file contents.	 */	public function parse($input = '') {		return $this->parseTags($input);	}		/**	 * 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_]+)((?: [^>]*)?)>~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;	}}/** * 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 Node, ArrayAccess {		/**	 * The text in-between the opening and closing nodes od this tag.	 * @internal	 */	private $buffer = '';		/**	 * This tags name.	 * @internal	 */	private $name = '';		/**	 * 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($name = '') {		$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]);	}}/** * 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() {		return $this->buffer();	}}