From 9201955ef03f0166ab692d68c08b447a7e245c1f Mon Sep 17 00:00:00 2001 From: Robin Johnson Date: Sat, 1 Mar 2003 11:43:58 +0000 Subject: [PATCH] Cleanup --- ChangeLog | 5 + libraries/xpath/XPath.class.php | 5078 ------------------------------- 2 files changed, 5 insertions(+), 5078 deletions(-) delete mode 100644 libraries/xpath/XPath.class.php diff --git a/ChangeLog b/ChangeLog index 961eeb1c2..f6cc89a6b 100755 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,10 @@ phpMyAdmin - Changelog $Id$ $Source$ +2003-03-01 Robin Johnson + * libraries/xpath/XPath.class.php, libraries/xpath/: + - Removed (was part of the early DB config prototype) + 2003-02-28 Michal Cihar * tbl_indexes.php3: Show SQL query after modifying/creating index (closes #694800), show structure after modifying index. @@ -33,6 +37,7 @@ $Source$ on standard 800x600 browser windows. 2003-02-27 Michal Cihar +>>>>>>> 1.2120 * lang/czech: Updated. * lang/english: Resorted. diff --git a/libraries/xpath/XPath.class.php b/libraries/xpath/XPath.class.php deleted file mode 100644 index 463c0c12f..000000000 --- a/libraries/xpath/XPath.class.php +++ /dev/null @@ -1,5078 +0,0 @@ - | -// | Started around 2001-07 and creator of V1.N.x branches. | -// | Sam Blum | -// | Started around 2001-09 major restruct and testbench initiator. | -// | Daniel Allen | -// | Started around 2001-10 working to make Php.XPath adhere to specs | -// | fab | -// | minor changes, especially the return of status codes instead of | -// | echo and exit. | -// +----------------------------------------------------------------------+ -// | Main Former Authors: | -// | Michael P. Mehl | -// | Inital creator of V 1.0. Stoped activities around 2001-03 | -// +----------------------------------------------------------------------+ -// | Requires PHP version 4.0.5 and up | -// +----------------------------------------------------------------------+ -// | Ref: | -// | @link http://sourceforge.net/projects/phpxpath/ Latest release | -// | @link http://www.w3.org/TR/xpath W3C XPath Recommendation | -// +----------------------------------------------------------------------+ - -////////////////////////////////////////////////////////////////////////////////// -// Class for accessing XML data through the XML Path Language -// XPath Version 1.0 defined by W3C -// -// This class offers methods for accessing the nodes of a XML document using -// the XPath language. You can add or remove children (nodes), set or modify their -// content and their attributes. No additional PHP extensions like DOM XML -// or something similar are required to use these features. - -class XPath { - //////////////////////////////////////////////////////////////////////////////// - //############### Old Public Interface #######################################// - //////////////////////////////////////////////////////////////////////////////// - - // This is the interface that has been with us from version 1.0 - 1.N.4. It's - // a hotch potch of radomly named junk now, so is getting renamed to DOM style - // function names. - // This interface will be expired after the 1.N.5, at first warning you and then - // later stopping execution when still used. *So update your code*. - // To turn warning/aborting on/off toggle the following flags. - // But be warned: The interface will be flushed soon. - var $deprecate_1N4_warning = TRUE; - var $deprecate_1N4_abort = TRUE; - - /** - * @deprecated Use XPath() instead - */ - function XML($fileName = '') { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Class name 'XML' is deprecated sinc V1.N.5. Use XPath() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->XPath($fileName); - } - - /** - * @deprecated Use importFromFile() instead - */ - function load_file($fileName) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'load_file' is deprecated sinc V1.N.5. Use importFromFile() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->importFromFile($fileName); - } - - /** - * @deprecated Use importFromString() instead - */ - function load_string($xmlString) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'load_string' is deprecated sinc V1.N.5. Use importFromString() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->importFromString($xmlString); - } - - /** - * Given a context this function returns the containing XML - * - * This method takes a context, which is derived from the evaluate - * function, and it returns the XML that is contained within this - * node - * - * @deprecated Use exportAsXml() instead - * @param array absoluteXPathArray an array of absolute XPath - * addresses to nodes. - * @param int $index which of the results of the absoluteXPathArray - * to use. - * @return string The string returned is valid XML - * @see exportAsXml(), evaluate() - */ - function grab($absoluteXPathArray = '', $index = 0) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'grab' is deprecated sinc V1.N.5. Use exportAsXml() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->exportAsXml($absoluteXPathArray[$index]); - } - - /** - * @deprecated Use exportAsXml() instead - */ - function get_as_xml_file() { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_as_xml_file' is deprecated sinc V1.N.5. Use exportAsXml() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return '' . "\n" . $this->exportAsXml(); - } - - /** - * @deprecated Use exportAsHtml() instead - */ - function get_as_html_file($highlight = array()) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_as_html_file' is deprecated sinc V1.N.5. Use exportAsHtml() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->exportAsHtml('', $highlight); - } - - /** - * @deprecated Use appendChild() instead - */ - function add_node($absoluteParentPath, $nodeName) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'add_node' is deprecated sinc V1.N.5. Use appendChild() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->appendChild($absoluteParentPath, $nodeName); - } - - /** - * @deprecated Use removeChild() instead - */ - function remove_node($absoluteXPath) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'remove_node' is deprecated sinc V1.N.5. Use removeChild() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->removeChild($absoluteXPath); - } - - /** - * @deprecated Use appendData() instead - */ - function add_content($absoluteXPath, $value ) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'add_content' is deprecated sinc V1.N.5. Use appendData() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - $this->_setContent(&$absoluteXPath, &$value, TRUE ); - } - - /** - * @deprecated Use replaceData() and appendData() instead - */ - function set_content($absoluteXPath, $value, $append=FALSE) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'set_content' is deprecated sinc V1.N.5. Use replaceData() and appendData() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->_setContent(&$absoluteXPath, &$value, $append); - } - - /** - * @deprecated Use substringData() instead - */ - function get_content($absoluteXPath) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_content' is deprecated sinc V1.N.5. Use getData() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->substringData($absoluteXPath); - } - - /** - * Add attributes to a node. - * - * This method adds attributes to a node. Existing attributes *WILL BE* - * overwritten unless $overwrite is set to FALSE. - * - * @deprecated Use setAttributes() instead - * @param string $absoluteXPath Full document path of the node, the - * attributes should be added to. *READONLY* - * @param array $attributes Associative array containing the new - * attributes for the node. *READONLY* - * @param bool $overwrite TRUE (=default): overwite attibutes / - * FALSE: Will not overwite existing attibutes *READONLY* - * @see getAttributes(), setAttributes(), removeAttributes() - */ - function add_attributes($absoluteXPath, $attributes, $overwrite = TRUE) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'add_attributes' is deprecated sinc V1.N.5. Use setAttributes() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - // If overwrite is not set, then we must make sure that we don't overwrite any of - // the existing attributes with the same name. - if (!$overwrite) { - $aExistingAttributes = $this->getAttributes($absoluteXPath); - $aExistingAttributes = array_intersect($aExistingAttributes, $attributes); - $aNewAttributes = $attributes; - @reset($aExistingAttributes); - while(list($name, $value) = each($aExistingAttributes)) { - unset($aNewAttributes[$name]); - return $this->setAttributes($absoluteXPath, $aNewAttributes); - } else { - return $this->setAttributes($absoluteXPath, $attributes); - } - } - - /** - * Sets the attributes of a node. - * - * This method sets the attributes of a node and overwrites all existing - * attributes by doing this. - * - * @deprecated Use setAttributes() instead - * @param string $absoluteXPath Full document path of the node, - * the attributes of which should be set. *READONLY* - * @param array $attributes Associative array containing the new - * attributes for the node. *READONLY* - * @see getAttributes(), setAttributes(), removeAttributes() - */ - function set_attributes ($absoluteXPath, $attributes) { - // Set the attributes of the node. - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'set_attributes' is deprecated sinc V1.N.5. Use setAttributes() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - if (!is_array($attributes)) return; - - // Remove the existing attributes - if ($overwrite) $this->removeAttributes(array_keys($this->getAttributes())); - return $this->setAttributes($absoluteXPath, $attributes); - } - - /** - * @deprecated Use getAttributes() instead - */ - function get_attributes($absoluteXPath) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_attributes' is deprecated sinc V1.N.5. Use getAttributes() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->getAttributes($absoluteXPath); - } - - /** - * @deprecated Use nodeName() instead - */ - function get_name($absoluteXPath) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_name' is deprecated sinc V1.N.5. Use nodeName() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->nodeName($absoluteXPath); - } - - - /** - * @deprecated Use nodeName() instead - */ - function get_names($absoluteXPaths) { - if ($this->deprecate_1N4_warning) { - $this->_displayError("Methode 'get_names' is deprecated sinc V1.N.5. Use nodeName() instead and read the doc.", __LINE__, $this->deprecate_1N4_abort); - } - return $this->nodeName($absoluteXPaths); - } - - //////////////////////////////////////////////////////////////////////////////// - //#################### Public Interface ######################################// - //////////////////////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////// - // ########################################### // - // Constructor of the class. - - /** - * Constructor of the class - * - * This constructor initializes the class and, when a filename is given, - * tries to read and parse the given file. - * You may also set the XML parsing parameters with an array. - * E.g. $xmlOpt = array(XML_OPTION_CASE_FOLDING => FALSE); - * - * @param $fileName string Path and name of the file to read and parsed. - * @param $userXmlOptions array vector array of (=>, =>, ...) - * @see importFromFile(), importFromString() - */ - function XPath($fileName='', $userXmlOptions=array()) { - // Set the options for parsing the XML data. - // Per default we want to keep spaces in the CDATA, as most people generally want to - // keep space, and you can call trim() but there's no untrim() is there? Francis Fillion - $this->xmlOptions[XML_OPTION_CASE_FOLDING] = FALSE; - $this->xmlOptions[XML_OPTION_SKIP_WHITE] = FALSE; - // Don't use PHP's array_merge! - reset($userXmlOptions); - while (list($key) = each($userXmlOptions)) { - $this->xmlOptions[$key] = $userXmlOptions[$key]; - } - - // Check whether a file was given. - if (!empty($fileName)) { - // Load the XML file. - $this->importFromFile($fileName); - } - } - - /** - * Returns the property/ies you want. - * - * if $param is not given, all properties will be returned in a hash. - * the following params are allowed: - * - fileName - * - hasContent - * - caseFolding - * - skipWhiteSpaces - * - * @author fab, sam - * @param string $param - * @return mixed (string OR hash of all params) - * @throws NULL on an unknown param. - */ - function getProperties($param=NULL) { - $properties = NULL; - if (empty($properties)) { - $properties = array( - 'fileName' => $this->fileName, - 'hasContent' => $this->_objectHasContent(), - 'caseFolding' => $this->xmlOptions[XML_OPTION_CASE_FOLDING], - 'skipWhiteSpaces' => $this->xmlOptions[XML_OPTION_SKIP_WHITE], - 'verboseLevel' => $this->_verboseLevel - ); - } - - if (empty($param)) return $properties; - - if (isSet($properties[$param])) { - return $properties[$param]; - } else { - return NULL; - } - } - - /** - * returns the last occured error message. - * @author fab - * @access public - * @return string (may be empty if there was no error at all) - * @see _setLastError(), _lastError - */ - function getLastError() { - return $this->_lastError; - } - - - ///////////////////////////////////////////////// - // ########################################### // - // Input - /** - * Controls whether case-folding is enabled for this XML parser. - * - * In other words, when it comes to XML, case-folding simply means uppercasing - * all tag- and attribute-names (NOT the content) if set to TRUE. - * - * @author Sam Blum - * @param $onOff bool (default TRUE) - */ - function setCaseFolding($onOff=TRUE) { - $this->xmlOptions[XML_OPTION_CASE_FOLDING] = $onOff; - } - - /** - * Controls whether skip-white-spaces is enabled for this XML parser. - * - * In other words, when it comes to XML, skip-white-spaces will trim - * the tag content (=the CDATA) - * - * @author Sam Blum - * @param $onOff bool (default TRUE) - */ - function setSkipWhiteSpaces($onOff=TRUE) { - $this->xmlOptions[XML_OPTION_SKIP_WHITE] = $onOff; - } - - /** - * xml_parser_set_option -- set options in an XML parser. - * - * See 'XML parser functions' in PHP doc - * - * @author Sam Blum - * @param $optionID int The option ID (e.g. XML_OPTION_SKIP_WHITE) - * @param $value int The option value. - * @see XML parser functions in PHP doc - */ - function setXmlOption($optionID, $value) { - if (!is_numeric($optionID)) return; - $this->xmlOptions[$optionID] = $value; - } - - /** - * Turn verbose (error) output ON or OFF - * Pass a bool. TRUE to turn on, FALSE to turn off. - * Pass a int >0 to reach higher levels of verbosity (for future use). - * - * @author Sam Blum - * @param mixed $levelOfVerbosity (default is 0 = off) - */ - function setVerbose($levelOfVerbosity = 1) { - $level = -1; - if ($levelOfVerbosity === TRUE) { - $level = 1; - } elseif ($levelOfVerbosity === FALSE) { - $level = 0; - } elseif (is_numeric($levelOfVerbosity)) { - $level = $levelOfVerbosity; - } - if ($level >= 0) $this->_verboseLevel = $levelOfVerbosity; - } - - - /** - * Reads a file and parses the XML data. - * - * This method reads the content of a XML file, tries to parse its - * content and upon success stores the information retrieved from - * the file into an array. - * - * @author Michael P. Mehl , changes by fab - * @param string $fileName Path and name of the file to be read and parsed. - * @return bool TRUE on success, FALSE on failure (check getLastError()) - * @see importFromString(), _getLastError(), - */ - function importFromFile($fileName) { - // Remember file name. Used in error output to know in which file it happend - $this->fileName = $fileName; - // If we already have content, then complain. - if ($this->_objectHasContent()) { - $this->_displayError('importFromFile() called when this object already contains xml data. Use reset().', __LINE__, FALSE); - return FALSE; - } - // Check whether the url exists or if the file exists and is readable. - if ( preg_match(';^http(s)?://;', $fileName) ) { - // Read the content of the url...this is really prone to errors, and we don't really - // check for too many here...for now, suppressing both possible warnings...we need - // to check if we get a none xml page or something of that nature in the future - $content = @implode('', @file($fileName)); - if ( empty($content) ) { - // Display an error message. - $this->_displayError("In importFromFile(): The url '{$fileName}' could not be found or read.", __LINE__, FALSE); - return FALSE; - } - } - elseif (!is_readable($fileName)) { // Read the content from the file - $this->_displayError("In importFromFile(): File '{$fileName}' could not be found or read.", __LINE__, FALSE); - return FALSE; - } - elseif (is_dir($fileName)) { - $this->_displayError("In importFromFile(): '{$fileName}' is a directory.", __LINE__, FALSE); - return FALSE; - } - // Read the content of the file. - $content = implode("", file($fileName)); - return $this->importFromString(&$content); - } - - /** - * Reads a string and parses the XML data. - * - * This method reads the content of a XML string, tries to parse its - * content and upon success stores the information retrieved from - * the string into an array. - * - * @author Francis Fillion modified from Michael P. Mehl , changes by fab - * @param string $xmlString name of the string to be read and parsed. - * @return bool TRUE on success, FALSE on failure (check getLastError()) - * @see _handleStartElement(), _handleEndElement(), - * _handleCharacterData(), importFromFile() - */ - function importFromString($xmlString) { - // If we already have content, then complain. - if ($this->_objectHasContent()) { - $this->_displayError('importFromString() called when this object already contains xml data. Use reset().', __LINE__, FALSE); - return FALSE; - } - // Check whether content has been read. - if (empty($xmlString)) { - $this->_displayError('This xml document (string) was empty', __LINE__, FALSE); - return FALSE; - } - // Create an XML parser. - $parser = xml_parser_create(); - - // Set default XML parser options. - if (is_array($this->xmlOptions)) { - reset($this->xmlOptions); - while (list($k) = each($this->xmlOptions)) { - xml_parser_set_option($parser, $k, $this->xmlOptions[$k]); - } - } - - // Set the object for the parser. - xml_set_object($parser, &$this); - // Set the element handlers for the parser. - xml_set_element_handler($parser, '_handleStartElement', '_handleEndElement'); - xml_set_character_data_handler($parser, '_handleCharacterData'); - xml_set_default_handler($parser, '_handleDefaultData'); - - // Set the document node. - $this->nodes['']['xml-declaration'] = ''; - $this->nodes['']['dtd-declaration'] = ''; - - // Parse the XML file. - if (!xml_parse($parser, $xmlString, TRUE)) { - //error - $source = empty($this->fileName) ? 'string' : 'file ' . basename($this->fileName) . "'"; - $this->_displayError("XML error in given {$source} on line ". - xml_get_current_line_number($parser). ' column '. xml_get_current_column_number($parser) . - '. Reason:'. xml_error_string(xml_get_error_code($parser)), __LINE__ ); - return FALSE; - } - - // Free the parser. - xml_parser_free($parser); - return TRUE; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Output - - /** - * Given a context this function returns the containing XML as marked up HTML - * - * This method takes the absolute path to a node in the XML object - * which is derived from the evaluate function, and it returns the - * XML that is contained within this node as a string as marked up - * html suitable for outputing inline to an HTML file for display or - * debugging reasons. So <> etc are replaced by < and > - * NOTE: Instead of an absolute path you may also pass a xpath query. - * If the result of the query leads to 1 unique node, then the - * path to that node is taken. - * - * @author Nigel Swinson - * @param string $absoluteXPath The path to the current node (see text above) - * @return string The string returned is valid XML - * @throws FALSE on error; - * @see exportAsXml(), exportToFile(), evaluate() - */ - function &exportAsHtml($absoluteXPath = '', $highlight = array()) { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - - if (is_array($absoluteXPath)) { - $this->_displayError('exportAsHtml() called with array as $absoluteXPath parameter. '. - 'This is not supported', __LINE__, FALSE); - return FALSE; - } - $level = 0; - if (!empty($absoluteXPath)) { - // Check that they gave us the path to one of the nodes. - if (!isSet($this->nodes[$absoluteXPath])) { - // Display an error message. - $this->_displayError('exportAsHtml() called with $absoluteXPath parameter that does not '. - 'describe a single node in the XML object.', __LINE__, FALSE); - return FALSE; - } - $level = $this->nodes[$absoluteXPath]['doc-pos']; - } - return $this->_export($highlight, $absoluteXPath, $level, 0); - } - - /** - * Given a context this function returns the containing XML - * - * This method takes the absolute path to a node in the XML object - * which is derived from the evaluate function, and it returns the - * XML that is contained within this node as a string. - * NOTE: Instead of an absolute path you may also pass a xpath query. - * If the result of the query leads to 1 unique node, then the - * path to that node is taken. - * - * @author Nigel Swinson - * @param string $absoluteXPath The path to the current node (see text above) - * @return string The string returned is valid XML - * @throws FALSE on error; - * @see exportAsHtml(), exportToFile(), evaluate() - */ - function &exportAsXml($absoluteXPath = '') { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - - $level = 0; - if (!empty($absoluteXPath)) { - $level = $this->nodes[$absoluteXPath]['doc-pos']; - } - return $this->_export(array(), $absoluteXPath, $level, 1); - } - - /** - * Generates a XML file with the content of the relevant portion of the current document. - * - * This method creates a string containing the XML data being read - * and modified by this class before. This string can be used to save - * a modified document back to a file or doing other nice things with - * it. It encludes a tag at the start of the data too. - * NOTE: Instead of an absolute path you may also pass a xpath query. - * If the result of the query leads to 1 unique node, then the - * path to that node is taken. - * - * @author Nigel Swinson - * @param string $fileName - * @param string $absoluteXPath The path to the current node (see text above) - * @param string $xmlHeader ( default is '< ? xml version="1.0" ? >' ) - * @return string The returned string contains well-formed XML data - * representing the content of this document suitable for - * writing out to a file. - * @throws FALSE on error; - * @see importFromFile(), evaluate(), exportAsHtml() - */ - function exportToFile($fileName, $absoluteXPath='', $xmlHeader='') { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - - // Open the file and we'll write out to it. - $hFile = fopen($fileName, "w"); - - // Did we open the file ok? - $bResult = TRUE; - if (!$hFile) { - $this->_displayError("Failed to open the $fileName xml file.", __LINE__, FALSE); - $bResult = FALSE; - } else { - // Lock the file - if (!flock($hFile, LOCK_EX)) { - $this->_displayError("Couldn't get an exclusive lock on the $fileName file.", __LINE__, FALSE); - fclose($hFile); - return FALSE; - } - - // setup the header, either by what came from the dom object or the default one - if ( preg_match(";<\?xml.*?\?>;",$this->nodes['']['xml-declaration']) ) - $xmlHeader = trim($this->nodes['']['xml-declaration']); - if ( trim($this->nodes['']['dtd-declaration']) != '' ) - $xmlHeader .= "\n".trim($this->nodes['']['dtd-declaration']); - // Get the relevant object as a string and write it to file - $xmlString = $this->exportAsXml($absoluteXPath); - if (!fwrite($hFile, $xmlHeader."\n".$xmlString)) { - $this->_displayError("Write error when writing back the $fileName file.", __LINE__, FALSE); - $bResult = FALSE; - } - - // Flush and unlock the file - fflush($hFile); - flock($hFile, LOCK_UN); - - if (!fclose($hFile)) { - $this->_displayError("Failed to close the $fileNamefile.", __LINE__, FALSE); - $bResult = FALSE; - } - } - } - - ///////////////////////////////////////////////// - // ########################################### // - // Search. - - /** - * Evaluates an XPath expression. - * - * This method tries to evaluate an XPath expression by parsing it. A - * XML document has to be read before this method is able to work. - * - * @param string $xPathQuery XPath expression to be evaluated. - * @param string $context Full path of a document node, starting - * from which the XPath expression should be evaluated. - * @return array The returned array contains a list of the full - * document paths of all nodes that match the evaluated - * XPath expression. - * @throws FALSE on error; - */ - function &match($xPathQuery, $baseXPath='') { - return $this->evaluate($xPathQuery, $baseXPath); - } - - /** - * Alias for the match function - * - * @see evaluate() - */ - function &evaluate($xPathQuery, $baseXPath='') { - // Starting point of the user sending an xPath query - static $slashes2descendant = array('//@'=>'/descendant::*/attribute::', '//'=>'/descendant::', '/@'=>'/attribute::'); - - if (empty($xPathQuery)) return array(); - - // Numpty check - if (!empty($baseXPath) && !isSet($this->nodes[$baseXPath])) { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'],$absoluteXPath), __LINE__, FALSE); - return FALSE; - } - - // Convert all entities. - $xPathQuery = strtr($xPathQuery, array_flip($this->entities)); - - // Replace a double slashes, because they'll cause problems otherwise. - $xPathQuery = strtr($xPathQuery, $slashes2descendant); - - /* By Dan Allen - I know the evaluate function is under review right now and I realize - that it is very complex...however, I thought of one out that should - definitely be in place...if a user specifies the full path - like - /root[1]/node[1]/child[1] - it should just return immediately - if(in_array($xPathQuery,array_keys($this->nodes))) { - return array($xPathQuery); - } - why even look? - */ - - // Stupid idea from W3C to take axes name containing a '-' (dash) - // Instead of the '-' in the names we use '_'. - $xPathQuery = strtr($xPathQuery, $this->dash2underscoreHash); - - return $this->_internalEvaluate($xPathQuery, $baseXPath); - } - - ///////////////////////////////////////////////// - // ########################################### // - // Element Name access - - /** - * Retrieves the names of a group of document nodes. - * - * This method retrieves the names of a group of document nodes - * specified in the argument. So if the argument was '/A[1]/B[2]' then it - * would return 'B' if the node did exist in the tree. - * - * @param array or string $absoluteXPath Array or single full document - * path(s) of the node(s), from which the names should be - * retrieved. - * @return array or string The returned array contains either an array - * of the names of the specified nodes, or just the individual - * name. - */ - function &nodeName($absoluteXPath) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction = FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction('nodeName'); - echo "Node: $absoluteXPath\n"; - echo "
"; - } - - ////////////////////////////////// - - // Did they ask for more than one name? - $parmIsString = FALSE; - $paths = null; - if (is_string($absoluteXPath)) { - $parmIsString = TRUE; - $paths[] = $absoluteXPath; - } else { - $paths = &$absoluteXPath; - } - - // Build the results array - $names = array(); - $size = sizeOf($paths); - // Get each name in turn. - for ($i=0; $i<$size; $i++) { - $path = &$paths[$i]; - // Numpty check - // Check that the path exists - if (isSet($this->nodes[$path])) { - $names[] = $this->nodes[$path]['name']; - } else { - $this->_displayError("The path '$path' isn't a path of this class.", __LINE__, FALSE); - } - } - if ($parmIsString) { - if (sizeOf($names)) $result = $names[0]; else $result = ''; - } else { - $result = $names; - } - - ////////////////////////////////// - - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - return $result; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Attribute modification - - /** - * Retrieves a list of all attributes of a node. - * - * This method retrieves a list of all attributes of the node specified in - * the argument. If you only want the value of 1 attribute, then you may - * specify that attribute as a parameter - * - * @param string $absoluteXPath Full document path of the node, from - * which the list of attributes should be retrieved. *READONLY* - * @param string $attribute the name of the attribute that you wish to - * retrieve or empty if you wish to retrive all of the attributes - * in an associative array. *READONLY* - * @return array or string The returned associative array contains the all - * attributes of the specified node, or the individual $attribute - * if that parameter was specified. - * @throws FALSE on error; - * @see removeAttribute() - */ - function getAttributes($absoluteXPath, $attribute = '') { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - - // Check that there is a node. - if (!isSet($this->nodes[$absoluteXPath])) { - if (is_array($attribute)) return array(); - else return ''; - } - // Get the attributes of the node. - $aAttributes = isset($this->nodes[$absoluteXPath]['attributes']) - ? $this->nodes[$absoluteXPath]['attributes'] - : array(); - // Return the complete list or just the desired element - if (empty($attribute) or is_array($attribute)) - return $aAttributes; - else - return $aAttributes[$attribute]; - } - - /** - * Set attributes to a node. - * - * This method sets a number of attributes. Existing attributes - * overwritten with the new values, but existing attributes will not be - * overwritten. - * - * @param string $xPathQuery Full document path of the node, the attributes - * should be added to. - * @param array $attributes Associative array containing the new - * attributes for the node - * @see getAttribute(), removeAttribute() - */ - function setAttribute($absoluteXPath, $name, $value) { - return $this->setAttributes($absoluteXPath, array($name => $value)); - } - - /** - * Version of setAttribute() that sets multiple attributes. - * - * NOTE: Instead of an absolute path you may also pass a xpath-query. - * All nodes that match the xpath query will be modified. - * - * @param string $xPathQuery The path to the current node (see text above) - * @param array $attributes associative array of attributes to set. - * @see setAttribute() - */ - function setAttributes($xPathQuery, $attributes) { - // The attributes parameter should be an associative array. - if (!is_array($attributes)) return; - - // Numpty check - $xpv = array(); - if (isSet($this->nodes[$xPathQuery])) { - $xpv[] = &$xPathQuery; - } else { - // Try to evaluate the $xPathQuery; if it returns only 1 node use it; otherwise give up. - $xpv = &$this->match($xPathQuery); - if (sizeOf($xpv) ==0) { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $xPathQuery), __LINE__); - } - } - - $xpvSize = sizeOf($xpv); - for ($k=0; $k < $xpvSize; $k++) { - $absoluteXPath = &$xpv[$k]; - // Add the attributes to the node. - if (isSet($this->nodes[$absoluteXPath]['attributes'])) { - $this->nodes[$absoluteXPath]['attributes'] = - array_merge($this->nodes[$absoluteXPath]['attributes'],$attributes); - } else { - $this->nodes[$absoluteXPath]['attributes'] = $attributes; - } - } // END for ($k=0; $k < $xpvSize; $k++) - } - - - /** - * Removes an attribute of a node. - * - * This method removes either a single, or a group of attributes from a node. - * - * @param string $absoluteXPath Full document path of the node, from - * which the list of attributes should be retrieved. *READONLY* - * @param string or array $attribute the name or names of the attribute(s) - * that you wish to remove. If $attribute is empty, then all - * attributes will be removed for the node. *READONLY* - * @see getAttribute(), setAttribute() - */ - function removeAttribute($absoluteXPath, $attribute = '') { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__); - } - } - - // If the attribute parameter wasn't set then remove all the attributes - if (!isSet($attribute)) { - unset($this->nodes[$absoluteXPath]['attributes']); - return; - } - - // If the attribute parameter isn't an array then we have just to remove the - // one attribute - if (!is_array($attribute)) { - if (isset($this->nodes[$absoluteXPath]['attributes'])) - unset($this->nodes[$absoluteXPath]['attributes'][$attribute]); - return; - } - - // Remove all the elements in the array then. - @reset($attribute); - while(list(, $name) = each($attribute)) { - unset($this->nodes[$absoluteXPath]['attributes'][$name]); - } - return; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Element Content modification - - /** - * Retrieve all the text from a node as a single string. - * - * Simplified wholeText(). This function is not actually in any spec and is - * therefore more of a helper "shortcut." - * - * @author Sam Blume and Daniel Allen - * @param string $absoluteXPath Full document path of the node, from - * which the text should be retrieved (any text() will be truncated). *READONLY* - * @return string The returned string contains either the value or the - * character data of the node. If the node had mixed data, it concats all the - * text nodes into a single string - * @see wholeText() - */ - function &getData($absoluteXPath) { - // If this node had a text() node specified at the end, we want to truncate - // text() part of the XPath, since this function is intended primarily for novice users - if ( preg_match(":(.*)/text\(\)(\[(.*)\])?$:U",$absoluteXPath) ) { - $absoluteXPath = substr($absoluteXPath,0,strrpos($absoluteXPath,"/text()")); - } - return $this->wholeText($absoluteXPath); - } - - /** - * Retrieves the normalized text of a node as an array of all the text() node contents. - * - * Retrieves the text content of a node as an array, where each element - * of the array was interrupted by a non-text child element. So if the node - * was 1234 Then getDataParts('a[1]') would return ('1','3','4'). - * If the xpath terminated as a text() node, it will be truncated since this function - * is intended primarily for novice users. - * - * @author Sam Blume and Daniel Allen - * @param string $absoluteXPath Full document path of the node, from - * which the content should be retrieved as an array (xpath ending in a text() - * node will be truncated). *READONLY* - * @return array The returned array contains strings that are either the value or the - * character data of the node. - * @throws FALSE on error; - * @see getData(), wholeText() - */ - function &getDataParts($absoluteXPath) { - $text = array(); - // If this node had a text() node specified at the end, we want to truncate - // text() part of the XPath, since this function is intended primarily for novice users - if ( preg_match(":(.*)/text\(\)(\[(.*)\])?$:U",$absoluteXPath) ) { - $absoluteXPath = substr($absoluteXPath,0,strrpos($absoluteXPath,"/text()")); - } - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - // Return the cdata of the node. - @reset($this->nodes[$absoluteXPath]['text']); - while(list(, $textNode) = each($this->nodes[$absoluteXPath]['text'])) { - $text[] = $textNode; - } - return $text; - } - - /** - * Retrieves and concats the contents of all the text nodes for a given parent - * - * In the recommendation for the XPath specification level 3, W3C makes the suggestion - * for a function called wholeText() which returns all of the content of the text nodes - * for a particular parent XPath, so as to eliminate only the non-text child nodes and - * return the data...what I am not sure of, is if this is supposed to be recursive into - * the text of the child nodes along side the text nodes of the original parent - * - * @author Daniel Allen - * @param string $absoluteXPath Full document path of the node, from - * which the content should be retrieved. *READONLY* - * @return string The returned string contains the concat cdata of all of the - text nodes - * @see getData(), getDataParts(), wholeText(), substringData() - **/ - function wholeText($absoluteXPath) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction('wholeText'); - echo "Node: $absoluteXPath\n"; - echo "
"; - } - - // Try block - do { - // if a specific text node was given, we actually just want the substringData function - if ( preg_match(":text\(\)(\[\d*\])?$:",$absoluteXPath) ) { - if ($bDebugThisFunction) echo "The xpath expression contained a :text() function.\n"; - $result = $this->substringData($absoluteXPath); - break; - } - - // if an attribute node was given, we actually just want the substringData function - if ( preg_match(";(.*)/(attribute::|@)([^/]*)$;U",$absoluteXPath) ) { - if ($bDebugThisFunction) echo "The xpath expression pointed to an attribute node.\n"; - $result = $this->substringData($absoluteXPath); - break; - } - - // Must have been just a node then. - - // Does it point direct to a single node? - if ( !isSet($this->nodes[$absoluteXPath]) ) { - if ($bDebugThisFunction) echo "The xpath expression isn't a node in the tree\n". - "Checking to see if it is an XPath expression for a single node.\n"; - // Try to evaluate the absoluteXPath (since it really isn't an absolutePath) - $resultArr = $this->match($absoluteXPath); - if ( sizeOf($resultArr) == 1 ) { - $absoluteXPath = $resultArr[0]; - } - else { - $this->_displayError("The $absoluteXPath does not evaluate to a single node in this document.", __LINE__, FALSE); - $result = ''; - break; - } - } - - if ($bDebugThisFunction) { - echo "The xpath expression $absoluteXPath points to a single node in the tree with text nodes:\n"; - print_r($this->nodes[$absoluteXPath]['text']); - } - $result = implode('',$this->nodes[$absoluteXPath]['text']); - } while (0); - - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - return $result; - } - - /** - * Retrieves all or part of the content of a text node. - * - * This method retrieves the content of a specific text node. If it's an attribute - * node, then the value of the attribute will be retrieved, otherwise - * it'll be the character data of the node. The node specified must be - * a text node (if not an attribute), or else the first text node of the parent will be used. - * The consideration just mentioned will only pertain to elements with mixed content, where - * the text is divided by other non-text child nodes, such as in html data. In that case, - * these text nodes are actually distinct nodes called text() nodes. Obviously, elements with - * only text as a child will have only a single text() node. - * - * @author Michael P. Mehl modified by Daniel Allen - * @param string $absoluteXPath Full document path of the text() node, from - * which the content should be retrieved. *READONLY* - * @param number $offset Return the string starting at this offset. - * *READONLY* - * @param number $count Return a maximum of count characters. 0 means - * entire string. *READONLY* - * @return string The returned string contains either the cdata of the text node - * @see wholeText(), getData(), getDataParts() - */ - function &substringData($absoluteXPath, $offset = 0, $count = 0) { - if ( $offset < 0 or !is_int($offset) ) $offset = 0; - // we can just use _setContent and append nothing and then return it, same exact - // code, no reason to write it twice - return $this->_setContent($absoluteXPath, "", FALSE, $offset, $count); - } - - /** - * Set the content of a node - * - * This method sets the content of a node. If it's an attribute node, then - * the value of the attribute will be set, otherwise the character data of - * the node will be set. It throws an error if data is already present - * - * @param string $absoluteXPath Full document path to the text node - * @param string $value String containing the context to be set - * @see appendData, replaceData(), deleteData() - */ - function insertData($absoluteXPath, $value, $offset = 0) { - if ( $offset < 0 or !is_int($offset) ) $offset = 0; - $this->_setContent($absoluteXPath, $value, FALSE, $offset); - } - - /** - * Replace the content of a text() node. - * - * This method replaces the content of a node. If it's an attribute node, then - * the value of the attribute will be set, otherwise the character data of - * the node will be set. Existing content will be overwritten. - * - * @param string $absoluteXPath Full document path of the text node. *READONLY* - * @param string $value String containing the content to be set. *READONLY* - * @see appendData(), deleteData() - */ - function replaceData($absoluteXPath, $value, $offset = 0, $count = 0) { - if ( $offset < 0 or !is_int($offset) ) $offset = 0; - // TRUE for replace the data for the selection, offset and count self explanatory - $this->_setContent($absoluteXPath, $value, TRUE, $offset, $count); - } - - /** - * Replace a node with an unprocessed (unparsed) text string. - * - * This function will delete the node you define by $absoluteXPath - * (plus it's sub-nodes) and substitute it by the string $text. - * Often used to push in not well formed HTML. - * WARNING: - * The $data is taken 1:1 (= not even an entity converting is done). - * Your in charge that the data you enter is valid XML if you intend - * to export and import it again the content. - * NOTE: Instead of an absolute path you may also pass a xpath-query. - * All nodes that match the xpath query will be modified. - * - * @author Sam Blum - * @param string $xPathQuery path to the node (See text above). *READONLY* - * @param string $data String containing the content to be set. *READONLY* - * @return TRUE on success - * @throws FALSE on error; - * @see replaceChild() - */ - function replaceChildByData($xPathQuery, $data) { - // Numpty check - $xpv = array(); - if (isSet($this->nodes[$xPathQuery])) { - $xpv[] = &$xPathQuery; - } else { - // Try to evaluate the $xPathQuery; if it returns only 1 node use it; otherwise give up. - $xpv = &$this->match($xPathQuery); - if (sizeOf($xpv) ==0) { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $xPathQuery), __LINE__, FALSE); - return FALSE; - } - } - - $xpvSize = sizeOf($xpv); - for ($k=0; $k < $xpvSize; $k++) { - $absoluteXPath = &$xpv[$k]; - $theNode = &$this->nodes[$absoluteXPath]; - $contextPos = $theNode['context-pos']; - $parentNode = &$this->nodes[$theNode['parent']]; - /** - echo "** " .$contextPos ." **
"; - echo "** " . var_dump($parentNode['text']) ." **
"; - **/ - $parentNode['text'][$contextPos-1] .= $data; - $this->removeChild($absoluteXPath, $keepSubnodes=FALSE); - } - return TRUE; - } - - - /** - * Replaces a node in the xml document - * - * This function will replace the entire content of the specified node(s) with - * the specified string. The string may either be xml data that will be parsed - * and inserted into the tree creating new grand children as necessary, or can - * be a plain text string that may be not well formed xml data and will be handled - * as text. Additionally to preserve the integrity of the xml structure facilitating - * export of the object you may specify that the string is to be treated as - * . - * - * examples: - * replaceChild('/root[1]/element[1]',''); - * replaceChild('/root[1]/element[1]','Html
Data', FALSE); - * replaceChild('/root[1]/element[1]','Html
Data that id like to be able '. - * 'to export to file
some day', FALSE, TRUE); - * - * NOTE: Instead of an absolute path you may also pass a xpath-query. - * All nodes that match the xpath query will be modified. - * NOTE: If you replace as CData, then when you exportAsXml, you will need to pass - * the $bTrimCData parameter. - * - * @param string $xPathQuery Full document path of the node. *READONLY* - * @param string $xmlData String containing either the XML data or the - * non well formed string that will be inserted as is. *READONLY* - * @param bool $asXml Flag that specifies whether xmlData holds XML data that - * will be parsed as xml and inserted, modifying the existing tree, - * or whether it should just be replace as text content, unparsed. - * @param bool $asCData places tags round xmlData so that the class - * may be exported to file. - */ - function replaceChild($absoluteXPath, $xmlData, $asXml=TRUE, $asCData=FALSE) { - // #### Not realy implemented yet. - - /**--------------------------------**/ - /** --sam Following is only temp. **/ - $this->replaceChildByData($absoluteXPath, $xmlData); - $xmlData = &$this->exportAsXml(); - $this->reset(); - return $this->importFromString($xmlData); - /**--------------------------------**/ - - $this->_displayError("Not implemented yet", __LINE__); - } - - /** - * Appends an xml string to the end of the text section of a node. - * - * This function will append XML after a node defined by the parameter - * xPathQuery (that is a absoluteXPath or xpath-query). - * NOTE: Instead of an absolute path you may also pass a xpath-query. - * All nodes that match the xpath query will be modified. - * - * #### Remove appendXml() and put it's functionality into appendChild() - * - * Not optimized at all!! - * - * @author Sam Blum - * @param string $xPathQuery Full document path of the node. *READONLY* - * @param string $xmlData String containing the XML data. *READONLY* - * @see replaceNodeByXml() - */ - function appendXml($xPathQuery, $xmlData) { - // Numpty check - $xpv = array(); - if (isSet($this->nodes[$xPathQuery])) { - $xpv[] = &$xPathQuery; - } else { - // Try to evaluate the $xPathQuery; if it returns only 1 node use it; otherwise give up. - $xpv = &$this->match($xPathQuery); - if (sizeOf($xpv) ==0) { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $xPathQuery), __LINE__); - } - } - - $xpvSize = sizeOf($xpv); - for ($k=0; $k < $xpvSize; $k++) { - $absoluteXPath = &$xpv[$k]; - - $theNode = &$this->nodes[$absoluteXPath]; - // Check boarders - if (!isSet($theNode['text'])) $theNode['text'][] = ''; - $maxPos = sizeOf($theNode['text'])-1; - $theNode['text'][$maxPos] .= $xmlData; - } - $xmlData = &$this->exportAsXml(); - $this->reset(); - return $this->importFromString($xmlData); - } - - /** - * Append text content to the end of the text for a node. - * - * This method adds content to a node. If it's an attribute node, then - * the value of the attribute will be set, otherwise the character data of - * the node will be set. The content is appended to existing content, - * so nothing will be overwritten. - * - * @param string $xPathQuery Full document path of the node. *READONLY* - * @param string $value String containing the content to be added. *READONLY* - * @see replaceData(), deleteData() - */ - function appendData( $absoluteXPath, $value ) { - // FALSE for no replace, -1 offset for the end of the string - $this->_setContent( $absoluteXPath, $value, FALSE ); - } - - /** - * Delete text content of a node. - * - * Deletes a max of $count characters starting at $offset from the text content - * for a node. - * - * @param string $xPathQuery Full document path of the node. *READONLY* - * @param number $offset Return the string starting at this offset. - * *READONLY* - * @param number $count Return a maximum of count characters. 0 means - * entire string. *READONLY* - * @return string The new text value. - */ - function deleteData($absoluteXPath, $offset = 0, $count = 0) { - if ( $offset < 0 or !is_int($offset) ) $offset = 0; - // Now delete the data starting at offset for count characters... - // count defaults to the length string, whereas $offset defaults to the beginning - // if neither offset nor count are given, the whole string is deleted - $this->_setContent( $absoluteXPath, "", TRUE, $offset, $count ); - } - - ///////////////////////////////////////////////// - // ########################################### // - // Element alteration - - /** - * Removes a node from the XML document. - * - * This method removes a node from the tree of nodes of the XML document. - * If the node is a document node, remove it. Depending on the parameter - * $keepSubnodes all children nodes are moved up one level (TRUE) or - * the node and the subnodes are removed with all character data. - * If the node is an attribute node, - * only this attribute will be removed, the node to which the attribute - * belongs as well as its children will remain unmodified. - * NOTE: Instead of an absolute path you may also pass a xpath query. - * If the result of the query leads to 1 unique node, then the - * path to that node is taken. - * - * @author Sam Blum - * @param string $xPathQuery The path to the current node (see text above) - * @param bool $keepSubnodes default TRUE = move subnodes up / FALSE cascaded delete - * @return TRUE on success - * @throws FALSE on error; - * @see appendChild(), evaluate() - */ - function removeChildSam($xPathQuery, $keepSubnodes=FALSE) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - - // Numpty check - $xpv = array(); - if (isSet($this->nodes[$xPathQuery])) { - $xpv[] = &$xPathQuery; - } else { - // Try to evaluate the $xPathQuery; if it returns 0 nodes give up; otherwise loop through the result. - $xpv = &$this->match($xPathQuery); - if (sizeOf($xpv) ==0) { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $xPathQuery), __LINE__, FALSE); - return FALSE; - } - } - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction('removeChild'); - echo "Node: $xPathQuery\n"; - echo "
"; - } - $xpvSize = sizeOf($xpv); - for ($k=0; $k < $xpvSize; $k++) { - $absoluteXPath = &$xpv[$k]; - do { // try block - ////////////////////////////////////////////// - // Check whether it's an attribute node. - $lastSlashPos = strrpos($absoluteXPath, '/') -1; - $attrPos = ($lastSlashPos>=0) ? strpos($absoluteXPath, '/attribute::', $lastSlashPos) : FALSE; - - if ($attrPos !== FALSE) { - if ($bDebugThisFunction) echo "We are removing an attribute node\n"; - // Extract the path to the node. - $thePath = substr($absoluteXPath, 0, $attrPos); - - // Get the name of the attribute. - $attribute = $this->_afterstr($absoluteXPath, '/attribute::'); - - // Exception empty attribute, ignor it. - if (strLen($attribute)==0) break; - - // Numpty check - if (isSet($this->nodes[$thePath])) { - // Unset the attribute - unSet($this->nodes[$thePath]['attributes'][$attribute]); - } - break; // try block - } - - $nodeList = array(); - // Get abs. parent xpath - $absoluteParentPath = $this->nodes[$absoluteXPath]['parent']; - - if ($bDebugThisFunction) { - echo "\nWe want to remove '$absoluteXPath'. The parent of the 'to remove' node at '$absoluteParentPath' is "; - echo "
styleSheet['Node']."\">\n"; - print_r($this->nodes[$absoluteParentPath]); - echo "
\n"; - } - - // Get a copy of the current parent - $copyOfParrent = $this->nodes[$absoluteParentPath]; - - // Remember the node name fragment to be removed e.g. BBB[2] - $nodenameToBeRemoved = substr($absoluteXPath, strrpos($absoluteXPath, '/')+1); - // Remember the pos of the node in the parents child list e.g. it's the 3rd child. - $nodePosInParent = array_search($nodenameToBeRemoved, $this->nodes[$absoluteParentPath]['children']); - if ($bDebugThisFunction) echo "Node to remove is at pos [{$nodePosInParent}] in the parents child list.\n"; - - // Do we have to move the sub nodes 1 leval up - if ($keepSubnodes) { - // Get a list of all child nodes that must be move up - $vxpChildrenNodes = $this->evaluate($absoluteXPath.'/*'); - - // Save chiled list in a sub tree stucture by cutting off the - // parent path fragment. That is chopping off the head. - // Unset the values in the main tree. - $headLeng = strLen($absoluteXPath); - $size = sizeOf($vxpChildrenNodes); - for ($i=0; $i<$size; $i++) { - $corePath = subStr($vxpChildrenNodes[$i], $headLeng); - $nodeList[$corePath] = &$this->nodes[$vxpChildrenNodes[$i]]; - unset($this->nodes[$vxpChildrenNodes[$i]]); - } - unset($this->nodes[$absoluteXPath]); - - // Now add the nodes again - reset($nodeList); - while (list($xpath) = each($nodeList)) { - $splitPos = strrpos($xpath, '/'); - $frontNodePart = substr($xpath, 0, $splitPos); - $lastNode = substr($xpath, $splitPos); - $lastNode = substr($lastNode, 1, strrpos($lastNode, '[')-1); - $newParentPath = $absoluteParentPath . $frontNodePart; - $newParentPath =$this->appendChild($newParentPath, $lastNode); - if (!empty($nodeList[$xpath]['attributes'])) { - $this->nodes[$newParentPath]['attributes'] = $nodeList[$xpath]['attributes']; - } - $this->nodes[$newParentPath]['text'] = $nodeList[$xpath]['text']; - //echo "
newParentPath:[$newParentPath], lastNode:[$lastNode]"; - } - } - ////////////////////////////////////// - // Now we have to clean up the parent. - // Make a new children list - $newChildSize = sizeOf($this->nodes[$absoluteParentPath]['children']); - $oldChildSize = sizeOf($copyOfParrent['children']); - $newCildren = array(); - $offset = 0; - for ($i=0; $i<$oldChildSize; $i++) { - if ($i == $nodePosInParent) { - for ($j=$oldChildSize; $j<$newChildSize; $j++) { - $newCildren[$offset++] = $this->nodes[$absoluteParentPath]['children'][$j]; - } - } else { - $newCildren[$offset++] = $copyOfParrent['children'][$i]; - } - } - $this->nodes[$absoluteParentPath]['children'] = $newCildren; - - // Correct the childCount - // Node fragment to be removed snip from BBB[2] to BBB - $nodenameToBeRemoved = substr($nodenameToBeRemoved, 0, strrpos($nodenameToBeRemoved, '[')); - $chiledCountHash = &$this->nodes[$absoluteParentPath]['childCount']; - if (isSet($chiledCountHash[$nodenameToBeRemoved])) { - if ($chiledCountHash[$nodenameToBeRemoved] > 1) { - $chiledCountHash[$nodenameToBeRemoved]--; - } else { - unset($chiledCountHash[$nodenameToBeRemoved]); - } - } - - // Now merge the 2 text parts that used to be split by the node - $newTextParts = array(); - $offset=0; - $textArray = &$this->nodes[$absoluteParentPath]['text']; - for ($i=0; $istyleSheet['ParentNode']."\">\n"; - print_r($this->nodes[$absoluteParentPath]); - echo "\n"; - } - } while(FALSE); // END try block - } // END for ($k=0; $k < $xpvSize; $k++) - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, ''); - } - return; - } - - /** - * Removes a node from the XML document. - * - * This method removes a node from the tree of nodes of the XML document. - * If the node is a document node, all children of the node and its - * character data will be removed. If the node is an attribute node, - * only this attribute will be removed, the node to which the attribute - * belongs as well as its children will remain unmodified. - * - * @author Nigel Swinson - * @param string $absoluteXPath Full path of the node to be removed. - * @return TRUE on success - * @throws FALSE on error; - * @see appendChild(), hasChildNodes(), evaluate() - */ - function removeChild($absoluteXPath) { - // Numpty check - if (!isSet($this->nodes[$absoluteXPath])) { - // Try to evaluate the $absoluteXPath; if it returns only 1 node use it; otherwise give up. - $resultArr = $this->match($absoluteXPath); - if (sizeOf($resultArr)==1) { - $absoluteXPath = $resultArr[0]; - } else { - $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $absoluteXPath), __LINE__, FALSE); - return FALSE; - } - } - - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction = FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction('removeChild'); - echo "Node: $absoluteXPath\n"; - echo "
"; - } - - // Numpty check - if (empty($absoluteXPath)) { - $this->_displayError('No child to remove. Passsed parameter was empty.', __LINE__, FALSE); - return FALSE; - } - ////////////////////////////////////////////// - // Check whether the node is an attribute node. - if (preg_match('/\/attribute::/', $absoluteXPath)) { - if ($bDebugThisFunction) echo "We are removing an attribute node\n"; - // Get the path to the attribute node's parent. - $parent = $this->_prestr($absoluteXPath, '/attribute::'); - - // Get the name of the attribute. - $attribute = $this->_afterstr($absoluteXPath, '/attribute::'); - - // Unset the attribute - unSet($this->nodes[$parent]['attributes'][$attribute]); - } else { - if ($bDebugThisFunction) echo "We are removing an element node\n"; - - // Find out if the node that they gave us exists. - if (!isSet($this->nodes[$absoluteXPath])) { - // Not sure if this is quite so fatal, but I think it likely. Typically - // you will evaluate() then remove. The alternative would be just to quit. - $this->_displayError("The $absoluteXPath argument does not uniquely refer to a node in the XML file use /AAA[1]/BBB[1] format. ". basename(__FILE__).':'.__LINE__); - } - - ///////////////////////////////////// - // Get some stats about the environment of the deceased. - - // Get the name, the parent and the siblings of current node. - $nameOfDeadChild = $this->nodes[$absoluteXPath]['name']; - $parentOfDeadChild = $this->nodes[$absoluteXPath]['parent']; - $sameNameSiblings = $this->nodes[$parentOfDeadChild]['childCount'][$nameOfDeadChild]; - $siblingsCount = count($this->nodes[$parentOfDeadChild]['children']); - // Get base node number e.g. /AAA[1]/BBB[3]/CCC[2] => 2 - $contextPosOfDeadChild = $this->nodes[$absoluteXPath]['context-pos']; - $fullNameOfDeadChild = $nameOfDeadChild.'['.$contextPosOfDeadChild.']'; - // Get the child number, ie the element number within the parent. - $aChildToIndex = array_flip($this->nodes[$parentOfDeadChild]['children']); - $deadChildSiblingOrder = $aChildToIndex[$fullNameOfDeadChild]; - // Construct the common element of all the same name siblings. - $commonComponent = $parentOfDeadChild.'/'.$nameOfDeadChild; - - ///////////////////////////////////// - - // Create an associative array, which contains information about - // all nodes that required to be renamed. The linear order of the - // elements is important, as the first rename should be done first, then - // the second, etc, otherwise we will "overwrite" the members on the - // way through - $rename = array(); - if ($bDebugThisFunction) echo "\nCreating list of nodes that need renamed.\n"; - - // Now run through the younger siblings, as they must be renamed - for ( $iRunner = $contextPosOfDeadChild + 1; $iRunner <= $sameNameSiblings; $iRunner++) { - // Create the renaming entry. - $old = $parentOfDeadChild.'/'.$nameOfDeadChild.'['.$iRunner.']'; - $new = $parentOfDeadChild.'/'.$nameOfDeadChild.'['.($iRunner - 1).']'; - - $rename[$old] = $new; - } - - if ($bDebugThisFunction) { - echo "The following nodes all have to be renamed\n"; - print_r($rename); - } - - ///////////////////////////////////// - // Fixing parent. - - if ($bDebugThisFunction) { - echo "
\n";
-        echo "\nThe parent was:\n";
-        print_r($this->nodes[$parentOfDeadChild]);
-        echo ".\n";
-      }
-
-      // Decrease the number of children.
-      $this->nodes[$parentOfDeadChild]['childCount'][$nameOfDeadChild]--;
-      if ($this->nodes[$parentOfDeadChild]['childCount'][$nameOfDeadChild] == 0)
-          unset($this->nodes[$parentOfDeadChild]['childCount'][$nameOfDeadChild]);
-      
-      // Merge the text before and after the child
-      if (!empty($this->nodes[$parentOfDeadChild]['text'][$deadChildSiblingOrder])) {
-        $this->nodes[$parentOfDeadChild]['text'][$deadChildSiblingOrder] .= $this->nodes[$parentOfDeadChild]['text'][$deadChildSiblingOrder+1];
-      }
-      // Shift all the next text nodes down in the array.
-      for ($iRunner = $deadChildSiblingOrder + 1; $iRunner < $siblingsCount; $iRunner++) {
-        $this->nodes[$parentOfDeadChild]['text'][$iRunner] = $this->nodes[$parentOfDeadChild]['text'][$iRunner+1];
-      }
-      // Unset the last text node.
-      unset($this->nodes[$parentOfDeadChild]['text'][$siblingsCount]);
-
-      // Remove the child from the parents memory.  Sniff sniff :o( We must finish the grieving process!
-      unSet($this->nodes[$parentOfDeadChild]['children'][$fullNameOfDeadChild]);
-      // Go through the array, and rename all the younger same-name siblings, shifting the kids up
-      // the array as we go.
-      $contextPosOfNextSameNameSibling = $contextPosOfDeadChild + 1;
-      $fullNameOfNextSameNameSibling = $nameOfDeadChild .'['.$contextPosOfNextSameNameSibling.']';
-      for ($iRunner = $deadChildSiblingOrder + 1; $iRunner < $siblingsCount; $iRunner++) {
-        $childName = &$this->nodes[$parentOfDeadChild]['children'][$iRunner];
-        if ($childName == $fullNameOfNextSameNameSibling) {
-          $childName = $nameOfDeadChild .'['.($contextPosOfNextSameNameSibling-1).']';
-          $fullNameOfNextSameNameSibling = $nameOfDeadChild .'['.++$contextPosOfNextSameNameSibling.']';
-        }
-        $this->nodes[$parentOfDeadChild]['children'][$iRunner-1] = $childName;
-      }
-      // Unset the last child to indicate we have one less child now.
-      unset($this->nodes[$parentOfDeadChild]['children'][$siblingsCount-1]);
-
-      if ($bDebugThisFunction) {
-        echo "\n
\n";
-        echo "\nThe parent is now:\n";
-        print_r($this->nodes[$parentOfDeadChild]);
-        echo ".\n";
-        echo "\n
\n"; - } - - ///////////////////////////////////// - // Rename all the entries in the $nodes array to correct the object for the dead child. - - if ($bDebugThisFunction) echo "\nModifying node list.\n"; - - // Run through all nodes of the document. - $aNodeKeys = array_keys($this->nodes); - reset($aNodeKeys); - while (list($key, $absoluteXPathRunner) = each($aNodeKeys)) { - // skip super-Root - if (empty($absoluteXPathRunner)) continue; - - // Check to see if this node starts with the common component of all nodes that are to be renamed - if ($commonComponent == substr($absoluteXPathRunner, 0, strlen($commonComponent))) { - if ($absoluteXPath == substr($absoluteXPathRunner, 0, strlen($absoluteXPath))) { - if ($bDebugThisFunction) echo "Removing node: $absoluteXPathRunner\n"; - unset($this->nodes[$absoluteXPathRunner]); - continue; - } - - // Run through the array of nodes to be renamed. - reset($rename); - while (list($old, $new) = each($rename)) { - // Does this rename prefix match our node? - $oldLength = strlen($old); - if ($old == substr($absoluteXPathRunner, 0, $oldLength)) { - // Build the new name of this node in the nodes array. - $nameOfRenamedChild = $new.substr($absoluteXPathRunner, $oldLength); - - // Get the complete values for this node. - $values = $this->nodes[$absoluteXPathRunner]; - - // Update the context pos as we have renamed it. - if ($values['parent'] == $parentOfDeadChild) - $values['context-pos'] = $values['context-pos'] - 1; - - // We may need to rename the 'parent' of this node too. - if ($old == substr($values['parent'], 0, $oldLength)) { - $values['parent'] = $new.substr($values['parent'], $oldLength); - } - - if ($bDebugThisFunction) { - echo "Renaming node: $absoluteXPathRunner from $old to $new.\n"; -// echo "Values:\n"; print_r($values); - } - - // Add the node to the list of nodes, remove it's old entry - $this->nodes[$nameOfRenamedChild] = $values; - unset($this->nodes[$absoluteXPathRunner]); - - break; - } - } - } - } - - if ($bDebugThisFunction) { - echo "The new node list is:\n"; - print_r($this->nodes); - } - } - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time); - } - return TRUE; - } - - /** - * Adds a new node to the XML document. - * - * This method adds a new node to the tree of nodes of the XML document - * being handled by this class. The new node is created according to the - * parameters passed to this method. - * - * It it assumed that adding starts with root and new nodes must have a - * corresponding parent. Otherwise the add will be ignored. - * Node stucture: - * []['name'] // - * []['doc-pos'] // Path-'depth' starting with 0 - * []['context-pos'] // child order - * []['parent'] // - * []['children'] // array(, ...) e.g. array(AAA[1],AAA[2],BBB[1]) - * []['childCount'] // array( => , ...) - * []['attributes'] // array( => , ...) - * []['text'] // array of text parts: E.g. helloworld -> array('hello','world') - * - * @author Michael P. Mehl - * @param string $absoluteParentPath Full path of the parent, to which the new - * node should be added as a child. *READONLY* - * @param string $nodeName Name of the new node. *READONLY* - * @return string The string returned by this method will contain the - * full document path of the created node. - * @see removeChild(), hasChildNodes(), evaluate() - */ - function appendChild($absoluteParentPath, $nodeName) { - $bDebugThisFunction= FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("appendChild"); - echo "Current Node (parent): '{$absoluteParentPath}'\n"; - echo "NewNodeName (child) : '{$nodeName}' \n
"; - echo implode(' - ', array_keys($this->nodes)); - } - - static $emptyNode = array( - 'name' => '', - 'doc-pos' => 0, - 'context-pos' => 1, - 'parent' => '', - 'children' => array(), - 'childCount' => array(), - 'attributes' => array(), - 'text' => array() - ); - - // If first call assume it's root. - if (empty($this->root)) { - // Use $nodeName root element as it is the first tag. - $this->root = '/'.$nodeName.'[1]'; - $fullNameOfNewChild = $this->root; - $this->nodes[$fullNameOfNewChild] = $emptyNode; - $this->nodes[$fullNameOfNewChild]['name'] = $nodeName; - // Make a super-root. Don't use PHP's array_merge!! - reset($emptyNode); - while (list($key) = each($emptyNode)) { - $this->nodes[''][$key] = $emptyNode[$key]; - } - $this->nodes['']['childCount'] = array($nodeName=>1); - $this->nodes['']['children'][] = $nodeName.'[1]'; - } else { - // Assemble the basic path for this element. - if ($absoluteParentPath == '/') - $absoluteParentPath = ''; - - $basicPath = $absoluteParentPath.'/'.$nodeName; - - // Modify the child count for this type of node - $tmpCount = &$this->nodes[$absoluteParentPath]['childCount'][$nodeName]; - $position = isSet($tmpCount) ? $tmpCount+1: 1; - - if ($bDebugThisFunction) { - echo "\nIn parent '{$absoluteParentPath}' the child '{$nodeName}' has position '{$position}'."; - } - - /////////////////////////////////////// - // Add the new node to them nodes array. - - // Assable the full path for this element. - $fullNameOfNewChild = $basicPath.'['.$position.']'; - - // Init node if not exsisting - if (!isSet($this->nodes[$fullNameOfNewChild])) - $this->nodes[$fullNameOfNewChild] = $emptyNode; - - // Use node directly - $newChildNode = &$this->nodes[$fullNameOfNewChild]; - // Calculate the position for the following and preceding axis detection. - $newChildNode['doc-pos'] = $this->nodes[$absoluteParentPath]['doc-pos'] + 1; - // Calculate the context position, which is the position of this - // element within elements of the same name in the parent node. - $newChildNode['context-pos'] = $position; - // Save the information about the node. - $newChildNode['name'] = $nodeName; - // Set the parent - $newChildNode['parent'] = $absoluteParentPath; - - if ($bDebugThisFunction) { - echo "\nThe new node at $fullNameOfNewChild is "; - echo "
styleSheet['Node']."\">\n"; - print_r($newChildNode); - echo "
\n"; - } - - /////////////////////////////////////// - // Fix the parent. - - // Get the parent node. - $newParentNode = &$this->nodes[$absoluteParentPath]; - // There a bug some where that will trigger the following check. --sam 12.12.2001 - if (!isSet($newParentNode['children'])) { - $this->_displayError("Internal node problem. When adding node '{$nodeName}' to an invalid parent node '{$absoluteParentPath}'." , __LINE__, FALSE); - } - - // Count the siblings - $siblingCount = count($newParentNode['children']); - // Add children in the *order* they come in. - $newParentNode['children'][$siblingCount] = $nodeName.'['.$position.']'; - // Update the number of childer in parent path - $newParentNode['childCount'][$nodeName] = $position; - // Add some default sensible whitespace before this new node. - - // Work out who the grandparent is. - if ($newParentNode['parent']) { - $newGrandParentNode = &$this->nodes[$newParentNode['parent']]; - $beforeText = $newGrandParentNode['text'][$newParentNode['context-pos']-1]; - $afterText = $newGrandParentNode['text'][$newParentNode['context-pos']]; - if ($bDebugThisFunction) { - echo "Text before this child is '$beforeText'\n"; - echo "Text after this child is '$afterText'\n"; - } - // If there is no text before or after, then default to something sensible - if ( $beforeText == '' && $afterText == '' ) { - $newParentNode['text'][(int)$siblingCount] = "\n\t"; - $newParentNode['text'][$siblingCount+1] = "\n"; - // If the text before and after is purely whitespace, derive from it. - } else if (!preg_match('/[^\s]/',$beforeText) && !preg_match('/[^\s]/',$afterText)) { // --sam regex shoud be /^\s*$/ I guess - $newParentNode['text'][(int)$siblingCount] = "$beforeText\t"; - $newParentNode['text'][$siblingCount+1] = "$afterText"; - // Otherwise default to something sensible. - } else { - $newParentNode['text'][(int)$siblingCount] = "\n\t"; - $newParentNode['text'][$siblingCount+1] = "\n"; - } - // We are adding to the root, so we know what these should be. - } else { - $newParentNode['text'][(int)$siblingCount] = "\n\t"; - $newParentNode['text'][$siblingCount+1] = "\n"; - } - - if ($bDebugThisFunction) { - echo "\nThe fixed parent at $absoluteParentPath is "; - echo "
styleSheet['ParentNode']."\">\n"; - print_r($this->nodes[$absoluteParentPath]); - echo "
\n"; - } - } - - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $fullNameOfNewChild); - } - - // Return the path of the new node. - return $fullNameOfNewChild; - } - - - /** - * Returns TRUE if the given node has child nodes below it - * - * @author Dietrich Ayala - * @param string $absoluteXPath full path of the potentail parent node - * *READONLY* - * @return bool TRUE if this node exists and has a child, FALSE otherwise - * @see removeChild(), appendChild(), evaluate() - */ - function hasChildNodes($absoluteXPath) { - if (!isSet($this->nodes[$absoluteXPath])) return FALSE; - if (count($this->nodes[$absoluteXPath]['children']) >= 1) return TRUE; - return FALSE; - } - - /** - * Resets the object so it's able to take a new xml sting/file - * - * @author Sam Blume bs_php@infeer.com - * - */ - function reset() { - $this->nodes = array(); - $this->path = ''; - $this->xpath = ''; - $this->root = ''; - $this->position = 0; - $this->xmlTxtBuffer = ''; - $this->fileName = ''; //added by fab - $this->inCData = 0; - $this->_lastError = ''; - } - - //////////////////////////////////////////////////////////////////////////////// - //################### Private Members ########################################// - //////////////////////////////////////////////////////////////////////////////// - - // 0=silent, 1 and above produce verbose output (an echo to screen). - // @see getLastError() - var $_verboseLevel = 1; - - // xml_parser_set_option -- set options in an XML parser. - var $xmlOptions = array(); - - // The filename, if XML was imported by a file (instead of a string) - var $fileName = ''; - - // List of all document nodes. - // - // This array contains a list of all document nodes saved as an - // associative array. - var $nodes = array(); - - // Current document path. - // - // This variable saves the current path while parsing a XML file and adding - // the nodes being read from the file. - var $path = ''; - - // Current document position. - // - // This variable counts the current document position while parsing a XML - // file and adding the nodes being read from the file. - var $position = 0; - - // Path of the document root. - // - // This string contains the full path to the node that acts as the root - // node of the whole document. - var $root = ''; - - // Current XPath expression. - // - // This string contains the full XPath expression being parsed currently. - var $xpath = ''; - - // Used as tmp storage for the char data collected during xml parsing - var $xmlTxtBuffer = ''; - - // When parsing the xml file, sets to true when we are inside a CDATA section. - var $inCData = 0; - - // As debugging of the xml parse is spread across several functions, we need - // to make this a member. - var $bDebugXmlParse = FALSE; - - // List of entities to be converted. - // - // This array contains a list of entities to be converted when an XPath - // expression is evaluated. - // - // ### People seem to think that &apos is a bad idea for charset ISO-8859-1 - //var $entities = array ( "&" => "&", "<" => "<", ">" => ">", - // "'" => "'", '"' => """ ); - var $entities = array('&'=>'&', '<'=>'<', '>'=>'>', '"'=>'"'); - - // List of supported XPath axes. - // What a stupid idea from W3C to take axes name containing a '-' (dash) - // NOTE: Instead of the '-' in the names we use '_'. - // We will then do the same on the users Xpath querys - // -sibling => _sibling - // -or- => _or_ - // - // This array contains a list of all valid axes that can be evaluated in an - // XPath expression. - var $axes = array ( 'child', 'descendant', 'parent', 'ancestor', - 'following_sibling', 'preceding_sibling', 'following', 'preceding', - 'attribute', 'text', 'namespace', 'self', 'descendant_or_self', - 'ancestor_or_self' ); - - // List of supported XPath functions. - // What a stupid idea from W3C to take function name containing a '-' (dash) - // NOTE: Instead of the '-' in the names we use '_'. - // We will then do the same on the users Xpath querys - // starts-with => starts_with - // substring-before => substring_before - // substring-after => substring_after - // string-length => string_length - // - // This array contains a list of all valid functions that can be evaluated - // in an XPath expression. - var $functions = array ( 'last', 'position', 'count', 'id', 'name', - 'string', 'concat', 'starts_with', 'contains', 'substring_before', - 'substring_after', 'substring', 'string_length', 'normalize_space', 'translate', - 'boolean', 'not', 'true', 'false', 'lang', 'number', 'sum', 'floor', - 'ceiling', 'round' ); - - // What a stupid idea from W3C to take axes name containing a '-' (dash) - // It's hard to distinguish from a minus operator. - // NOTE: Instead of the '-' in the names we use '_'. - // We will then do the same on the users Xpath querys - var $dash2underscoreHash = array( - '-sibling' => '_sibling', - '-or-' => '_or_', - 'starts-with' => 'starts_with', - 'substring-before' => 'substring_before', - 'substring-after' => 'substring_after', - 'string-length' => 'string_length', - 'normalize-space' => 'normalize_space'); - - // List of supported XPath operators. - // - // This array contains a list of all valid operators that can be evaluated - // in a predicate of an XPath expression. The list is ordered by the - // precedence of the operators (lowest precedence first). - var $operators = array( ' or ', ' and ', '=', '!=', '<=', '<', '>=', '>', - '+', '-', '*', ' div ', ' mod ' ); - - // This is the array of error strings, to keep consistency. - var $errorStrings = array( - 'AbsoluteXPathRequired' => 'The supplied string does not uniquely describe a node in the xml document: %s' - ); - - // Style sheet of strings used to make output prettier. - var $styleSheet = array( - 'Node' => 'background-color: #FFFFEE;', - 'ParentNode' => 'background-color: #FFEEFF;', - ); - - //////////////////////////////////////////////////////////////////////////////// - //################### Private Interface ######################################// - //////////////////////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////// - // ########################################### // - // Export functions - - /** - * Generates a XML file with the content of the current document. - * - * This method creates a string containing the XML data being read - * and modified by this class before. This string can be used to save - * a modified document back to a file or doing other nice things with - * it. - * - * @author Michael P. Mehl - * @param array $highlight Array containing a list of full document - * paths of nodes to be highlighted by ... tags - * in the generated XML string. - * @param string $currentXpath While doing a recursion with this method, this - * parameter is used for internal purpose. - * @param int $level While doing a recursion with this method, this - * parameter is used for internal purpose. - * @param $outputAsXml specifies whether or not you want to escape - * <> as > and < Added by N.S. - * @return string The returned string contains well-formed XML data - * representing the content of this document. - */ - function &_export($highlight = array(), $currentXpath = '', $level = 0, $outputAsXml = 0) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("_export"); - echo "Highlights:\n"; - print_r($highlight); - echo "Root: $currentXpath\n"; - echo "Level: $level\n"; - echo "Output As Xml: $outputAsXml\n\n"; - } - ////////////////////////////////////////////// - // Create a string to save the generated XML data. - $xml = ''; - - // Create two strings containing the tags for highlighting a node. - static $highlight_start = ''; - static $highlight_end = ''; - - // Check whether a root node is given. - if (empty($currentXpath)) { - // Set it to the document root. - $currentXpath = $this->root; - if ($bDebugThisFunction) echo "Changing root to $this->root as it is empty\n"; - } - - // Generate a string to be displayed before the tags. - $before = ''; - // Calculate the amount of whitespaces to display. - for ( $i=0; $i<$level; $i++) { - //Add a whitespaces to the string. - $before .= ' '; - } - - // If there is no node at $currentXpath then we have nothing to export. Quit now - if (!isSet($this->nodes[$currentXpath])) { - if ($bDebugThisFunction) echo "No node at $currentXpath, returning null\n"; - // Not completely sure yet if this is fatal, but I think it is. - $this->_displayError("When exporting the class, the node at $currentXpath ". - "was not found. This is probably due to previous internal corruption."); - return ''; - } - - $theNode = &$this->nodes[$currentXpath]; - - - // Check whether the node is selected. - $selected = empty($highlight) ? FALSE : in_array($currentXpath, $highlight); - $hasChildren = (sizeOf($theNode['children'])>0) ? TRUE : FALSE; - - // Check whether the node is selected for highlight. - if ($selected ) $xml .= $highlight_start; - - // Now open the tag adding the whitespaces to the XML data. - if (!$outputAsXml && $level>0) $xml .= "\n".$before; - - if (empty($theNode['name'])) { - // If the node has no name, then ring alarm bells. - $this->_displayError("When exporting the class, the node at '{$currentXpath}' ". - "was found to have no node name.", __LINE__, FALSE); - return ''; - } - - $xml .= ($outputAsXml) ? '<' : '<'; - $xml .= htmlspecialchars($theNode['name']); - - // Check whether there are attributes for this node. - if (count($theNode['attributes']) > 0) { - if ($bDebugThisFunction) echo "Outputing the attributes\n"; - // Run through all attributes. - $highlighting = FALSE; - reset($theNode['attributes']); - while (list($key) = each($theNode['attributes'])) { - // Check whether this attribute is highlighted. - if (is_array($highlight) and in_array($currentXpath.'/attribute::'.$key, $highlight)) { - // Add the highlight code to the XML data. - $xml .= $highlight_start; - $highlighting = TRUE; - } - - // Add the attribute to the XML data. - $xml .= ' '.$key.'="'.htmlspecialchars($theNode['attributes'][$key]).'"'; - - // Check whether this attribute is highlighted. - if ($highlighting) { - // Add the highlight code to the XML data. - $xml .= $highlight_end; - $highlighting = FALSE; - } - } - } - - if (!isset($theNode['text'])) { - $mergedText = ''; - $useShortEnd = !$hasChildren; - } else { - $mergedText = implode('', $theNode['text']); - $useShortEnd = (!$hasChildren && ($mergedText == '')); - } - - // Check whether the node contains character data or has children. - if ($useShortEnd) { - // Add the end to the tag. - $xml .= ($outputAsXml) ? '/>' : "/>"; - } else { - // Close the tag. - $xml .= ($outputAsXml) ? '>' : '>'; - } - - // Check whether the node is selected. Add the highlight code to the XML data. - if ($selected) $xml .= $highlight_end; - - // Check whether the node has children or not. - if (!$hasChildren) { - $xml .= $mergedText; - } else { - // Run through all children in the order they where set. - $childSize = sizeOf($theNode['children']); - if ($bDebugThisFunction) { - echo "$childSize children to output.\n"; - print_r($theNode['children']); - } - for ($i=0; $i<$childSize; $i++) { - if (!empty($theNode['text'][$i])) $xml .= $theNode['text'][$i]; - // Generate the full path of the child. - $fullchild = $currentXpath.'/'.$theNode['children'][$i]; - if ($bDebugThisFunction) echo "Outputing child $i: $fullchild\n"; - // Add the child's XML data to the existing data. - $xml .= $this->_export(&$highlight, $fullchild, $level + 1, $outputAsXml); - } - // Add the text fagment after the child node - if (!empty($theNode['text'][$i])) { - if ($outputAsXml) $xml .= $theNode['text'][$i]; - else $xml .= htmlspecialchars($theNode['text'][$i]); - } - } - - // Check if we have to set a ending tag - if (! $useShortEnd) { - // Add the whitespaces to the XML data, but only if there were kids. - if (!$outputAsXml && $hasChildren) $xml .= "\n".$before; - - // Check whether the node is selected. Add the highlight code to the XML data. - if ($selected) $xml .= $highlight_start; - - // Add the closing tag. - $xml .= ($outputAsXml) ? '' : '>'; - - // Check whether the node is selected. Add the highlight code to the XML data. - if ($selected) $xml .= $highlight_end; - - // Add a linebreak. - if (!$outputAsXml) $xml .= "\n"; - } - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $xml); - } - - // Return the XML data. - return $xml; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Xml parsing utitilties - - /** - * Handles opening XML tags while parsing. - * - * While parsing a XML document for each opening tag this method is - * called. It'll add the tag found to the tree of document nodes. - * - * @author Michael P. Mehl - * @param int $parser Handler for accessing the current XML parser. - * @param string $name Name of the opening tag found in the document. - * @param array $attributes Associative array containing a list of - * all attributes of the tag found in the document. - * @see _handleEndElement(), _handleCharacterData() - */ - function _handleStartElement($parser, $nodeName, $attributes) { - if (empty($nodeName)) { - $this->_displayError('XML error in file at line'. xml_get_current_line_number($parser) .'. Empty name.', __LINE__); - return; - } - if ($this->bDebugXmlParse) - echo "
Start node: ".$nodeName . "
"; - - $oldPath = $this->path; - - // Add a node. - $this->path = $this->appendChild($this->path, $nodeName); - - // Add text fragments to the old node, resetting the defaults applied by appendChild() - if (!empty($this->path)) { - // When we read a start element, we replace the before text for this node with - // what we actually read. Hence the -2. - if (isset($this->nodes[$oldPath]['text'])) - $iIndex = count($this->nodes[$oldPath]['text'])-2; - else - $iIndex = 0; - $this->nodes[$oldPath]['text'][$iIndex?$iIndex:0] = $this->xmlTxtBuffer; - $this->xmlTxtBuffer = ''; - } - - // Set the attributes. - if (!empty($attributes)) - $this->nodes[$this->path]['attributes'] = $attributes; - } - - /** - * Handles closing XML tags while parsing. - * - * While parsing a XML document for each closing tag this method is called. - * - * @author Michael P. Mehl - * @param int $parser Handler for accessing the current XML parser. - * @param string $name Name of the closing tag found in the document. - * @see _handleStartElement(), _handleCharacterData() - */ - function _handleEndElement($parser, $name) { - // Add text fragments. If the node has kids, then there will be at least two - // text entries, before and after. So we replace the last entry with the txtBuffer. - // If the node had no kids, then we need to set the 1 and only text node. - $iIndex = count($this->nodes[$this->path]['text']); - $this->nodes[$this->path]['text'][$iIndex?$iIndex-1:0] = $this->xmlTxtBuffer; - if ($this->bDebugXmlParse) { - echo "End node:$name: [{$this->path}]: '".htmlspecialchars($this->xmlTxtBuffer)."'
Text nodes:
\n";
-      print_r($this->nodes[$this->path]['text']);
-      echo "
\n"; - } - $this->xmlTxtBuffer = ''; - // Jump back to the parent element. - $this->path = substr($this->path, 0, strrpos($this->path, '/')); - } - - /** - * Handles character data while parsing. - * - * While parsing a XML document for each character data this method - * is called. It'll add the character data to the document tree. - * - * @author Michael P. Mehl - * @param int $parser Handler for accessing the current XML parser. - * @param string $text Character data found in the document. - * @see _handleStartElement(), _handleEndElement() - */ - function _handleCharacterData($parser, $text) { - // Replace entities. - if (!$this->inCData) - $text = strtr($text, $this->entities); - - $this->xmlTxtBuffer .= $text; - if ($this->bDebugXmlParse) - echo "Handling character data: ".htmlspecialchars($text)."
"; - } - - /** - * Default handler for the XML parser. - * - * While parsing a XML document for string not caught by one of the other - * handler functions, we end up here. - * - * @param int $parser Handler for accessing the current XML parser. - * @param string $text Character data found in the document. - * @see _handleStartElement(), _handleEndElement() - */ - function _handleDefaultData($parser, $text) { - // Replace entities. - do { // try-block - if ($this->path) { - $this->xmlTxtBuffer .= $text; - if (!strcmp($text, 'inCData++; - } else if (!strcmp($text, ']]>')) { - $this->inCData--; - if ($this->inCData < 0) $this->inCData = 0; - } - if ($this->bDebugXmlParse) echo "Default handler data: ".htmlspecialchars($text)."
"; - break; // try-block - } - - // handle the dtd and xml declarations - if ( preg_match(";\?>$;", $this->nodes['']['xml-declaration']) ) { - $this->nodes['']['dtd-declaration'] .= $text; - break; // try-block - } - - $this->nodes['']['xml-declaration'] .= $text; - - } while (FALSE); // END try-block - } - - ///////////////////////////////////////////////// - // ########################################### // - // XPath expression parsing functions - - /** - * Split a string by a searator-string -- BUT the searator-string must be located *outside* of any brackets. - * - * Returns an array of strings, each of which is a substring of string formed - * by splitting it on boundaries formed by the string separator. - * - * @param string $separator String that should be searched. - * @param string $term String in which the search shall take place. - * @return array (see above) - */ - function &_bracketExplode($separator, &$term) { - // Note that it doesn't make sense for $separator to itself contain (,),[ or ], - // but as this is a private function we should be ok. - $resultArr = array(); - $bracketCounter = 0; // Record where we are in the brackets. If we are inside - // a () or [] bracket, then we won't be able to reliably - // extract strings. - do { // BEGIN try block - // Check if any separator is in the term - $sepLeng = strlen($separator); - if (strpos($term, $separator)===FALSE) { // no separator found so end now - $resultArr[] = $term; - break; // try-block - } - - // Make a substitute separator out of 'unused chars'. - $substituteSep = str_repeat(chr(2), $sepLeng); - - // Now determin the first bracket '(' or '['. - $tmp1 = strpos($term, '('); - $tmp2 = strpos($term, '['); - if ($tmp1===FALSE) { - $startAt = (int)$tmp2; - } elseif ($tmp2===FALSE) { - $startAt = (int)$tmp1; - } else { - $startAt = min($tmp1, $tmp2); - } - - // Get prefix string part befor the first bracket. - $preStr = substr($term, 0, $startAt); - // Substitute separator in prefix string. - $preStr = str_replace($separator, $substituteSep, $preStr); - - // Now get the rest-string (postfix string) - $postStr = substr($term, $startAt); - // Go all the way through the rest-string. - $strLeng = strlen($postStr); - for ($i=0; $i < $strLeng; $i++) { - $char = $postStr[$i]; - // Spot (,),[,] and modify our bracket counter. Note there is an - // assumption here that you don't have a string(with[mis)matched]brackets. - // This should be ok as the dodgy string will be detected elsewhere. - if ($char=='(' || $char=='[') { - $bracketCounter++; - continue; - } - elseif ($char==')' || $char==']') { - $bracketCounter--; - } - // If no brackets surround us check for separator - if ($bracketCounter == 0) { - // Check whether we can find the expression starting at this index. - if ((substr($postStr, $i, $sepLeng) == $separator)) { - // Substitute the found separator - for ($j=0; $j<$sepLeng; $j++) { - $postStr[$i+$j] = $substituteSep[$j]; - } - } - } - } - // Now explod using the substitute separator as key. - $resultArr = explode($substituteSep, $preStr . $postStr); - } while (FALSE); // End try block - // Return the results that we found. May be a array with 1 entry. - return $resultArr; - } - - - /** - * Retrieves axis information from an XPath expression step. - * - * This method tries to extract the name of the axis and its node-test - * from a given step of an XPath expression at a given node. - * - * @author Michael P. Mehl - * @param string $step String containing a step of an XPath expression. - * @param string $nodePath Full document path of the node on which the - * step is executed. - * @return array This method returns an array containing information - * about the axis found in the step. - * @see _evaluateStep() - */ - function _getAxis($step, $nodePath) { - // Create an array to save the axis information. - $axis = array( - 'axis' => '', - 'node-test' => '', - 'predicate' => array() - ); - - do { // parse block - $parseBlock = 1; - - // Check whether the step is empty or only self. - if ( empty($step) OR ($step == '.') OR ($step == 'current()') ) { - // Set it to the default value. - $step = '.'; - $axis['axis'] = 'self'; - $axis['node-test'] = '*'; - break $parseBlock; - } - - // Check whether is an abbreviated syntax. - if ($step == '*') { - // Use the child axis and select all children. - $axis['axis'] = 'child'; - $axis['node-test'] = '*'; - break $parseBlock; - } - - // Check whether it's all wrapped in a function. will be like count(.*) where .* is anything - // text() will try to be matched here, so just explicitly ignore it - $regex = ":(.*)\s*\((.*)\)$:U"; - if (preg_match($regex, $step, $match) && $step != "text()") { - $function = $match[1]; - $data = $match[2]; - if ($this->_isFunction($function)) { - // Save the evaluated function. - $axis['axis'] = 'function'; - $axis['node-test'] = $this->_evaluateFunction($function, $data, $nodePath, $nodeTest); - } - else { - // Use the child axis and a function. - $axis['axis'] = 'child'; - $axis['node-test'] = $step; - } - break $parseBlock; - } - - // Check whether there are predicates and add the predicate - // to the list of predicates without []. Get contents of - // every [] found. - $regex = '/\[(.*)\]/'; - preg_match_all($regex, $step, $regs); - if (!empty($regs[1])) { - $axis['predicate'] = $regs[1]; - // Reduce the step. - $step = preg_replace($regex,"",$step); //$this->_prestr($step, '['); - } - - // Check whether the axis is given in plain text. - if ($this->_searchString($step, '::') > -1) { - // Split the step to extract axis and node-test. - $axis['axis'] = $this->_prestr($step, '::'); - $axis['node-test'] = $this->_afterstr($step, '::'); - break $parseBlock; - } - - if ($step[0] == '@') { - // Use the attribute axis and select the attribute. - $axis['axis'] = 'attribute'; - $axis['node-test'] = substr($step, 1); - break $parseBlock; - } - - if (eregi('\]$', $step)) { - // Use the child axis and select a position. - $axis['axis'] = 'child'; - $axis['node-test'] = substr($step, strpos($step, '[')); - break $parseBlock; - } - - if ($step == '..') { - // Select the parent axis. - $axis['axis'] = 'parent'; - $axis['node-test'] = '*'; - break $parseBlock; - } - - if (preg_match('/^[a-zA-Z0-9\-_]+$/', $step)) { - // Select the child axis and the child. - $axis['axis'] = 'child'; - $axis['node-test'] = $step; - break $parseBlock; - } - - if ( $step == "text()" ) { - // Handle the text node - $axis["axis"] = "child"; - $axis["node-test"] = "cdata"; - break $parseBlock; - } - - // Default will be to fall back to using the child axis and a name. - $axis['axis'] = 'child'; - $axis['node-test'] = $step; - - } while(FALSE); // end parse block - - // Check whether it's a valid axis. - if (!in_array($axis['axis'], array_merge($this->axes, array('function')))) { - // Display an error message. - $this->_displayError('While parsing an XPath expression, in the step ' . - str_replace($step, ''.$step.'', $this->xpath) . - ' the invalid axis ' . $axis['axis'] . ' was found.', __LINE__); - } - - // Return the axis information. - return $axis; - } - - /** - * Checks for a valid function name. - * - * This method check whether an expression contains a valid name of an - * XPath function. - * - * @author Michael P. Mehl - * @param string $expression Name of the function to be checked. - * @return boolean This method returns TRUE if the given name is a valid - * XPath function name, otherwise FALSE. - * @see evaluate() - */ - function _isFunction(&$expression) { - // Check whether it's in the list of supported functions. - if (in_array($expression, $this->functions)) { - // It's a function. - return TRUE; - } else { - // It's not a function. - return FALSE; - } - } - - ///////////////////////////////////////////////// - // ########################################### // - // Evaluation functions - - /** - * Internal recursive evaluate an-XPath-expression function. - * - * $this->evaluate() is the entry point and does some inits, while this - * function is called recursive internaly for every sub-xPath expresion we find. - * - * @param string $xPathQuery XPath expression to be evaluated. - * @param string or array $context Full path of a document node, starting - * from which the XPath expression should be evaluated. - * @see evaluate() - */ - function _internalEvaluate($xPathQuery, $context='') { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("evaluate"); - echo "Path: $xPathQuery\n"; - echo "Context: $context\n"; - } - - // Numpty check - if (empty($xPathQuery)) { - $this->_displayError("The $xPathQuery argument must have a value.", __LINE__); - return FALSE; - } - - // Split the paths that are sparated by '|' into distinct xPath expresions. - $xPaths = &$this->_bracketExplode('|', $xPathQuery); - if ($bDebugThisFunction) { echo "
Split the paths that are sparated by '|'\n"; print_r($xPaths); } - - // Create an empty set to save the result. - $result = array(); - - // Run through all paths. - reset($xPaths); - while (list(,$xPath) = each($xPaths)) { - // mini syntax check - if (!$this->_bracketsCheck($xPath)) { - $this->_displayError('While parsing an XPath expression, in the predicate ' . - str_replace($xPath, ''.$xPath.'', $xPathQuery) . - ', there was an invalid number of brackets or a bracket mismatch.', __LINE__); - } - // Save the current path. - $this->xpath = $xPath; - // Split the path at every slash *outside* a bracket. - $steps = &$this->_bracketExplode('/', $xPath); - if ($bDebugThisFunction) { echo "
Split the path '$xPath' at every slash *outside* a bracket.\n "; print_r($steps); } - // Check whether the first element is empty. - if (empty($steps[0])) { - // Remove the first and empty element. It's a starting '//'. - array_shift($steps); - } - // Start to evaluate the steps. - $nodes = $this->_evaluateStep($context, $steps); - // Remove duplicated nodes. - $nodes = array_unique($nodes); - // Add the nodes to the result set. - $result = array_merge($result, $nodes); - } - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - - // Return the result. - return $result; - } - - /** - * Evaluates a step of an XPath expression. - * - * This method tries to evaluate a step from an XPath expression at a - * specific context. - * - * @author Michael P. Mehl - * @param string or array $context Full document path of the context from - * which starting the step should be evaluated. Either a single - * context, or an array of contexts. - * @param array $steps Array containing the remaining steps of the - * current XPath expression. - * @return array This method returns an array containing all nodes - * that are the result of evaluating the given XPath step. - * @see evaluate() - */ - function _evaluateStep($context, $steps) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction = FALSE; - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("_evaluateStep(Context:[$context], steps:[$steps])"); - if (is_array($context)) { - echo "Context:\n"; - print_r($context); - } else { - echo "Context: $context\n"; - } - echo "Steps: "; - print_r($steps); - echo "
\n"; - } - ////////////////////////////////////////////// - // Create an empty array for saving the nodes found. - $nodes = array(); - // We may have an "array" of one context. If so convert it from - // array to single string. Often, this function will be called with - // a /Path1[1]/Path[3]/Path[2] sytle predicate. - if (is_array($context) && (count($context) == 1)) $context = $context[0]; - // Check whether the context is an array of contexts. - if (is_array($context)) { - // Run through the array. - $size = sizeOf($context); - for ($i=0; $i<$size; $i++) { - if ($bDebugThisFunction) echo "Evaluating step for the {$context[$i]} context...\n"; - // Call this method for this single path. - $nodes = array_merge($nodes, $this->_evaluateStep($context[$i], $steps)); - } - } else { - // Get this step. - $step = trim(array_shift($steps)); - - if ($bDebugThisFunction) echo "Evaluating step $step\n"; - // Create an array to save the new contexts. - $contexts = array(); - - // Get the axis of the current step. - $axis = $this->_getAxis($step, $context); - if ($bDebugThisFunction) { - echo __LINE__.":Axis of step is:\n"; - print_r($axis); - echo "\n"; - } - // Check whether it's a function. - if ($axis['axis'] == 'function') { - // Check whether an array was return by the function. - if (is_array($axis['node-test'])) { - // Add the results to the list of contexts. - $contexts = array_merge($contexts, $axis['node-test']); - } else { - // Add the result to the list of contexts. - $contexts[] = $axis['node-test']; - } - } else { - // Create the name of the method. - $method = '_handleAxis_' . $axis['axis']; - - // Check whether the axis handler is defined. - if (!method_exists(&$this, $method)) { - // Display an error message. - $this->_displayError('While parsing an XPath expression, the axis ' . - $axis['axis'] . ' could not be handled, because this version does not support this axis.', __LINE__); - } - if ($bDebugThisFunction) echo "Calling user method $method\n"; - - // Perform an axis action. - $contexts = $this->$method($axis, $context); - if ($bDebugThisFunction) { - echo "We found these contexts from this step:\n"; - print_r( $contexts ); - echo "\n"; - } - - // Check whether there are predicates. - if (count($axis['predicate']) > 0) { - if ($bDebugThisFunction) echo "Filtering contexts by predicate...\n"; - - // Check whether each node fits the predicates. - $contexts = $this->_checkPredicates($contexts, $axis['predicate'], $axis['node-test']); - } - } - - // Check whether there are more steps left. - if (count($steps) > 0) { - if ($bDebugThisFunction) echo "Evaluating next step given the context of the first step...\n"; - // Continue the evaluation of the next steps. - $nodes = $this->_evaluateStep($contexts, $steps); - } else { - // Save the found contexts. - $nodes = $contexts; - } - } - - ////////////////////////////////////////////// - // Return the nodes found. - $result = $nodes; - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - - // Return the result. - return $result; - } - - /** - * Evaluates an XPath function - * - * This method evaluates a given XPath function with its arguments on a - * specific node of the document. - * - * @author Michael P. Mehl - * @param string $function Name of the function to be evaluated. - * @param string $arguments String containing the arguments being - * passed to the function. - * @param string $node Full path to the document node on which the - * function should be evaluated. - * @return mixed This method returns the result of the evaluation of - * the function. Depending on the function the type of the - * return value can be different. - * @see evaluate() - */ - function _evaluateFunction($function, $arguments, $node, $nodeTest) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("_evaluateFunction(Function:[$function], Arguments:[$arguments], node:[$node], nodeTest:[$nodeTest])"); - if (is_array($arguments)) { - echo "Arguments:\n"; - print_r($arguments); - } else { - echo "Arguments: $arguments\n"; - } - echo "
\n"; - } - - ///////////////////////////////////// - // Remove whitespaces. - $function = trim($function); - $arguments = trim($arguments); - // Create the name of the function handling function. - $method = '_handleFunction_'. $function; - - // Check whether the function handling function is available. - if (!method_exists(&$this, $method)) { - // Display an error message. - $this->_displayError("While parsing an XPath expression, ". - "the function \"$function\" could not be handled, because this ". - "version does not support this function.", __LINE__); - } - - if ($bDebugThisFunction) echo "Calling function $method($node, $arguments)\n"; - - // Return the result of the function. - $result = $this->$method($node, $arguments, $nodeTest); - - ////////////////////////////////////////////// - // Return the nodes found. - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - - // Return the result. - return $result; - } - - /** - * Evaluates a predicate on a node. - * - * This method tries to evaluate a predicate on a given node. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the predicate - * should be evaluated. - * @param string $predicate String containing the predicate expression - * to be evaluated. - * @return mixed This method is called recursively. The first call should - * return a boolean value, whether the node matches the predicate - * or not. Any call to the method being made during the recursion - * may also return other types for further processing. - * @see evaluate() - */ - function _evaluatePredicate($node, $predicate, $nodeTest) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction= FALSE; - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("_evaluatePredicate"); - echo "Node: [$node]\n"; - echo "Predicate: [$predicate]\n"; - echo "
"; - } - - // Numpty check - if (!is_string($predicate)) { - // Display an error message. - $this->_displayError("While parsing an XPath expression ". - "there was an error in the following predicate, ". - "because it was not a string. It was a '".$predicate."'", __LINE__); - return FALSE; - } - $predicate = trim($predicate); - // Numpty check. If they give us an empty string, then this is an error. ## N.S - if ($predicate === '') { - // Display an error message. - $this->_displayError("While parsing an XPath expression ". - "there was an error in the predicate " . - "because it was the null string. If you wish to seach ". - "for the empty string, you must use ''.", __LINE__); - return FALSE; - } - ///////////////////////////////////////////// - // Quick ways out. - // If it is a literal string, then we return the literal string. ## N.S. --sb - $stringDelimiterMismatsh = 0; - if (preg_match(':^"(.*)"$:', $predicate, $regs)) { - $result = $regs[1]; - $stringDelimiterMismatsh = strpos(' ' . $result, '"'); - if ($bDebugThisFunction) echo "Predicate is literal\n"; - } elseif (preg_match(":^'(.*)'$:", $predicate, $regs)) { - $result = $regs[1]; - $stringDelimiterMismatsh = strpos(' ' . $result, "'"); - if ($bDebugThisFunction) echo "Predicate is literal\n"; - } - - if ($stringDelimiterMismatsh>0) { - $this->_displayError("While parsing an XPath expression ". - "there was an string delimiter miss match at pos [{$stringDelimiterMismatsh}] in the predicate string '{$predicate}'.", __LINE__); - return FALSE; - } - - // Check whether the predicate is just a digit. - if (!isSet($result)) { - if (is_numeric($predicate)) { - // Return the value of the digit. - $result = doubleval($predicate); - if ($bDebugThisFunction) echo "Predicate is double\n"; - } - } - ///////////////////////////////////////////// - // Check for operators. - // Set the default position and the type of the operator. - $position = 0; - $operator = ''; - - // Run through all operators and try to find one. - if (!isSet($result)) { - for ($i=0; $ioperators); $i++) { - if ($position >0) break; - $operator = $this->operators[$i]; - // Quickcheck. If not present don't wast time searching 'the hard way' - if (strpos($predicate, $operator)===FALSE) continue; - // Special check - $position = $this->_searchString($predicate, $operator); - // Check whether a operator was found. - if ($position <= 0 ) continue; - // Check whether it's the equal operator. - if ($operator == '=') { - // Also look for other operators containing the equal sign. - switch ($predicate[$position-1]) { - case '<' : - $position--; - $operator = '<='; - break; - case '>' : - $position--; - $operator = '>='; - break; - case '!' : - $position--; - $operator = '!='; - break; - default: - } - } - if ($operator == '*') { - // Get some substrings. - $character = substr($predicate, $position - 1, 1); - $attribute = substr($predicate, $position - 11, 11); - - // Check whether it's an attribute selection. - if (( $character == '@' ) || ( $attribute == 'attribute::' )) { - // Don't use the operator. - $operator = ''; - $position = -1; - } - } - } // end while each($this->operators) - - // Check whether an operator was found. - if ($position > 0) { - if ($bDebugThisFunction) echo "\nPredicate operator is a [$operator] at pos '$position'"; - // Get the left and the right part of the expression. - $left_predicate = trim(substr($predicate, 0, $position)); - $right_predicate = trim(substr($predicate, $position + strlen($operator))); - if ($bDebugThisFunction) echo "\nLEFT:[$left_predicate] oper:[$operator] RIGHT:[$right_predicate]"; - - // Remove whitespaces. - $left_predicate = trim($left_predicate); - $right_predicate = trim($right_predicate); - // Evaluate the left and the right part. - if ($bDebugThisFunction) echo "\nEvaluating LEFT:[$left_predicate]"; - $left = $this->_evaluatePredicate($node, $left_predicate, $nodeTest); - if ($bDebugThisFunction) echo "$left_predicate evals as: $left - "; - // Only evaluate the right part if we need to. - $right = FALSE; - if (!$left and ($operator == ' and ')) { - if ($bDebugThisFunction) echo "\nNo point in evaluating the right predicate: [$right_predicate]"; - } else { - if ($bDebugThisFunction) echo "\nEvaluating RIGHT:[$right_predicate]"; - $right = $this->_evaluatePredicate($node, $right_predicate, $nodeTest); - if ($bDebugThisFunction) echo "$right_predicate evals as: $right \n"; - } - // Check the kind of operator. - $b_result = FALSE; - switch ( $operator) { - case ' or ': - // Return the two results connected by an 'or'. - $b_result = (bool)( $left or $right ); - break; - case ' and ': - // Return the two results connected by an 'and'. - $b_result = (bool)( $left and $right ); - break; - case '=': - // Compare the two results. - $b_result = (bool)( $left == $right ); - break; - case '!=': - // Check whether the two results are not equal. - $b_result = (bool)( $left != $right ); - break; - case '<=': - // Compare the two results. - $b_result = (bool)( $left <= $right ); - break; - case '<': - // Compare the two results. - $b_result = (bool)( $left < $right ); - break; - case '>=': - // Compare the two results. - $b_result = (bool)( $left >= $right ); - break; - case '>': - // Compare the two results. - $b_result = (bool)( $left > $right ); - break; - case '+': - // Return the result by adding one result to the other. - $b_result = $left + $right; - break; - case '-': - // Return the result by decrease one result by the other. - $b_result = $left - $right; - break; - case '*': - // Return a multiplication of the two results. - $b_result = $left * $right; - break; - case ' div ': - // Return a division of the two results. - if ($right == 0) { - // Display an error message. - $this->_displayError('While parsing an XPath '. - 'predicate, a error due a division by zero '. - 'occured.', __LINE__); - } else { - // Return the result of the division. - $b_result = $left / $right; - } - break; - case ' mod ': - // Return a modulo of the two results. - $b_result = $left % $right; - break; - } - $result = $b_result; - } - } - - ///////////////////////////////////////////// - // Check for functions. - // Check whether the predicate is a function. - if (!isSet($result)) { - // do not catch the text() node, which looks like a function in its pattern - if (preg_match(':\(:U', $predicate) && !preg_match(":text\(\)(\[\d*\])?$:",$predicate) ) { - // Get the position of the first bracket. - $start = strpos($predicate, '('); - // If we search for the right bracket from the end of the string, we can - // support nested function calls. Fix by Andrei Zmievski - $end = strrpos($predicate, ')'); - - // Get everything before, between and after the brackets. - $before = substr($predicate, 0, $start); - $between = substr($predicate, $start + 1, $end - $start - 1); - $after = substr($predicate, $end + 1); - - // Trim each string. - $before = trim($before); - $between = trim($between); - $after = trim($after); - - if ($bDebugThisFunction) echo "\nPredicate is function \"$before\""; - // Check whether there's something after the bracket. - if (!empty($after)) { - // Display an error message. - $this->_displayError('While parsing an XPath expression there was an error in the predicate ' . - str_replace($predicate,''.$predicate.'', $this->xpath) . - '. After a closing bracket there was something unknown: "'. $after .'"', __LINE__); - } - - // Check whether it's a function. - if (empty($before) && empty($after)) { - // Evaluate the content of the brackets. - $result = $this->_evaluatePredicate($node, $between, $nodeTest); - } - elseif ($this->_isFunction($before)) { - // Return the evaluated function. - $result = $this->_evaluateFunction($before, $between, $node, $nodeTest); - } - else { - // Display an error message. - $this->_displayError('While parsing a predicate in an XPath expression, a function '. - str_replace($before, ''.$before.'', $this->xpath) . - ' was found, which is not yet supported by the parser.', __LINE__); - } - } - } - - ///////////////////////////////////////////// - // Else it must just be an XPath expression. - // Check whether it's an XPath expression. - if (!isSet($result)) { - if ($bDebugThisFunction) echo "\nPredicate is XPath expression."; - $a_xpath_result = $this->_internalEvaluate($predicate, $node); - if (count($a_xpath_result) > 0) { - // Convert the array. - $result = explode("|", implode("|", $a_xpath_result)); - // Get the value of the first result (which means we want to concat all the text...unless - // a specific text() node has been given, and it will switch off to substringData - $result = $this->wholeText($a_xpath_result[0]); - } - } - - // Else no content so return the empty string. ## N.S - if (!isSet($result)) $result = ''; - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - - // Return the array of nodes. - return $result; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Check functions for tailoring a node set - - /** - * -- sb:stoped - * - * Checks whether a node matches predicates. - * - * This method checks whether a list of nodes passed to this method match - * a given list of predicates. - * - * @author Michael P. Mehl - * @param array $nodes Array of full paths of all nodes to be tested. - * @param array $predicates Array of predicates to use. - * @return array The array returned by this method contains a list of - * all nodes matching the given predicates. - * @see _evaluateStep() - */ - function _checkPredicates($nodes, $predicates, $nodeTest) { - // If you are having difficulty using this function. Then set this to TRUE and - // you'll get diagnostic info displayed to the output. - $bDebugThisFunction = FALSE; - if ($bDebugThisFunction) { - $a_start_time = $this->_beginDebugFunction("_checkPredicates(Nodes:[$nodes], Predicates:[$predicates])"); - echo "Nodes:"; - print_r($nodes); - echo "Predicates:"; - print_r($predicates); - echo "
"; - } - ////////////////////////////////////////////// - // Create an empty set of nodes. - $result = array(); - - // Run through all nodes. - for ($i=0; $i_handleFunction_position($node, '', $nodeTest)); - // Enhance the predicate. - // $predicate .= "=position()"; - } else { - // Else do the predicate check the long and thorough way. - $check = $this->_evaluatePredicate($node, $predicate, $nodeTest); - } - // Check whether it's a string. - if (is_string($check) && ( ( $check == '' ) - || ( $check == $predicate ) )) { - // Set the result to FALSE. - $check = FALSE; - } - else if (is_bool($check) ) { - // 0 and 1 are both bools and ints. We need to capture the bools - // as they might have been the intended result ## N.S - } else - // Check whether it's an integer. - if (is_int($check)) { - // Check whether it's the current position. - if ($check == $this->_handleFunction_position($node, '', $nodeTest)) { - // Set it to TRUE. - $check = TRUE; - } - else { - // Set it to FALSE. - $check = FALSE; - } - } - if ($bDebugThisFunction) echo "Node $node matches predicate $predicate: " . (($check) ? "TRUE" : "FALSE") ."\n"; - // Check whether the predicate is OK for this node. - $add = $add && $check; - } - - // Check whether to add this node to the node-set. - if ($add) { - // Add the node to the node-set. - $result[] = $node; - } - if ($bDebugThisFunction) echo "Node $node matches: " . (($add) ? "TRUE" : "FALSE") ."\n\n"; - } - ////////////////////////////////////////////// - if ($bDebugThisFunction) { - $this->_closeDebugFunction($a_start_time, $result); - } - - // Return the array of nodes. - return $result; - } - - /** - * Checks whether a node matches a node-test. - * - * This method checks whether a node in the document matches a given - * node-test. - * - * @author Michael P. Mehl - * @param string $context Full path of the node, which should be tested - * for matching the node-test. - * @param string $nodeTest String containing the node-test for the - * node. - * @return boolean This method returns TRUE if the node matches the - * node-test, otherwise FALSE. - * @see evaluate() - */ - function _checkNodeTest($context, $nodeTest) { - // Check whether it's a function. - if (preg_match('/\(/U', $nodeTest)) { - // Get the type of function to use. - $function = $this->_prestr($nodeTest, '('); - - // Check whether the node fits the method. - switch ( $function) { - case 'node': - // Add this node to the list of nodes. - return TRUE; - case 'text': - // Check whether the node has some text. - $tmp = implode('', $this->nodes[$context]['text']); - if (!empty($tmp)) { - // Add this node to the list of nodes. - return TRUE; - } - break; - case 'comment': - // Check whether the node has some comment. - if (!empty($this->nodes[$context]['comment'])) { - // Add this node to the list of nodes. - return TRUE; - } - break; - case 'processing-instruction': - // Get the literal argument. - $literal = $this->_afterstr($axis['node-test'], '('); - - // Cut the literal. - $literal = substr($literal, 0, strlen($literal) - 1); - - // Check whether a literal was given. - if (!empty($literal)) { - // Check whether the node's processing instructions - // are matching the literals given. - if ($this->nodes[$context]['processing-instructions'] == $literal) { - // Add this node to the node-set. - return TRUE; - } - } else { - // Check whether the node has processing - // instructions. - if (!empty($this->nodes[$context]['processing-instructions'])) { - // Add this node to the node-set. - return TRUE; - } - } - break; - default: - // Display an error message. - $this->_displayError('While parsing an XPath expression there was an undefined function called "' . - str_replace($function, ''.$function.'', $this->xpath) .'"', __LINE__); - } - } - elseif ($nodeTest == '*') { - // Add this node to the node-set. - return TRUE; - } - elseif (preg_match('/^[a-zA-Z0-9\-_]+/', $nodeTest)) { - // Check whether the node-test can be fulfilled. - if ($this->nodes[$context]['name'] == $nodeTest) { - // Add this node to the node-set. - return TRUE; - } - } - else { - // Display an error message. - $this->_displayError("While parsing the XPath expression \"{$this->xpath}\" ". - "an empty and therefore invalid node-test has been found.", __LINE__); - } - - // Don't add this context. - return FALSE; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Functions to handle each of the different xpath axes. - - /** - * Handles the XPath child axis. - * - * This method handles the XPath child axis. It essentially filters out the - * children to match the name specified after the / - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_Child($axis, $context) { - // Create an empty node-set to hold the results of the child matches - $nodes = array(); - - if ( $axis["node-test"] == "cdata" ) { - if ( !isSet($this->nodes[$context]["text"]) ) return ""; - @reset($this->nodes[$context]['text']); - while(list($index, $text) = each($this->nodes[$context]['text'])) { - $nodes[] = $context . "/text()[".($index + 1)."]"; - } - } - else { - // Get a list of all children. - $allChildren = &$this->nodes[$context]['children']; - // Run through all children in the order they were set. - for ( $i=0; $i < sizeOf($allChildren); $i++ ) { - $child = $context.'/'.$allChildren[$i]; - // Check whether - if ($this->_checkNodeTest($child, $axis['node-test'])) - { - // Add the child to the node-set. - $nodes[] = $child; - } - } - } - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath parent axis. - * - * This method handles the XPath parent axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_parent ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Check whether the parent matches the node-test. - if ($this->_checkNodeTest($this->nodes[$context]['parent'], $axis['node-test'])) { - // Add this node to the list of nodes. - $nodes[] = $this->nodes[$context]['parent']; - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath attribute axis. - * - * This method handles the XPath attribute axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_attribute ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Check whether all nodes should be selected. - $nodeAttr = &$this->nodes[$context]['attributes']; - if ($axis['node-test'] == '*') { - // Check whether there are attributes. - if (count($nodeAttr) > 0) { - // Run through the attributes. - reset($nodeAttr); - while (list($key) = each($nodeAttr)) { - // Add this node to the node-set. - $nodes[] = $context.'/attribute::'.$key; - } - } - } - elseif (isSet($nodeAttr[$axis['node-test']]) AND strlen($nodeAttr[$axis['node-test']])) { - // Add this node to the node-set. - $nodes[] = $context . '/attribute::'. $axis['node-test']; - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath self axis. - * - * This method handles the XPath self axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_self ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Check whether the context match the node-test. - if ($this->_checkNodeTest($context, $axis['node-test'])) { - // Add this node to the node-set. - $nodes[] = $context; - } - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath descendant axis. - * - * This method handles the XPath descendant axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_descendant ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - // Get a list of all children. - $children = &$this->nodes[$context]['children']; - // Run through all children in the order they where set. - $childSize = sizeOf($children); - for ($i=0; $i<$childSize; $i++) { - $child = $context.'/'.$children[$i]; - // Check whether the child matches the node-test. - if ($this->_checkNodeTest($child, $axis['node-test'])) { - // Add the child to the list of nodes. - $nodes[] = $child; - } - // Recurse to the next level. - $nodes = array_merge($nodes, $this->_handleAxis_descendant($axis, $child)); - } - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath ancestor axis. - * - * This method handles the XPath ancestor axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_ancestor ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Get the parent of the current node. - $parent = $this->nodes[$context]['parent']; - - // Check whether the parent isn't empty. - if (!empty($parent)) { - // Check whether the parent matches the node-test. - if ($this->_checkNodeTest($parent, $axis['node-test'])) { - // Add the parent to the list of nodes. - $nodes[] = $parent; - } - - // Handle all other ancestors. - $nodes = array_merge($nodes, $this->_handleAxis_ancestor($axis, $parent)); - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath namespace axis. - * - * This method handles the XPath namespace axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_namespace ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Check whether all nodes should be selected. - if (!empty($this->nodes[$context]['namespace'])) { - // Add this node to the node-set. - $nodes[] = $context; - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath following axis. - * - * This method handles the XPath following axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_following ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Get the current document position. - $position = $this->nodes[$context]['doc-pos']; - - // Run through all nodes of the document. - reset($this->nodes); - while (list($node) = each($this->nodes)) { - // Check whether this is the context node. - if ($node == $context ) break; - } - while (list($node) = each($this->nodes)) { - // Check whether this is the context node. - if ($this->nodes[$node]['doc-pos'] <= $position) break; - } - do { - // Check whether the node fits the node-test. - if ($this->_checkNodeTest($node, $axis['node-test'])) { - // Add the node to the list of nodes. - $nodes[] = $node; - } - - } while (list($node) = each($this->nodes)); - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath preceding axis. - * - * This method handles the XPath preceding axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_preceding ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Get the current document position. - $position = $this->nodes[$context]['doc-pos']; - - // Run through all nodes of the document. - reset($this->nodes); - while (list($node) = each($this->nodes)) { - // skip super-Root - if (empty($node)) continue; - // Check whether this is the context node. - - if ($node == $context) { - // After this we won't look for more nodes. - break; - } - if (!strncmp($node, $context, strLen($node))) { - continue; - } - // Check whether the node fits the node-test. - if ($this->_checkNodeTest($node, $axis['node-test'])) { - // Add the node to the list of nodes. - $nodes[] = $node; - } - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath following-sibling axis. - * - * This method handles the XPath following-sibling axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_following_sibling ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Get all children from the parent. - $siblings = &$this->_handleAxis_child($axis, $this->nodes[$context]['parent']); - // Create a flag whether the context node was already found. - $found = FALSE; - - // Run through all siblings. - $size = sizeOf($siblings); - for ($i=0; $i<$size; $i++) { - $sibling = &$siblings[$i]; - - // Check whether the context node was already found. - if ($found) { - // Check whether the sibling matches the node-test. - if ($this->_checkNodeTest($sibling, $axis['node-test'])) { - // Add the sibling to the list of nodes. - $nodes[] = $sibling; - } - } - - // Check if we reached *this* context node. - if ($sibling == $context) { - // Continue looking for other siblings. - $found = TRUE; - } - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath preceding-sibling axis. - * - * This method handles the XPath preceding-sibling axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_preceding_sibling ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Get all children from the parent. - $siblings = $this->_handleAxis_child($axis, $this->nodes[$context]['parent']); - - // Run through all siblings. - $size = sizeOf($siblings); - for ($i=0; $i<$size; $i++) { - $sibling = &$siblings[$i]; - // Check whether this is the context node. - if ($sibling == $context) { - // Don't continue looking for other siblings. - break; - } - - // Check whether the sibling matches the node-test. - if ($this->_checkNodeTest($sibling, $axis['node-test'])) { - // Add the sibling to the list of nodes. - $nodes[] = $sibling; - } - } - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath descendant-or-self axis. - * - * This method handles the XPath descendant-or-self axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_descendant_or_self ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Read the nodes. - $nodes = array_merge( - $this->_handleAxis_self($axis, $context), - $this->_handleAxis_descendant($axis, $context) - ); - - // Return the nodeset. - return $nodes; - } - - /** - * Handles the XPath ancestor-or-self axis. - * - * This method handles the XPath ancestor-or-self axis. - * - * @author Michael P. Mehl - * @param array $axis Array containing information about the axis. - * @param string $context Node from which starting the axis should - * be processed. - * @return array This method returns an array containing all nodes - * that were found during the evaluation of the given axis. - * @see evaluate() - */ - function &_handleAxis_ancestor_or_self ( $axis, $context) { - // Create an empty node-set. - $nodes = array(); - - // Read the nodes. - $nodes = array_merge( - $this->_handleAxis_self($axis, $context), - $this->_handleAxis_ancestor($axis, $context) - ); - - // Return the nodeset. - return $nodes; - } - - ///////////////////////////////////////////////// - // ########################################### // - // Functions to handle each of the different xpath functions. - - /** - * Handles the XPath function last. - * - * This method handles the XPath function last. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_last ( $node, $arguments, $nodeTest) { - // Calculate the size of the context. - $parent = $this->nodes[$node]['parent']; - if ($nodeTest == "*") - { - $context = sizeOf($this->nodes[$parent]['children']); - } - else if ($nodeTest == "cdata") - { - $node = substr($node,0,strrpos($node,"/text()")); - $context = sizeOf($this->nodes[$node]["text"]); - } - else - { - $children = $this->nodes[$parent]['childCount']; - $context = $children[$this->nodes[$node]['name']]; - } - // Return the size. - return $context; - } - - /** - * Handles the XPath function position. - * - * This method handles the XPath function position. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_position ( $node, $arguments, $nodeTest) { - // return the context-position. - if ($nodeTest == "*") - { - // if we are matching all children, then we need to find the position regardless of name - $parent = $this->nodes[$node]['parent']; - $indexChildren = array_flip($this->nodes[$parent]['children']); - $currentChild = substr($node,strrpos($node,"/")+1,strlen($node)); - $context = $indexChildren[$currentChild] + 1; - } - // if we are looking for text nodes, we go about it a bit differently - else if( $nodeTest == "cdata" ) { - $context = substr($node,strrpos($node,"[")+1,-1); - } - else - { - $context = $this->nodes[$node]['context-pos']; - } - return $context; - } - - /** - * Handles the XPath function count. - * - * This method handles the XPath function count. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_count ( $node, $arguments, $nodeTest) { - // Evaluate the argument of the method as an XPath and return - // the number of results. - return count($this->_internalEvaluate($arguments, $node)); - } - - /** - * Handles the XPath function id. - * - * This method handles the XPath function id. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_id ( $node, $arguments, $nodeTest) { - // Trim the arguments. - $arguments = trim($arguments); - - // Now split the arguments. - $arguments = explode(' ', $arguments); - - // Check whether - - // Create a list of nodes. - $nodes = array(); - - // Run through all nodes of the document. - reset($this->nodes); - while (list($node) = each($this->nodes)) { - // skip super-Root - if (empty($node)) continue; - // Check whether the node has the ID we're looking for. - if (in_array($this->nodes[$node]['attributes']['id'], $arguments)) { - // Add this node to the list of nodes. - $nodes[] = $node; - } - } - - // Return the list of nodes. - return $nodes; - } - - /** - * Handles the XPath function name. - * - * This method handles the XPath function name. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_name ( $node, $arguments, $nodeTest) { - // Return the name of the node. - return $this->nodes[$node]['name']; - } - - /** - * Handles the XPath function string. - * - * This method handles the XPath function string. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_string ( $node, $arguments, $nodeTest) { - // Check what type of parameter is given - if (preg_match('/^[0-9]+(\.[0-9]+)?$/', $arguments) - || preg_match('/^\.[0-9]+$/', $arguments)) { - // Convert the digits to a number. - $number = doubleval($arguments); - - // Return the number. - return strval($number); - } - elseif (is_bool($arguments)) { - // Check whether it's TRUE. - if ($arguments == TRUE) { - // Return TRUE as a string. - return 'TRUE'; - } - else { - // Return FALSE as a string. - return 'FALSE'; - } - } - elseif (!empty($arguments)) { - // Use the argument as an XPath. - $result = $this->_internalEvaluate($arguments, $node); - - // Get the first argument. - $result = explode('|', implode('|', $result)); - - // Return the first result as a string. - return $result[0]; - } - elseif (empty($arguments)) { - // Return the current node. - return $node; - } - else { - // Return an empty string. - return ''; - } - } - - /** - * Handles the XPath function concat. - * - * This method handles the XPath function concat. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_concat ( $node, $arguments, $nodeTest) { - // Split the arguments. - $arguments = explode(',', $arguments); - - // Run through each argument and evaluate it. - for ( $i = 0; $i < sizeof($arguments); $i++) { - // Trim each argument. - $arguments[$i] = trim($arguments[$i]); - - // Evaluate it. - $arguments[$i] = $this->_evaluatePredicate($node, $arguments[$i], $nodeTest); - } - - // Put the string together. - $arguments = implode('', $arguments); - - // Return the string. - return $arguments; - } - - /** - * Handles the XPath function starts-with. - * - * This method handles the XPath function starts-with. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_starts_with ($node, $arguments, $nodeTest) { - // Get the arguments. - $first = trim($this->_prestr($arguments, ',')); - $second = trim($this->_afterstr($arguments, ',')); - - // Evaluate each argument. - $first = $this->_evaluatePredicate($node, $first, $nodeTest); - $second = $this->_evaluatePredicate($node, $second, $nodeTest); - - // Check whether the first string starts with the second one. - if (ereg('^'.$second, $first)) { - // Return TRUE. - return TRUE; - } else { - // Return FALSE. - return FALSE; - } - } - - /** - * Handles the XPath function contains. - * - * This method handles the XPath function contains. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_contains ( $node, $arguments, $nodeTest) { - // Get the arguments. - $first = trim($this->_prestr($arguments, ',')); - $second = trim($this->_afterstr($arguments, ',')); - //echo "Predicate: $arguments First: ".$first." Second: ".$second."\n"; - - // Evaluate each argument. - $first = $this->_evaluatePredicate($node, $first, $nodeTest); - $second = $this->_evaluatePredicate($node, $second, $nodeTest); - //echo $second.": ".$first."\n"; - // If the search string is null, then the provided there is a value it will contain it as - // it is considered that all strings contain the empty string. ## N.S. - if ($second==='') return TRUE; - - // Check whether the first string starts with the second one. - if (strpos($first, $second) === FALSE) { - return FALSE; - } else { - return TRUE; - } - } - - /** - * Handles the XPath function substring-before. - * - * This method handles the XPath function substring-before. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_substring_before ( $node, $arguments, $nodeTest) { - // Get the arguments. - $first = trim($this->_prestr($arguments, ',')); - $second = trim($this->_afterstr($arguments, ',')); - - // Evaluate each argument. - $first = $this->_evaluatePredicate($node, $first, $nodeTest); - $second = $this->_evaluatePredicate($node, $second, $nodeTest); - - // Return the substring. - return $this->_prestr(strval($first), strval($second)); - } - - /** - * Handles the XPath function substring-after. - * - * This method handles the XPath function substring-after. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_substring_after ( $node, $arguments, $nodeTest) { - // Get the arguments. - $first = trim($this->_prestr($arguments, ',')); - $second = trim($this->_afterstr($arguments, ',')); - - // Evaluate each argument. - $first = $this->_evaluatePredicate($node, $first, $nodeTest); - $second = $this->_evaluatePredicate($node, $second, $nodeTest); - - // Return the substring. - return $this->_afterstr(strval($first), strval($second)); - } - - /** - * Handles the XPath function substring. - * - * This method handles the XPath function substring. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_substring ( $node, $arguments, $nodeTest) { - // Split the arguments. - $arguments = explode(",", $arguments); - - // Run through all arguments. - for ( $i = 0; $i < sizeof($arguments); $i++) { - // Trim the string. - $arguments[$i] = trim($arguments[$i]); - - // Evaluate each argument. - $arguments[$i] = $this->_evaluatePredicate($node, $arguments[$i], $nodeTest); - } - - // Check whether a third argument was given. - if (!empty($arguments[2])) { - // Return the substring. - return substr(strval($arguments[0]), $arguments[1] - 1, - $arguments[2]); - } else { - // Return the substring. - return substr(strval($arguments[0]), $arguments[1] - 1); - } - } - - /** - * Handles the XPath function string-length. - * - * This method handles the XPath function string-length. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_string_length ( $node, $arguments, $nodeTest) { - // Trim the argument. - $arguments = trim($arguments); - // Evaluate the argument. - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - // Return the length of the string. - return strlen(strval($arguments)); - } - - /** - * Handles the XPath function normalize-space. - * - * The normalize-space function returns the argument string with whitespace - * normalized by stripping leading and trailing whitespace and replacing sequences - * of whitespace characters by a single space. - * If the argument is omitted, it defaults to the context node converted to a string, - * in other words the string-value of the context node - * - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return string trimed string - * @see evaluate() - */ - function _handleFunction_normalize_space ( $node, $arguments, $nodeTest) { - // Trim the argument. - if (empty($arguments)) { - $arguments = $this->nodes[$node]['parent'].'/'.$this->nodes[$node]['name'].'['.$this->nodes[$node]['context-pos'].']'; - } else { - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - } - $arguments = trim(preg_replace (";[[:space:]]+;s",' ',$arguments)); - return $arguments; - } - - /** - * Handles the XPath function translate. - * - * This method handles the XPath function translate. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_translate ( $node, $arguments, $nodeTest) { - // Split the arguments. - $arguments = explode(',', $arguments); - - // Run through all arguments. - for ( $i = 0; $i < sizeof($arguments); $i++) { - // Trim the argument. - $arguments[$i] = trim($arguments[$i]); - // Evaluate the argument. - $arguments[$i] = $this->_evaluatePredicate($node, $arguments[$i], $nodeTest); - } - - // Return the translated string. - return strtr($arguments[0], $arguments[1], $arguments[2]); - } - - /** - * Handles the XPath function boolean. - * - * This method handles the XPath function boolean. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_boolean ( $node, $arguments, $nodeTest) { - // Trim the arguments. - $arguments = trim($arguments); - - // Check what type of parameter is given - if (preg_match('/^[0-9]+(\.[0-9]+)?$/', $arguments) - || preg_match('/^\.[0-9]+$/', $arguments)) { - // Convert the digits to a number. - $number = doubleval($arguments); - - // Check whether the number zero. - if ($number == 0) { - // Return FALSE. - return FALSE; - } else { - // Return TRUE. - return TRUE; - } - } - elseif (empty($arguments)) { - // Sorry, there were no arguments. - return FALSE; - } - else { - // Try to evaluate the argument as an XPath. - $result = $this->_internalEvaluate($arguments, $node); - - // Check whether we found something. - if (count($result) > 0) { - // Return TRUE. - return TRUE; - } else { - // Return FALSE. - return FALSE; - } - } - } - - /** - * Handles the XPath function not. - * - * This method handles the XPath function not. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_not ( $node, $arguments, $nodeTest) { - // Trim the arguments. - $arguments = trim($arguments); - - // Return the negative value of the content of the brackets. - return !$this->_evaluatePredicate($node, $arguments, $nodeTest); - } - - /** - * Handles the XPath function TRUE. - * - * This method handles the XPath function TRUE. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_true ( $node, $arguments, $nodeTest) { - // Return TRUE. - return TRUE; - } - - /** - * Handles the XPath function FALSE. - * - * This method handles the XPath function FALSE. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_false ( $node, $arguments, $nodeTest) { - // Return FALSE. - return FALSE; - } - - /** - * Handles the XPath function lang. - * - * This method handles the XPath function lang. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_lang ( $node, $arguments, $nodeTest) { - // Trim the arguments. - $arguments = trim($arguments); - - // Check whether the node has an language attribute. - if (empty($this->nodes[$node]['attributes']['xml:lang'])) { - // Run through the ancestors. - while ( !empty($node)) { - // Select the parent node. - $node = $this->nodes[$node]['parent']; - - // Check whether there's a language definition. - if (!empty($this->nodes[$node]['attributes']['xml:lang'])) { - // Check whether it's the language, the user asks for. - if (eregi('^'.$arguments, $this->nodes[$node]['attributes']['xml:lang'])) { - // Return TRUE. - return TRUE; - } else { - // Return FALSE. - return FALSE; - } - } - } - - // Return FALSE. - return FALSE; - } else { - // Check whether it's the language, the user asks for. - if (eregi('^'.$arguments, $this->nodes[$node]['attributes']['xml:lang'])) { - // Return TRUE. - return TRUE; - } else { - // Return FALSE. - return FALSE; - } - } - } - - /** - * Handles the XPath function number. - * - * This method handles the XPath function number. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_number ( $node, $arguments, $nodeTest) { - if (!is_numeric($arguments)) { - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - } - // Check the type of argument. - if (is_numeric($arguments)) { - // Return the argument as a number. - return doubleval($arguments); - } - elseif (is_bool($arguments)) { - // Check whether it's TRUE. - if ($arguments == TRUE) { - // Return 1. - return 1; - } else { - // Return 0. - return 0; - } - } - } - - /** - * Handles the XPath function sum. - * - * This method handles the XPath function sum. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_sum ( $node, $arguments, $nodeTest) { - // Trim the arguments. - $arguments = trim($arguments); - - // Evaluate the arguments as an XPath expression. - $result = $this->_internalEvaluate($arguments, $node); - - // Create a variable to save the sum. - $sum = 0; - - // Run through all results. - for ($i=0; $isubstringData($result[$i]); - // Add it to the sum. - $sum += doubleval($value); - } - - // Return the sum. - return $sum; - } - - /** - * Handles the XPath function floor. - * - * This method handles the XPath function floor. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_floor ( $node, $arguments, $nodeTest) { - if (!is_numeric($arguments)) { - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - } - // Convert the arguments to a number. - $arguments = doubleval($arguments); - - // Return the result - return floor($arguments); - } - - /** - * Handles the XPath function ceiling. - * - * This method handles the XPath function ceiling. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_ceiling ( $node, $arguments, $nodeTest) { - if (!is_numeric($arguments)) { - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - } - - // Convert the arguments to a number. - $arguments = doubleval($arguments); - - // Return the result - return ceil($arguments); - } - - /** - * Handles the XPath function round. - * - * This method handles the XPath function round. - * - * @author Michael P. Mehl - * @param string $node Full path of the node on which the function - * should be processed. - * @param string $arguments String containing the arguments that were - * passed to the function. - * @return mixed Depending on the type of function being processed this - * method returns different types. - * @see evaluate() - */ - function _handleFunction_round ( $node, $arguments, $nodeTest) { - if (!is_numeric($arguments)) { - $arguments = $this->_evaluatePredicate($node, $arguments, $nodeTest); - } - - // Convert the arguments to a number. - $arguments = doubleval($arguments); - - // Return the result - return round($arguments); - } - - ///////////////////////////////////////////////// - // ########################################### // - // General helper functions - - /** - * Set the content of a node. - * - * This method modifies the content of a text node. If it's an attribute node, then - * the value of the attribute will be modified, otherwise the complete character - * data of the specific text node will be set. This function is used for all the Data - * functions, as the modification can be reduced to a single statment once all the checks - * and initializations are done - * - * @param string $xPathQuery path to the node (See text above). *READONLY* - * @param string $content String containing the content to be set. *READONLY* - * @param bool $replace if TRUE the given substring will be replaced with the $content... - * else it will be inserted or appended - * @param int $offset will be where the string will be inserted or begin to be replaced.. - * a value of 1 is the beginning, 0 will be the end - * @see appendData(), insertData(), replaceData(), deleteData(), substringData() - */ - function _setContent($absoluteXPath, $content, $replace, $offset = -1, $count = 0 ) { - // we default offset to -1 for the end of the string - // Check whether this path goes right to a text node, if so then modify the cdata - if ( preg_match(":(.*)/text\(\)(\[(.*)\])?$:U",$absoluteXPath,$matches) ) { - $absoluteXPath = $matches[1]; - // default to the first text node if a text node was not specified - $textPart = isset($matches[2]) ? substr($matches[2],1,-1) : 1; - // Numpty check - if ( !isSet($this->nodes[$absoluteXPath]) ) { - // Try to evaluate the absoluteXPath (since it really isn't an absolutePath) - $resultArr = $this->match("$absoluteXPath/text()[$textPart]"); - if ( sizeOf($resultArr) == 1 ) { - preg_match(":(.*)/text\(\)(\[(.*)\])?$:U",$resultArr[0],$matches); - $absoluteXPath = $matches[1]; - $textPart = isset($matches[2]) ? substr($matches[2],1,-1) : 1; - } - else { - $this->_displayError("The $absoluteXPath/text() does not evaluate to a single text node in this document.", __LINE__, FALSE); - return; - } - } - if ( !isSet($this->nodes[$absoluteXPath]["text"][$textPart - 1]) ) - { - $this->_displayError("The $absoluteXPath does not have a text value at position ".($textPart-1), __LINE__, FALSE); - return; - } - // Get a reference to the text node - $textNode = &$this->nodes[$absoluteXPath]["text"][$textPart - 1]; - } - else if ( preg_match(";(.*)/(attribute::|@)([^/]*)$;U",$absoluteXPath,$matches) ) { - $absoluteXPath = $matches[1]; - $attribute = $matches[3]; - // Numpty check (1. make sure root path exists 2. make sure attribute exists) - if ( !isSet($this->nodes[$absoluteXPath]) ) { - // Try to evaluate the absoluteXPath (since it really isn't an absolutePath) - $resultArr = $this->match("$absoluteXPath/attribute::$attribute"); - if ( sizeOf($resultArr) == 1 ) { - preg_match(";(.*)/attribute::([^/]*)$;U",$resultArr[0],$matches); - $absoluteXPath = $matches[1]; - $attribute = $matches[2]; - } - else { - $this->_displayError("The $absoluteXPath/attribute::$attribute does not evaluate to a single node.", __LINE__, FALSE); - break; - } - } - else if ( !isSet($this->nodes[$absoluteXPath]["attributes"][$attribute]) ) { - $this->_displayError("The $absoluteXPath/attribute::$attribute value isn't a node in this document.", __LINE__, FALSE); - break; - } - // Get a reference to the attribute node - $textNode = &$this->nodes[$absoluteXPath]['attributes'][$attribute]; - } - else { - // we have been given an xpath with neither a text() or attribute axis at the end - // find the first text() node and return that - // Numpty check - if ( !isSet($this->nodes[$absoluteXPath]) ) { - // Try to evaluate the absoluteXPath (since it really isn't an absolutePath) - $resultArr = $this->match($absoluteXPath); - if ( sizeOf($resultArr) == 1 ) { - $absoluteXPath = $resultArr[0]; - } - else { - $this->_displayError("The $absoluteXPath does not evaluate to a single node in this document.", __LINE__, FALSE); - return; - } - } - if ( !is_array($this->nodes[$absoluteXPath]["text"]) ) { - $this->_displayError("The $absoluteXPath is not a valid element node in the document.", __LINE__, FALSE); - return; - } - $textNode = &$this->nodes[$absoluteXPath]["text"][0]; - } - - // Now modify the cdata, using the reference $textNode - if ($replace) { - $textNode = substr($textNode, 0, $offset) . $content . (($count) ? substr($textNode, $offset+$count) : ''); - } - else { - // if the offset is -1, append it, else insert it at the offset - $textNode = ($offset == -1) ? "$textNode$content" : substr($textNode, 0, $offset).$content.substr($textNode, $offset); - // This return is for substringData, ignored by rest of the functions - return ($count) ? substr($textNode, $offset, $count) : substr($textNode, $offset); - } - - } - - ///////////////////////////////////////////////// - // ########################################### // - // Auxilliary functions for dealing with bracketed strings. - - /** - * This method checks the right ammount and match of brackets - * - * @author Sam Blume - * @param string $term String in which is checked. - * @return bool TRUE: OK / FALSE: KO - * @see _evaluateStep() - */ - function _bracketsCheck(&$term) { - $leng = strlen($term); - $brackets = 0; - $bracketMisscount = $bracketMissmatsh = FALSE; - $stack = array(); - for ( $i = 0; $i < $leng; $i++) { - switch ($term[$i]) { - case '(' : - case '[' : - $stack[$brackets] = $term[$i]; - $brackets++; - break; - case ')': - $brackets--; - if ($brackets<0) { - $bracketMisscount = TRUE; - break 2; - } - if ($stack[$brackets] != '(') { - $bracketMissmatsh = TRUE; - break 2; - } - break; - case ']' : - $brackets--; - if ($brackets<0) { - $bracketMisscount = TRUE; - break 2; - } - if ($stack[$brackets] != '[') { - $bracketMissmatsh = TRUE; - break 2; - } - break; - } - } - // Check whether we had a valid number of brackets. - if ($brackets != 0 ) $bracketMisscount = TRUE; - if ($bracketMisscount || $bracketMissmatsh) { - return FALSE; - } - return TRUE; - } - - /** - * Looks for a string within another string. - * - * This method looks for a string within another string. Brackets in the - * string the method is looking through will be respected, which means that - * only if the string the method is looking for is located outside of - * brackets, the search will be successful. - * - * @author Michael P. Mehl - * @param string $term String in which the search shall take place. - * @param string $expression String that should be searched. - * @return int This method returns -1 if no string was found, otherwise - * the offset at which the string was found. - * @see _evaluateStep() - */ - function _searchString($term, $expression) { - $bracketCounter = 0; - $leng = strlen($term); - for ( $i = 0; $i < $leng; $i++) { - $char = $term[$i]; - if ($char=='(' || $char=='[') { - $bracketCounter++; - continue; - } - elseif ($char==')' || $char==']') { - $bracketCounter--; - continue; - } - if ($bracketCounter == 0) { - // Check whether we can find the expression at this index. - if (substr($term, $i, strlen($expression)) == $expression) { - // Return the current index. - return $i; - } - } - } - // Check whether we had a valid number of brackets. - if ($bracketCounter != 0) { - // Display an error message. - $this->_displayError('While parsing an XPath expression, in the predicate ' . - str_replace($term, ''.$term.'', $this->xpath) . - ', there was an invalid number of brackets.', __LINE__); - } - // Nothing was found. - return (-1); - } - - ///////////////////////////////////////////////// - // ########################################### // - // Auxilliary utilities - - /** - * Retrieves a substring before a delimiter. - * - * This method retrieves everything from a string before a given delimiter, - * not including the delimiter. - * - * @author Michael P. Mehl - * @param string $string String, from which the substring should be - * extracted. - * @param string $delimiter String containing the delimiter to use. - * @return string Substring from the original string before the - * delimiter. - * @see _afterstr() - */ - function _prestr(&$string, $delimiter, $offset=0) { - // Return the substring. - //return substr($string, 0, strlen($string) - strlen(strstr($string, "$delimiter"))); - $offset = ($offset<0) ? 0 : $offset; - $pos = strpos($string, $delimiter, $offset); - if ($pos===FALSE) { - return $string; - } else { - return substr($string, 0, $pos); - } - } - - /** - * Retrieves a substring after a delimiter. - * - * This method retrieves everything from a string after a given delimiter, - * not including the delimiter. - * - * @author Michael P. Mehl - * @param string $string String, from which the substring should be - * extracted. - * @param string $delimiter String containing the delimiter to use. - * @return string Substring from the original string after the - * delimiter. - * @see _prestr() - */ - function _afterstr(&$string, $delimiter, $offset=0) { - $offset = ($offset<0) ? 0 : $offset; - // Return the substring. - return substr($string, strpos($string, $delimiter, $offset) + strlen($delimiter)); - } - - /** - * !! terminate should not be allowed !! --fab - * - * Displays an error message. - * - * This method displays an error messages and stops the execution of the - * script. - * - * @author Michael P. Mehl - * @param $message string Error message to be displayed. - * @param $lineNumber int line number given by __LINE__ - * @param $terminate bool (default TURE) End the execution of this script. - */ - function _displayError($message, $lineNumber='-', $terminate=TRUE) { - // Display the error message. - $err = 'XPath error in '.basename(__FILE__).':'.$lineNumber.' '.$message."
\n"; - $this->_setLastError($message, $lineNumber); - if (($this->_verboseLevel > 0) OR ($terminate)) echo $err; - // End the execution of this script. - if ($terminate) exit; - } - - /** - * creates a textual error message and sets it. - * - * example: 'XPath error in THIS_FILE_NAME:LINE. Message: YOUR_MESSAGE'; - * - * i don't think the message should include any markup because not everyone wants to debug - * into the browser window. - * - * based on the deprecated _displayError() and replaces it. - * - * @author fab - * @param string $message a textual error message default is '' - * @param int $line the line number where the error occured, use __LINE__ - * @return void - * @see getLastError() - */ - function _setLastError($message='', $line='-') { - $this->_lastError = 'XPath error in ' . basename(__FILE__) . ':' . $line . '. Message: ' . $message; - } - - /** - * Determine if the function has any content - * - * Returns TRUE if this object has any xml content. i.e. after a successfull - * load_XXX() call we will have content, but before we shouldn't. - * - * @author Nigel Swinson - * @return TRUE if the object holds any content, FALSE otherwise. - */ - function _objectHasContent() { - return (count($this->nodes)); - } - - ///////////////////////////////////////////////// - // ########################################### // - // Auxilliary debug utilities to help debug functions. - - /** - * Called to begin the debug run of a function. - * - * This method starts a
 tag so that the entry to this function
