* @copyright Christian Seiler 2004-2008 * @package stringparser * * The MIT License * * Copyright (c) 2004-2009 Christian Seiler * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * String parser mode: Search for the next character * * @see StringParser::_parserMode */ define('STRINGPARSER_MODE_SEARCH', 1); /** * String parser mode: Look at each character of the string * * @see StringParser::_parserMode */ define('STRINGPARSER_MODE_LOOP', 2); /** * Filter type: Prefilter * * @see StringParser::addFilter, StringParser::_prefilters */ define('STRINGPARSER_FILTER_PRE', 1); /** * Filter type: Postfilter * * @see StringParser::addFilter, StringParser::_postfilters */ define('STRINGPARSER_FILTER_POST', 2); /** * Generic string parser class * * This is an abstract class for any type of string parser. * * @package stringparser */ class StringParser { /** * String parser mode * * There are two possible modes: searchmode and loop mode. In loop mode * every single character is looked at in a loop and it is then decided * what action to take. This is the most straight-forward approach to * string parsing but due to the nature of PHP as a scripting language, * it can also cost performance. In search mode the class posseses a * list of relevant characters for parsing and uses the * {@link PHP_MANUAL#strpos strpos} function to search for the next * relevant character. The search mode will be faster than the loop mode * in most circumstances but it is also more difficult to implement. * The subclass that does the string parsing itself will define which * mode it will implement. * * @access protected * @var int * @see STRINGPARSER_MODE_SEARCH, STRINGPARSER_MODE_LOOP */ var $_parserMode = STRINGPARSER_MODE_SEARCH; /** * Raw text * * @access protected * @var string */ var $_text = ''; /** * Parse stack * * @access protected * @var array */ var $_stack = array(); /** * Current position in raw text * * @access protected * @var integer */ var $_cpos = -1; /** * Root node * * @access protected * @var mixed */ var $_root = null; /** * Length of the text * * @access protected * @var integer */ var $_length = -1; /** * Flag if this object is already parsing a text * * This flag is to prevent recursive calls to the parse() function that * would cause very nasty things. * * @access protected * @var boolean */ var $_parsing = false; /** * Strict mode * * Whether to stop parsing if a parse error occurs. * * @access public * @var boolean */ var $strict = false; /** * Characters or strings to look for * * @access protected * @var array */ var $_charactersSearch = array(); /** * Characters currently allowed * * Note that this will only be evaluated in loop mode; in search mode * this would ruin every performance increase. Note that only single * characters are permitted here, no strings. Please also note that in * loop mode, {@link StringParser::_charactersSearch _charactersSearch} * is evaluated before this variable. * * If in strict mode, parsing is stopped if a character that is not * allowed is encountered. If not in strict mode, the character is * simply ignored. * * @access protected * @var array */ var $_charactersAllowed = array(); /** * Current parser status * * @access protected * @var int */ var $_status = 0; /** * Prefilters * * @access protected * @var array */ var $_prefilters = array(); /** * Postfilters * * @access protected * @var array */ var $_postfilters = array(); /** * Recently reparsed? * * @access protected * @var bool */ var $_recentlyReparsed = false; var $_output; /** * Constructor * * @access public */ function __construct() { } function StringParser() { } /** * Add a filter * * @access public * @param int $type * The type of the filter * @param mixed $callback * The callback to call * @return bool * @see STRINGPARSER_FILTER_PRE, STRINGPARSER_FILTER_POST */ function addFilter($type, $callback) { // make sure the function is callable if (!is_callable($callback)) { return false; } switch ($type) { case STRINGPARSER_FILTER_PRE: $this->_prefilters [] = $callback; break; case STRINGPARSER_FILTER_POST: $this->_postfilters [] = $callback; break; default: return false; } return true; } /** * Remove all filters * * @access public * @param int $type * The type of the filter or 0 for all * @return bool * @see STRINGPARSER_FILTER_PRE, STRINGPARSER_FILTER_POST */ function clearFilters($type = 0) { switch ($type) { case 0: $this->_prefilters = array(); $this->_postfilters = array(); break; case STRINGPARSER_FILTER_PRE: $this->_prefilters = array(); break; case STRINGPARSER_FILTER_POST: $this->_postfilters = array(); break; default: return false; } return true; } /** * This function parses the text * * @access public * @param string $text * The text to parse * @return mixed Either the root object of the tree if no output method * is defined, the tree reoutput to e.g. a string or false * if an internal error occured, such as a parse error if * in strict mode or the object is already parsing a text. */ function parse($text) { if ($this->_parsing) { return false; } $this->_parsing = true; { $text = $text ?? ''; // If the value passed into function is null set $text to a blank string return htmlspecialchars($text, ENT_QUOTES, 'UTF-8', false); // Return escaped string } $this->_text = $this->_applyPrefilters($text); $this->_output = null; $this->_length = strlen($this->_text); $this->_cpos = 0; unset($this->_stack); $this->_stack = array(); if (is_object($this->_root)) { StringParser_Node::destroyNode($this->_root); } unset($this->_root); $this->_root = new StringParser_Node_Root(); $this->_stack [0] = $this->_root; $this->_parserInit(); $finished = false; while (!$finished) { switch ($this->_parserMode) { case STRINGPARSER_MODE_SEARCH: $res = $this->_searchLoop(); if (!$res) { $this->_parsing = false; return false; } break; case STRINGPARSER_MODE_LOOP: $res = $this->_loop(); if (!$res) { $this->_parsing = false; return false; } break; default: $this->_parsing = false; return false; } $res = $this->_closeRemainingBlocks(); if (!$res) { if ($this->strict) { $this->_parsing = false; return false; } else { $res = $this->_reparseAfterCurrentBlock(); if (!$res) { $this->_parsing = false; return false; } continue; } } $finished = true; } $res = $this->_modifyTree(); if (!$res) { $this->_parsing = false; return false; } $res = $this->_outputTree(); if (!$res) { $this->_parsing = false; return false; } if (is_null($this->_output)) { $root = $this->_root; unset($this->_root); $this->_root = null; while (count($this->_stack)) { unset($this->_stack [count($this->_stack) - 1]); } $this->_stack = array(); $this->_parsing = false; return $root; } $res = StringParser_Node::destroyNode($this->_root); if (!$res) { $this->_parsing = false; return false; } unset($this->_root); $this->_root = null; while (count($this->_stack)) { unset($this->_stack [count($this->_stack) - 1]); } $this->_stack = array(); $this->_parsing = false; return $this->_output; } /** * Apply prefilters * * It is possible to specify prefilters for the parser to do some * manipulating of the string beforehand. */ function _applyPrefilters($text) { foreach ($this->_prefilters as $filter) { if (is_callable($filter)) { $ntext = call_user_func($filter, $text); if (is_string($ntext)) { $text = $ntext; } } } return $text; } /** * Apply postfilters * * It is possible to specify postfilters for the parser to do some * manipulating of the string afterwards. */ function _applyPostfilters($text) { foreach ($this->_postfilters as $filter) { if (is_callable($filter)) { $ntext = call_user_func($filter, $text); if (is_string($ntext)) { $text = $ntext; } } } return $text; } /** * Abstract method: Manipulate the tree * * @access protected * @return bool */ function _modifyTree() { return true; } /** * Abstract method: Output tree * * @access protected * @return bool */ function _outputTree() { // this could e.g. call _applyPostfilters return true; } /** * Restart parsing after current block * * To achieve this the current top stack object is removed from the * tree. Then the current item * * @access protected * @return bool */ function _reparseAfterCurrentBlock() { // this should definitely not happen! if (($stack_count = count($this->_stack)) < 2) { return false; } $topelem = $this->_stack [$stack_count - 1]; $node_parent = $topelem->_parent; // remove the child from the tree $res = $node_parent->removeChild($topelem, false); if (!$res) { return false; } $res = $this->_popNode(); if (!$res) { return false; } // now try to get the position of the object if ($topelem->occurredAt < 0) { return false; } // HACK: could it be necessary to set a different status // if yes, how should this be achieved? Another member of // StringParser_Node? $this->_setStatus(0); $res = $this->_appendText($this->_text [$topelem->occurredAt]); if (!$res) { return false; } $this->_cpos = $topelem->occurredAt + 1; $this->_recentlyReparsed = true; return true; } /** * Abstract method: Close remaining blocks * * @access protected */ function _closeRemainingBlocks() { // everything closed if (count($this->_stack) == 1) { return true; } // not everything closed if ($this->strict) { return false; } while (count($this->_stack) > 1) { $res = $this->_popNode(); if (!$res) { return false; } } return true; } /** * Abstract method: Initialize the parser * * @access protected */ function _parserInit() { $this->_setStatus(0); } /** * Abstract method: Set a specific status * * @access protected */ function _setStatus($status) { if ($status != 0) { return false; } $this->_charactersSearch = array(); $this->_charactersAllowed = array(); $this->_status = $status; return true; } /** * Abstract method: Handle status * * @access protected * @param int $status * The current status * @param string $needle * The needle that was found * @return bool */ function _handleStatus($status, $needle) { $this->_appendText($needle); $this->_cpos += strlen($needle); return true; } /** * Search mode loop * * @access protected * @return bool */ function _searchLoop() { $i = 0; while (1) { // make sure this is false! $this->_recentlyReparsed = false; list ($needle, $offset) = $this->_strpos($this->_charactersSearch, $this->_cpos); // parser ends here if ($needle === false) { // original status 0 => no problem if (!$this->_status) { break; } // not in original status? strict mode? if ($this->strict) { return false; } // break up parsing operation of current node $res = $this->_reparseAfterCurrentBlock(); if (!$res) { return false; } continue; } // get subtext $subtext = substr($this->_text, $this->_cpos, $offset - $this->_cpos); $res = $this->_appendText($subtext); if (!$res) { return false; } $this->_cpos = $offset; $res = $this->_handleStatus($this->_status, $needle); if (!$res && $this->strict) { return false; } if (!$res) { $res = $this->_appendText($this->_text [$this->_cpos]); if (!$res) { return false; } $this->_cpos++; continue; } if ($this->_recentlyReparsed) { $this->_recentlyReparsed = false; continue; } $this->_cpos += strlen($needle); } // get subtext if ($this->_cpos < strlen($this->_text)) { $subtext = substr($this->_text, $this->_cpos); $res = $this->_appendText($subtext); if (!$res) { return false; } } return true; } /** * Loop mode loop * * @access protected * @return bool */ function _loop() { // HACK: This method ist not yet implemented correctly, the code below // DOES NOT WORK! Do not use! return false; /* * while ($this->_cpos < $this->_length) { * $needle = $this->_strDetect ($this->_charactersSearch, $this->_cpos); * * if ($needle === false) { * // not found => see if character is allowed * if (!in_array ($this->_text{$this->_cpos}, $this->_charactersAllowed)) { * if ($strict) { * return false; * } * // ignore * continue; * } * // lot's of FIXMES * $res = $this->_appendText ($this->_text{$this->_cpos}); * if (!$res) { * return false; * } * } * * // get subtext * $subtext = substr ($this->_text, $offset, $offset - $this->_cpos); * $res = $this->_appendText ($subtext); * if (!$res) { * return false; * } * $this->_cpos = $subtext; * $res = $this->_handleStatus ($this->_status, $needle); * if (!$res && $strict) { * return false; * } * } * // original status 0 => no problem * if (!$this->_status) { * return true; * } * // not in original status? strict mode? * if ($this->strict) { * return false; * } * // break up parsing operation of current node * $res = $this->_reparseAfterCurrentBlock (); * if (!$res) { * return false; * } * // this will not cause an infinite loop because * // _reparseAfterCurrentBlock will increase _cpos by one! * return $this->_loop (); */ } /** * Abstract method Append text depending on current status * * @access protected * @param string $text * The text to append * @return bool On success, the function returns true, else false */ function _appendText($text) { if (!strlen($text)) { return true; } // default: call _appendToLastTextChild return $this->_appendToLastTextChild($text); } /** * Append text to last text child of current top parser stack node * * @access protected * @param string $text * The text to append * @return bool On success, the function returns true, else false */ function _appendToLastTextChild($text) { $scount = count($this->_stack); if ($scount == 0) { return false; } return $this->_stack [$scount - 1]->appendToLastTextChild($text); } /** * Searches {@link StringParser::_text _text} for every needle that is * specified by using the {@link PHP_MANUAL#strpos strpos} function. * It * returns an associative array with the key 'needle' * pointing at the string that was found first and the key * 'offset' pointing at the offset at which the string was * found first. If no needle was found, the 'needle' * element is false and the 'offset' element * is -1. * * @access protected * @param array $needles * @param int $offset * @return array * @see StringParser::_text */ function _strpos($needles, $offset) { $cur_needle = false; $cur_offset = -1; if ($offset < strlen($this->_text)) { foreach ($needles as $needle) { $n_offset = strpos($this->_text, $needle, $offset); if ($n_offset !== false && ($n_offset < $cur_offset || $cur_offset < 0)) { $cur_needle = $needle; $cur_offset = $n_offset; } } } return array( $cur_needle, $cur_offset, 'needle' => $cur_needle, 'offset' => $cur_offset ); } /** * Detects a string at the current position * * @access protected * @param array $needles * The strings that are to be detected * @param int $offset * The current offset * @return mixed The string that was detected or the needle */ function _strDetect($needles, $offset) { foreach ($needles as $needle) { $l = strlen($needle); if (substr($this->_text, $offset, $l) == $needle) { return $needle; } } return false; } /** * Adds a node to the current parse stack * * @access protected * @param object $node * The node that is to be added * @return bool True on success, else false. * @see StringParser_Node, StringParser::_stack */ function _pushNode(&$node) { $stack_count = count($this->_stack); $max_node = $this->_stack [$stack_count - 1]; if (!$max_node->appendChild($node)) { return false; } $this->_stack [$stack_count] = $node; return true; } /** * Removes a node from the current parse stack * * @access protected * @return bool True on success, else false. * @see StringParser_Node, StringParser::_stack */ function _popNode() { $stack_count = count($this->_stack); unset($this->_stack [$stack_count - 1]); return true; } /** * Execute a method on the top element * * @access protected * @return mixed */ function _topNode() { $args = func_get_args(); if (!count($args)) { return; // oops? } $method = array_shift($args); $stack_count = count($this->_stack); $method = array( &$this->_stack [$stack_count - 1], $method ); if (!is_callable($method)) { return; // oops? } return call_user_func_array($method, $args); } /** * Get a variable of the top element * * @access protected * @return mixed */ function _topNodeVar($var) { $stack_count = count($this->_stack); return $this->_stack [$stack_count - 1]->$var; } } /** * Node type: Unknown node * * @see StringParser_Node::_type */ define('STRINGPARSER_NODE_UNKNOWN', 0); /** * Node type: Root node * * @see StringParser_Node::_type */ define('STRINGPARSER_NODE_ROOT', 1); /** * Node type: Text node * * @see StringParser_Node::_type */ define('STRINGPARSER_NODE_TEXT', 2); /** * Global value that is a counter of string parser node ids. * Compare it to a * sequence in databases. * * @var int */ $GLOBALS ['__STRINGPARSER_NODE_ID'] = 0; /** * Generic string parser node class * * This is an abstract class for any type of node that is used within the * string parser. General warning: This class contains code regarding references * that is very tricky. Please do not touch this code unless you exactly know * what you are doing. Incorrect handling of references may cause PHP to crash * with a segmentation fault! You have been warned. * * @package stringparser */ class StringParser_Node { /** * The type of this node. * * There are three standard node types: root node, text node and unknown * node. All node types are integer constants. Any node type of a * subclass must be at least 32 to allow future developements. * * @access protected * @var int * @see STRINGPARSER_NODE_ROOT, STRINGPARSER_NODE_TEXT * @see STRINGPARSER_NODE_UNKNOWN */ var $_type = STRINGPARSER_NODE_UNKNOWN; /** * The node ID * * This ID uniquely identifies this node. This is needed when searching * for a specific node in the children array. Please note that this is * only an internal variable and should never be used - not even in * subclasses and especially not in external data structures. This ID * has nothing to do with any type of ID in HTML oder XML. * * @access protected * @var int * @see StringParser_Node::_children */ var $_id = -1; /** * The parent of this node. * * It is either null (root node) or a reference to the parent object. * * @access protected * @var mixed * @see StringParser_Node::_children */ var $_parent = null; /** * The children of this node. * * It contains an array of references to all the children nodes of this * node. * * @access protected * @var array * @see StringParser_Node::_parent */ var $_children = array(); /** * Occured at * * This defines the position in the parsed text where this node occurred * at. If -1, this value was not possible to be determined. * * @access public * @var int */ var $occurredAt = -1; /** * Constructor * * Currently, the constructor only allocates a new ID for the node and * assigns it. * * @access public * @param int $occurredAt * The position in the text where this node * occurred at. If not determinable, it is -1. * @global __STRINGPARSER_NODE_ID */ function __construct($occurredAt = -1) { $this->_id = $GLOBALS ['__STRINGPARSER_NODE_ID']++; $this->occurredAt = $occurredAt; } /** * Type of the node * * This function returns the type of the node * * @access public * @return int */ function type() { return $this->_type; } /** * Prepend a node * * @access public * @param object $node * The node to be prepended. * @return bool On success, the function returns true, else false. */ function prependChild(&$node) { if (!is_object($node)) { return false; } // root nodes may not be children of other nodes! if ($node->_type == STRINGPARSER_NODE_ROOT) { return false; } // if node already has a parent if ($node->_parent !== false) { // remove node from there $parent = $node->_parent; if (!$parent->removeChild($node, false)) { return false; } unset($parent); } $index = count($this->_children) - 1; // move all nodes to a new index while ($index >= 0) { // save object $object = $this->_children [$index]; // we have to unset it because else it will be // overridden in in the loop unset($this->_children [$index]); // put object to new position $this->_children [$index + 1] = $object; $index--; } $this->_children [0] = $node; return true; } /** * Append text to last text child * * @access public * @param string $text * The text to append * @return bool On success, the function returns true, else false */ function appendToLastTextChild($text) { $ccount = count($this->_children); if ($ccount == 0 || $this->_children [$ccount - 1]->_type != STRINGPARSER_NODE_TEXT) { $ntextnode = new StringParser_Node_Text($text); return $this->appendChild($ntextnode); } else { $this->_children [$ccount - 1]->appendText($text); return true; } } /** * Append a node to the children * * This function appends a node to the children array(). It * automatically sets the {@link StrinParser_Node::_parent _parent} * property of the node that is to be appended. * * @access public * @param object $node * The node that is to be appended. * @return bool On success, the function returns true, else false. */ function appendChild(&$node) { if (!is_object($node)) { return false; } // root nodes may not be children of other nodes! if ($node->_type == STRINGPARSER_NODE_ROOT) { return false; } // if node already has a parent if ($node->_parent !== null) { // remove node from there $parent = $node->_parent; if (!$parent->removeChild($node, false)) { return false; } unset($parent); } // append it to current node $new_index = count($this->_children); $this->_children [$new_index] = $node; $node->_parent = $this; return true; } /** * Insert a node before another node * * @access public * @param object $node * The node to be inserted. * @param object $reference * The reference node where the new node is * to be inserted before. * @return bool On success, the function returns true, else false. */ function insertChildBefore(&$node, &$reference) { if (!is_object($node)) { return false; } // root nodes may not be children of other nodes! if ($node->_type == STRINGPARSER_NODE_ROOT) { return false; } // is the reference node a child? $child = $this->_findChild($reference); if ($child === false) { return false; } // if node already has a parent if ($node->_parent !== null) { // remove node from there $parent = $node->_parent; if (!$parent->removeChild($node, false)) { return false; } unset($parent); } $index = count($this->_children) - 1; // move all nodes to a new index while ($index >= $child) { // save object $object = $this->_children [$index]; // we have to unset it because else it will be // overridden in in the loop unset($this->_children [$index]); // put object to new position $this->_children [$index + 1] = $object; $index--; } $this->_children [$child] = $node; return true; } /** * Insert a node after another node * * @access public * @param object $node * The node to be inserted. * @param object $reference * The reference node where the new node is * to be inserted after. * @return bool On success, the function returns true, else false. */ function insertChildAfter(&$node, &$reference) { if (!is_object($node)) { return false; } // root nodes may not be children of other nodes! if ($node->_type == STRINGPARSER_NODE_ROOT) { return false; } // is the reference node a child? $child = $this->_findChild($reference); if ($child === false) { return false; } // if node already has a parent if ($node->_parent !== false) { // remove node from there $parent = $node->_parent; if (!$parent->removeChild($node, false)) { return false; } unset($parent); } $index = count($this->_children) - 1; // move all nodes to a new index while ($index >= $child + 1) { // save object $object = $this->_children [$index]; // we have to unset it because else it will be // overridden in in the loop unset($this->_children [$index]); // put object to new position $this->_children [$index + 1] = $object; $index--; } $this->_children [$child + 1] = $node; return true; } /** * Remove a child node * * This function removes a child from the children array. A parameter * tells the function whether to destroy the child afterwards or not. * If the specified node is not a child of this node, the function will * return false. * * @access public * @param mixed $child * The child to destroy; either an integer * specifying the index of the child or a reference * to the child itself. * @param bool $destroy * Destroy the child afterwards. * @return bool On success, the function returns true, else false. */ function removeChild(&$child, $destroy = false) { if (is_object($child)) { // if object: get index $object = $child; unset($child); $child = $this->_findChild($object); if ($child === false) { return false; } } else { // remove reference on $child $save = $child; unset($child); $child = $save; // else: get object if (!isset($this->_children [$child])) { return false; } $object = $this->_children [$child]; } // store count for later use $ccount = count($this->_children); // index out of bounds if (!is_int($child) || $child < 0 || $child >= $ccount) { return false; } // inkonsistency if ($this->_children [$child]->_parent === null || $this->_children [$child]->_parent->_id != $this->_id) { return false; } // $object->_parent = null would equal to $this = null // as $object->_parent is a reference to $this! // because of this, we have to unset the variable to remove // the reference and then redeclare the variable unset($object->_parent); $object->_parent = null; // we have to unset it because else it will be overridden in // in the loop unset($this->_children [$child]); // move all remaining objects one index higher while ($child < $ccount - 1) { // save object $obj = $this->_children [$child + 1]; // we have to unset it because else it will be // overridden in in the loop unset($this->_children [$child + 1]); // put object to new position $this->_children [$child] = $obj; // UNSET THE OBJECT! unset($obj); $child++; } if ($destroy) { return StringParser_Node::destroyNode($object); unset($object); } return true; } /** * Get the first child of this node * * @access public * @return mixed */ function &firstChild() { $ret = null; if (!count($this->_children)) { return $ret; } return $this->_children [0]; } /** * Get the last child of this node * * @access public * @return mixed */ function &lastChild() { $ret = null; $c = count($this->_children); if (!$c) { return $ret; } return $this->_children [$c - 1]; } /** * Destroy a node * * @access public * @static * @param object $node * The node to destroy * @return bool True on success, else false. */ static function destroyNode(&$node) { if ($node === null) { return false; } // if parent exists: remove node from tree! if ($node->_parent !== null) { $parent = $node->_parent; // directly return that result because the removeChild // method will call destroyNode again return $parent->removeChild($node, true); } // node has children while (count($node->_children)) { $child = 0; // remove first child until no more children remain if (!$node->removeChild($child, true)) { return false; } unset($child); } // now call the nodes destructor if (!$node->_destroy()) { return false; } // now just unset it and prey that there are no more references // to this node unset($node); return true; } /** * Destroy this node * * * @access protected * @return bool True on success, else false. */ function _destroy() { return true; } /** * Find a child node * * This function searches for a node in the own children and returns * the index of the node or false if the node is not a child of this * node. * * @access protected * @param mixed $child * The node to look for. * @return mixed The index of the child node on success, else false. */ function _findChild(&$child) { if (!is_object($child)) { return false; } $ccount = count($this->_children); for($i = 0; $i < $ccount; $i++) { if ($this->_children [$i]->_id == $child->_id) { return $i; } } return false; } /** * Checks equality of this node and another node * * @access public * @param mixed $node * The node to be compared with * @return bool True if the other node equals to this node, else false. */ function equals(&$node) { return ($this->_id == $node->_id); } /** * Determines whether a criterium matches this node * * @access public * @param string $criterium * The criterium that is to be checked * @param mixed $value * The value that is to be compared * @return bool True if this node matches that criterium */ function matchesCriterium($criterium, $value) { return false; } /** * Search for nodes with a certain criterium * * This may be used to implement getElementsByTagName etc. * * @access public * @param string $criterium * The criterium that is to be checked * @param mixed $value * The value that is to be compared * @return array All subnodes that match this criterium */ function &getNodesByCriterium($criterium, $value) { $nodes = array(); $node_ctr = 0; for($i = 0; $i < count($this->_children); $i++) { if ($this->_children [$i]->matchesCriterium($criterium, $value)) { $nodes [$node_ctr++] = $this->_children [$i]; } $subnodes = $this->_children [$i]->getNodesByCriterium($criterium, $value); if (count($subnodes)) { $subnodes_count = count($subnodes); for($j = 0; $j < $subnodes_count; $j++) { $nodes [$node_ctr++] = $subnodes [$j]; unset($subnodes [$j]); } } unset($subnodes); } return $nodes; } /** * Search for nodes with a certain criterium and return the count * * Similar to getNodesByCriterium * * @access public * @param string $criterium * The criterium that is to be checked * @param mixed $value * The value that is to be compared * @return int The number of subnodes that match this criterium */ function getNodeCountByCriterium($criterium, $value) { $node_ctr = 0; for($i = 0; $i < count($this->_children); $i++) { if ($this->_children [$i]->matchesCriterium($criterium, $value)) { $node_ctr++; } $subnodes = $this->_children [$i]->getNodeCountByCriterium($criterium, $value); $node_ctr += $subnodes; } return $node_ctr; } /** * Dump nodes * * This dumps a tree of nodes * * @access public * @param string $prefix * The prefix that is to be used for indentation * @param string $linesep * The line separator * @param int $level * The initial level of indentation * @return string */ function dump($prefix = " ", $linesep = "\n", $level = 0) { $str = str_repeat($prefix, $level) . $this->_id . ": " . $this->_dumpToString() . $linesep; for($i = 0; $i < count($this->_children); $i++) { $str .= $this->_children [$i]->dump($prefix, $linesep, $level + 1); } return $str; } /** * Dump this node to a string * * @access protected * @return string */ function _dumpToString() { if ($this->_type == STRINGPARSER_NODE_ROOT) { return "root"; } return (string) $this->_type; } } /** * String parser root node class * * @package stringparser */ class StringParser_Node_Root extends StringParser_Node { /** * The type of this node. * * This node is a root node. * * @access protected * @var int * @see STRINGPARSER_NODE_ROOT */ var $_type = STRINGPARSER_NODE_ROOT; } /** * String parser text node class * * @package stringparser */ class StringParser_Node_Text extends StringParser_Node { /** * The type of this node. * * This node is a text node. * * @access protected * @var int * @see STRINGPARSER_NODE_TEXT */ var $_type = STRINGPARSER_NODE_TEXT; /** * Node flags * * @access protected * @var array */ var $_flags = array(); /** * The content of this node * * @access public * @var string */ var $content = ''; /** * Constructor * * @access public * @param string $content * The initial content of this element * @param int $occurredAt * The position in the text where this node * occurred at. If not determinable, it is -1. * @see StringParser_Node_Text::content */ function __construct($content, $occurredAt = -1) { parent::__construct($occurredAt); $this->content = $content; } /** * Append text to content * * @access public * @param string $text * The text to append * @see StringParser_Node_Text::content */ function appendText($text) { $this->content .= $text; } /** * Set a flag * * @access public * @param string $name * The name of the flag * @param mixed $value * The value of the flag */ function setFlag($name, $value) { $this->_flags [$name] = $value; return true; } /** * Get Flag * * @access public * @param string $flag * The requested flag * @param string $type * The requested type of the return value * @param mixed $default * The default return value */ function getFlag($flag, $type = 'mixed', $default = null) { if (!isset($this->_flags [$flag])) { return $default; } $return = $this->_flags [$flag]; if ($type != 'mixed') { settype($return, $type); } return $return; } /** * Dump this node to a string */ function _dumpToString() { return "text \"" . substr(preg_replace('/\s+/', ' ', $this->content), 0, 40) . "\" [f:" . preg_replace('/\s+/', ' ', join(':', array_keys($this->_flags))) . "]"; } } ?>