-   * is clear to the debugging user.  Call _closeDebugFunction() at the
-   * end of the function to create a clean box round the function call.
-   *
-   * @author    Nigel Swinson 
-   * @author    Sam   Blum    
-   * @param     string $FunctionName the name of the function we are beginning to debug
-   * @return    array the output from the gettimeofday function.
-   * @see       _closeDebugFunction()
-   */
-  function _beginDebugFunction($function_name) {
-    $fileName = basename(__FILE__);
-    static $color = array('green','blue','red','lime','fuchsia', 'aqua');
-    static $colIndex = -1;
-    $colIndex++;
-    $pre = '
';
-    $out = '
' . $pre . "{$fileName} : {$function_name}
"; - echo $out; - return microtime(); - } - - /** - * Called to end the debug run of a function. - * - * This method ends a
 block and reports the time since $aStartTime
-   * is clear to the debugging user.
-   *
-   * @author    Nigel Swinson 
-   * @param     array $a_starttime the time that the function call was started.
-   * @param     any $return_value the return value from the function call that 
-   *            we are debugging
-   */
-  function _closeDebugFunction($a_starttime, $return_value = "") {
-    echo "
"; - if (isSet($return_value)) { - if (is_array($return_value)) - echo "Return Value: ".print_r($return_value)."\n"; - else if (is_numeric($return_value)) - echo "Return Value: '$return_value'\n"; - else if (is_bool($return_value)) - echo "Return Value: ".($return_value ? "TRUE" : "FALSE")."\n"; - else - echo "Return Value: \"".htmlspecialchars($return_value)."\"\n"; - } - $this->_profileFunction($a_starttime, "Function took"); - echo " \n
"; - } - - /** - * Call to return time since start of function for Profiling - * - * @param array $a_starttime the time that the function call was started. - * @param string $alert_string the string to describe what has just finished happening - */ - function _profileFunction($a_starttime, $alert_string) { - // Print the time it took to call this function. - $now = explode(' ', microtime()); - $last = explode(' ', $a_starttime); - $delta = (round( (($now[1] - $last[1]) + ($now[0] - $last[0]))*1000 )); - echo "\n{$alert_string} {$delta} ms"; - } - //////////////////////////////////////////////////////////////////////////////////////////////// - -} -?>