version 1.0 | // | Copyright (c) 2001 Michael P. Mehl. All rights reserved. | // +----------------------------------------------------------------------+ // | Latest releases are available at http://phpxml.org/. For feedback or | // | bug reports, please contact the author at mpm@phpxml.org. Thanks! | // +----------------------------------------------------------------------+ // | The contents of this file are subject to the Mozilla Public License | // | Version 1.1 (the "License"); you may not use this file except in | // | compliance with the License. You may obtain a copy of the License at | // | http://www.mozilla.org/MPL/ | // | | // | Software distributed under the License is distributed on an "AS IS" | // | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See | // | the License for the specific language governing rights and | // | limitations under the License. | // | | // | The Original Code is . | // | | // | The Initial Developer of the Original Code is Michael P. Mehl. | // | Portions created by Michael P. Mehl are Copyright (C) 2001 Michael | // | P. Mehl. All Rights Reserved. | // +----------------------------------------------------------------------+ // | Authors: | // | Michael P. Mehl | // +----------------------------------------------------------------------+ // /** * Class for accessing XML data through the XPath language. * * This class offers methods for accessing the nodes of a XML document using * the XPath language. You can add or remove 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. * * @link http://www.phpxml.org/ Latest release of this class * @link http://www.w3.org/TR/xpath W3C XPath Recommendation * @copyright Copyright (c) 2001 Michael P. Mehl. All rights reserved. * @author Michael P. Mehl * @version 1.0 (2001-03-08) * @access public */ class XML { /** * List of all document nodes. * * This array contains a list of all document nodes saved as an * associative array. * * @access private * @var array */ var $nodes = array(); /** * List of document node IDs. * * This array contains a list of all IDs of all document nodes that * are used for counting when adding a new node. * * @access private * @var array */ var $ids = array(); /** * Current document path. * * This variable saves the current path while parsing a XML file and adding * the nodes being read from the file. * * @access private * @var string */ 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. * * @access private * @var int */ 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. * * @access private * @var string */ var $root = ""; /** * Current XPath expression. * * This string contains the full XPath expression being parsed currently. * * @access private * @var string */ var $xpath = ""; /** * List of entities to be converted. * * This array contains a list of entities to be converted when an XPath * expression is evaluated. * * @access private * @var array */ var $entities = array ( "&" => "&", "<" => "<", ">" => ">", "'" => "&apos", '"' => """ ); /** * List of supported XPath axes. * * This array contains a list of all valid axes that can be evaluated in an * XPath expression. * * @access private * @var array */ var $axes = array ( "child", "descendant", "parent", "ancestor", "following-sibling", "preceding-sibling", "following", "preceding", "attribute", "namespace", "self", "descendant-or-self", "ancestor-or-self" ); /** * List of supported XPath functions. * * This array contains a list of all valid functions that can be evaluated * in an XPath expression. * * @access private * @var array */ var $functions = array ( "last", "position", "count", "id", "name", "string", "concat", "starts-with", "contains", "substring-before", "substring-after", "substring", "string-length", "translate", "boolean", "not", "true", "false", "lang", "number", "sum", "floor", "ceiling", "round", "text" ); /** * 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). * * @access private * @var array */ var $operators = array( " or ", " and ", "=", "!=", "<=", "<", ">=", ">", "+", "-", "*", " div ", " mod " ); /** * Constructor of the class. * * This constructor initializes the class and, when a filename is given, * tries to read and parse the given file. * * @access public * @author Michael P. Mehl * @param string $file Path and name of the file to read and parsed. * @see load_file() */ function XML ( $file = "" ) { // Check whether a file was given. if ( !empty($file) ) { // Load the XML file. $this->load_file($file); } } /** * 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. * * @access public * @author Michael P. Mehl * @param string $file Path and name of the file to be read and parsed. * @see handle_start_element(), handle_end_element(), * handle_character_data() */ function load_file ( $file ) { // Check whether the file exists and is readable. if ( file_exists($file) && is_readable($file) ) { // Read the content of the file. $content = implode("", file($file)); // Check whether content has been read. if ( !empty($content) ) { // Create an XML parser. $parser = xml_parser_create(); // Set the options for parsing the XML data. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Set the object for the parser. xml_set_object($parser, &$this); // Set the element handlers for the parser. xml_set_element_handler($parser, "handle_start_element", "handle_end_element"); xml_set_character_data_handler($parser, "handle_character_data"); // Parse the XML file. if ( !xml_parse($parser, $content, true) ) { // Display an error message. $this->display_error("XML error in file %s, line %d: %s", $file, xml_get_current_line_number($parser), xml_error_string(xml_get_error_code($parser))); } // Free the parser. xml_parser_free($parser); } } else { // Display an error message. $this->display_error("File %s could not be found or read.", $file); } } /** * 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. * * @access public * @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 $root 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. * @return string The returned string contains well-formed XML data * representing the content of this document. * @see load_file(), evaluate(), get_content() */ function get_file ( $highlight = array(), $root = "", $level = 0 ) { // Create a string to save the generated XML data. $xml = ""; // Create two strings containing the tags for highlighting a node. $highlight_start = ""; $highlight_end = ""; // Generate a string to be displayed before the tags. $before = ""; // Calculate the amount of whitespaces to display. for ( $i = 0; $i < ( $level * 2 ); $i++ ) { // Add a whitespaces to the string. $before .= " "; } // Check whether a root node is given. if ( empty($root) ) { // Set it to the document root. $root = $this->root; } // Check whether the node is selected. $selected = in_array($root, $highlight); // Now add the whitespaces to the XML data. $xml .= $before; // Check whether the node is selected. if ( $selected ) { // Add the highlight code to the XML data. $xml .= $highlight_start; } // Now open the tag. $xml .= "<".$this->nodes[$root]["name"]; // Check whether there are attributes for this node. if ( count($this->nodes[$root]["attributes"]) > 0 ) { // Run through all attributes. foreach ( $this->nodes[$root]["attributes"] as $key => $value ) { // Check whether this attribute is highlighted. if ( in_array($root."/attribute::".$key, $highlight) ) { // Add the highlight code to the XML data. $xml .= $highlight_start; } // Add the attribute to the XML data. $xml .= " ".$key."=\"".trim(stripslashes($value))."\""; // Check whether this attribute is highlighted. if ( in_array($root."/attribute::".$key, $highlight) ) { // Add the highlight code to the XML data. $xml .= $highlight_end; } } } // Check whether the node contains character data or has children. if ( empty($this->nodes[$root]["text"]) && !isset($this->nodes[$root]["children"]) ) { // Add the end to the tag. $xml .= "/"; } // Close the tag. $xml .= ">\n"; // Check whether the node is selected. if ( $selected ) { // Add the highlight code to the XML data. $xml .= $highlight_end; } // Check whether the node contains character data. if ( !empty($this->nodes[$root]["text"]) ) { // Add the character data to the XML data. $xml .= $before." ".$this->nodes[$root]["text"]."\n"; } // Check whether the node has children. if ( isset($this->nodes[$root]["children"]) ) { // Run through all children with different names. foreach ( $this->nodes[$root]["children"] as $child => $pos ) { // Run through all children with the same name. for ( $i = 1; $i <= $pos; $i++ ) { // Generate the full path of the child. $fullchild = $root."/".$child."[".$i."]"; // Add the child's XML data to the existing data. $xml .= $this->get_file($highlight, $fullchild, $level + 1); } } } // Check whether there are attributes for this node. if ( !empty($this->nodes[$root]["text"]) || isset($this->nodes[$root]["children"]) ) { // Add the whitespaces to the XML data. $xml .= $before; // Check whether the node is selected. if ( $selected ) { // Add the highlight code to the XML data. $xml .= $highlight_start; } // Add the closing tag. $xml .= "</".$this->nodes[$root]["name"].">"; // Check whether the node is selected. if ( $selected ) { // Add the highlight code to the XML data. $xml .= $highlight_end; } // Add a linebreak. $xml .= "\n"; } // Return the XML data. return $xml; } /** * 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. * * @access public * @author Michael P. Mehl * @param string $content Full path of the parent, to which the new * node should be added as a child. * @param string $name Name of the new node. * @return string The string returned by this method will contain the * full document path of the created node. * @see remove_node(), evaluate() */ function add_node ( $context, $name ) { // Check whether a name for this element is already set. if ( empty($this->root) ) { // Use this tag as the root element. $this->root = "/".$name."[1]"; } // Calculate the full path for this element. $path = $context."/".$name; // Set the relative context and the position. $position = ++$this->ids[$path]; $relative = $name."[".$position."]"; // Calculate the full path. $fullpath = $context."/".$relative; // Calculate the context position, which is the position of this // element within elements of the same name in the parent node. $this->nodes[$fullpath]["context-position"] = $position; // Calculate the position for the following and preceding axis // detection. $this->nodes[$fullpath]["document-position"] = $this->nodes[$context]["document-position"] + 1; // Save the information about the node. $this->nodes[$fullpath]["name"] = $name; $this->nodes[$fullpath]["text"] = ""; $this->nodes[$fullpath]["parent"] = $context; // Add this element to the element count array. if ( !$this->nodes[$context]["children"][$name] ) { // Set the default name. $this->nodes[$context]["children"][$name] = 1; } else { // Calculate the name. $this->nodes[$context]["children"][$name] = $this->nodes[$context]["children"][$name] + 1; } // Return the path of the new node. return $fullpath; } /** * 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. * * @access public * @author Michael P. Mehl * @param string $node Full path of the node to be removed. * @see add_node(), evaluate() */ function remove_node ( $node ) { // Check whether the node is an attribute node. if ( preg_match('/'."\\/attribute::".'/', $node) ) { // Get the path to the attribute node's parent. $parent = $this->prestr($node, "/attribute::"); // Get the name of the attribute. $attribute = $this->afterstr($node, "/attribute::"); // Check whether the attribute exists. if ( isset($this->nodes[$parent]["attributes"][$attribute]) ) { // Create a new array. $new = array(); // Run through the existing attributes. foreach ( $this->nodes[$parent]["attributes"] as $key => $value ) { // Check whether it's the attribute to remove. if ( $key != $attribute ) { // Add it to the new array again. $new[$key] = $value; } } // Save the new attributes. $this->nodes[$parent]["attributes"] = $new; } } else { // Create an associative array, which contains information about // all nodes that required to be renamed. $rename = array(); // Get the name, the parent and the siblings of current node. $name = $this->nodes[$node]["name"]; $parent = $this->nodes[$node]["parent"]; $siblings = $this->nodes[$parent]["children"][$name]; // Decrease the number of children. $this->nodes[$parent]["children"][$name]--; // Create a counter for renumbering the siblings. $counter = 1; // Now run through the siblings. for ( $i = 1; $i <= $siblings; $i++ ) { // Create the name of the sibling. $sibling = $parent."/".$name."[".$i."]"; // Check whether it's the name of the current node. if ( $sibling != $node ) { // Create the new name for the sibling. $new = $parent."/".$name."[".$counter."]"; // Increase the counter. $counter++; // Add the old and the new name to the list of nodes // to be renamed. $rename[$sibling] = $new; } } // Create an array for saving the new node-list. $nodes = array(); // Now run through through the existing nodes. foreach ( $this->nodes as $name => $values ) { // Check the position of the path of the node to be deleted // in the path of the current node. $position = strpos($name, $node); // Check whether it's not the node to be deleted. if ( $position === false ) { // Run through the array of nodes to be renamed. foreach ( $rename as $old => $new ) { // Check whether this node and it's parent requires to // be renamed. $name = str_replace($old, $new, $name); $values["parent"] = str_replace($old, $new, $values["parent"]); } // Add the node to the list of nodes. $nodes[$name] = $values; } } // Save the new array of nodes. $this->nodes = $nodes; } } /** * Add content to 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. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node. * @param string $value String containing the content to be added. * @see get_content(), evaluate() */ function add_content ( $path, $value ) { // Check whether it's an attribute node. if ( preg_match('/'."\\/attribute::".'/', $path) ) { // Get the path to the attribute node's parent. $parent = $this->prestr($path, "/attribute::"); // Get the parent node. $parent = $this->nodes[$parent]; // Get the name of the attribute. $attribute = $this->afterstr($path, "/attribute::"); // Set the attribute. $parent["attributes"][$attribute] .= $value; } else { // Set the character data of the node. $this->nodes[$path]["text"] .= $value; } } /** * 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. Existing content will be overwritten. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node. * @param string $value String containing the content to be set. * @see get_content(), evaluate() */ function set_content ( $path, $value ) { // Check whether it's an attribute node. if ( preg_match('/'."\\/attribute::".'/', $path) ) { // Get the path to the attribute node's parent. $parent = $this->prestr($path, "/attribute::"); // Get the parent node. $parent = $this->nodes[$parent]; // Get the name of the attribute. $attribute = $this->afterstr($path, "/attribute::"); // Set the attribute. $parent["attributes"][$attribute] = $value; } else { // Set the character data of the node. $this->nodes[$path]["text"] = $value; } } /** * Retrieves the content of a node. * * This method retrieves the content of a 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. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node, from which the * content should be retrieved. * @return string The returned string contains either the value or the * character data of the node. * @see set_content(), evaluate() */ function get_content ( $path ) { // Check whether it's an attribute node. if ( preg_match('/'."\\/attribute::".'/', $path) ) { // Get the path to the attribute node's parent. $parent = $this->prestr($path, "/attribute::"); // Get the parent node. $parent = $this->nodes[$parent]; // Get the name of the attribute. $attribute = $this->afterstr($path, "/attribute::"); // Get the attribute. $attribute = $parent["attributes"][$attribute]; // Return the value of the attribute. return $attribute; } else { // Return the cdata of the node. return stripslashes($this->nodes[$path]["text"]); } } /** * Add attributes to a node. * * This method adds attributes to a node. Existing attributes will not be * overwritten. * * @access public * @author Michael P. Mehl * @param string $path 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 set_content(), get_content() */ function add_attributes ( $path, $attributes ) { // Add the attributes to the node. $this->nodes[$path]["attributes"] = array_merge($attributes, $this->nodes[$path]["attributes"]); } /** * Sets the attributes of a node. * * This method sets the attributes of a node and overwrites all existing * attributes by doing this. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node, the attributes * of which should be set. * @param array $attributes Associative array containing the new * attributes for the node. * @see set_content(), get_content() */ function set_attributes ( $path, $attributes ) { // Set the attributes of the node. $this->nodes[$path]["attributes"] = $attributes; } /** * Retrieves a list of all attributes of a node. * * This method retrieves a list of all attributes of the node specified in * the argument. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node, from which the * list of attributes should be retrieved. * @return array The returned associative array contains the all * attributes of the specified node. * @see get_content(), $nodes, $ids */ function get_attributes ( $path ) { // Return the attributes of the node. return $this->nodes[$path]["attributes"]; } /** * Retrieves the name of a document node. * * This method retrieves the name of document node specified in the * argument. * * @access public * @author Michael P. Mehl * @param string $path Full document path of the node, from which the * name should be retrieved. * @return string The returned array contains the name of the specified * node. * @see get_content(), $nodes, $ids */ function get_name ( $path ) { // Return the name of the node. return $this->nodes[$path]["name"]; } /** * 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. * * @access public * @author Michael P. Mehl * @param string $path 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. * @see $nodes, $ids */ function evaluate ( $path, $context = "" ) { // Remove slashes and quote signs. $path = stripslashes($path); $path = str_replace("\"", "", $path); $path = str_replace("'", "", $path); // Split the paths into different paths. $paths = $this->split_paths($path); // Create an empty set to save the result. $result = array(); // Run through all paths. foreach ( $paths as $path ) { // Trim the path. $path = trim($path); // Save the current path. $this->xpath = $path; // Convert all entities. $path = strtr($path, array_flip($this->entities)); // Split the path at every slash. $steps = $this->split_steps($path); // Check whether the first element is empty. if ( empty($steps[0]) ) { // Remove the first and empty element. array_shift($steps); } // Start to evaluate the steps. $nodes = $this->evaluate_step($context, $steps); // Remove duplicated nodes. $nodes = array_unique($nodes); // Add the nodes to the result set. $result = array_merge($result, $nodes); } // Return the result. return $result; } /** * 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. * * @access private * @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 handle_end_element(), handle_character_data(), $nodes, $ids */ function handle_start_element ( $parser, $name, $attributes ) { // Add a node. $this->path = $this->add_node($this->path, $name); // Set the attributes. $this->set_attributes($this->path, $attributes); } /** * Handles closing XML tags while parsing. * * While parsing a XML document for each closing tag this method is * called. * * @access private * @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 handle_start_element(), handle_character_data(), $nodes, $ids */ function handle_end_element ( $parser, $name ) { // 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. * * @access private * @author Michael P. Mehl * @param int $parser Handler for accessing the current XML parser. * @param string $text Character data found in the document. * @see handle_start_element(), handle_end_element(), $nodes, $ids */ function handle_character_data ( $parser, $text ) { // Replace entities. $text = strtr($text, $this->entities); // Save the text. $this->add_content($this->path, addslashes(trim($text))); } /** * Splits an XPath expression into its different expressions. * * This method splits an XPath expression. Each expression can consists of * list of expression being separated from each other by a | character. * * @access private * @author Michael P. Mehl * @param string $expression The complete expression to be splitted * into its different expressions. * @return array The array returned from this method contains a list * of all expressions found in the expression passed to this * method as a parameter. * @see evalute() */ function split_paths ( $expression ) { // Create an empty array. $paths = array(); // Save the position of the slash. $position = -1; // Run through the expression. do { // Search for a slash. $position = $this->search_string($expression, "|"); // Check whether a | was found. if ( $position >= 0 ) { // Get the left part of the expression. $left = substr($expression, 0, $position); $right = substr($expression, $position + 1); // Add the left value to the steps. $paths[] = $left; // Reduce the expression to the right part. $expression = $right; } } while ( $position > -1 ); // Add the remaing expression to the list of steps. $paths[] = $expression; // Return the steps. return $paths; } /** * Splits an XPath expression into its different steps. * * This method splits an XPath expression. Each expression can consists of * list of steps being separated from each other by a / character. * * @access private * @author Michael P. Mehl * @param string $expression The complete expression to be splitted * into its different steps. * @return array The array returned from this method contains a list * of all steps found in the expression passed to this * method as a parameter. * @see evalute() */ function split_steps ( $expression ) { // Create an empty array. $steps = array(); // Replace a double slashes, because they'll cause problems otherwise. $expression = str_replace("//@", "/descendant::*/@", $expression); $expression = str_replace("//", "/descendant::", $expression); // Save the position of the slash. $position = -1; // Run through the expression. do { // Search for a slash. $position = $this->search_string($expression, "/"); // Check whether a slash was found. if ( $position >= 0 ) { // Get the left part of the expression. $left = substr($expression, 0, $position); $right = substr($expression, $position + 1); // Add the left value to the steps. $steps[] = $left; // Reduce the expression to the right part. $expression = $right; } } while ( $position > -1 ); // Add the remaing expression to the list of steps. $steps[] = $expression; // Return the steps. return $steps; } /** * 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. * * @access private * @author Michael P. Mehl * @param string $step String containing a step of an XPath expression. * @param string $node 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 evaluate_step() */ function get_axis ( $step, $node ) { // Create an array to save the axis information. $axis = array( "axis" => "", "node-test" => "", "predicate" => array() ); // Check whether there are predicates. if ( preg_match('/'."\[".'/', $step) ) { // Get the predicates. $predicates = substr($step, strpos($step, "[")); // Reduce the step. $step = $this->prestr($step, "["); // Try to split the predicates. $predicates = str_replace("][", "]|[", $predicates); $predicates = explode("|", $predicates); // Run through all predicates. foreach ( $predicates as $predicate ) { // Remove the brackets. $predicate = substr($predicate, 1, strlen($predicate) - 2); // Add the predicate to the list of predicates. $axis["predicate"][] = $predicate; } } // Check whether the axis is given in plain text. if ( $this->search_string($step, "::") > -1 ) { // Split the step to extract axis and node-test. $axis["axis"] = $this->prestr($step, "::"); $axis["node-test"] = $this->afterstr($step, "::"); } else { // Check whether the step is empty. if ( empty($step) ) { // Set it to the default value. $step = "."; } // Check whether is an abbreviated syntax. if ( $step == "*" ) { // Use the child axis and select all children. $axis["axis"] = "child"; $axis["node-test"] = "*"; } elseif ( preg_match('/'."\(".'/', $step) ) { // Check whether it's a function. if ( $this->is_function($this->prestr($step, "(")) ) { // Get the position of the first bracket. $start = strpos($step, "("); $end = strpos($step, ")", $start); // Get everything before, between and after the brackets. $before = substr($step, 0, $start); $between = substr($step, $start + 1, $end - $start - 1); $after = substr($step, $end + 1); // Trim each string. $before = trim($before); $between = trim($between); $after = trim($after); // Save the evaluated function. $axis["axis"] = "function"; $axis["node-test"] = $this->evaluate_function($before, $between, $node); } else { // Use the child axis and a function. $axis["axis"] = "child"; $axis["node-test"] = $step; } } elseif ( preg_match('/'."^@".'/i', $step) ) { // Use the attribute axis and select the attribute. $axis["axis"] = "attribute"; $axis["node-test"] = substr($step, 1); } elseif ( preg_match('/'."\]$".'/i', $step) ) { // Use the child axis and select a position. $axis["axis"] = "child"; $axis["node-test"] = substr($step, strpos($step, "[")); } elseif ( $step == "." ) { // Select the self axis. $axis["axis"] = "self"; $axis["node-test"] = "*"; } elseif ( $step == ".." ) { // Select the parent axis. $axis["axis"] = "parent"; $axis["node-test"] = "*"; } elseif ( preg_match('/'."^[a-zA-Z0-9\-_]+$".'/', $step) ) { // Select the child axis and the child. $axis["axis"] = "child"; $axis["node-test"] = $step; } else { // Use the child axis and a name. $axis["axis"] = "child"; $axis["node-test"] = $step; } } // Check whether it's a valid axis. if ( !in_array($axis["axis"], array_merge($this->axes, array("function"))) ) { // Display an error message. $this->display_error("While parsing an XPath expression, in ". "the step \"%s\" the invalid axis \"%s\" was found.", str_replace($step, "".$step."", $this->xpath),# $axis["axis"]); } // Return the axis information. return $axis; } /** * 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. * * @access private * @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 evaluate_step() */ function search_string ( $term, $expression ) { // Create a new counter for the brackets. $brackets = 0; // Run through the string. for ( $i = 0; $i < strlen($term); $i++ ) { // Get the character at the position of the string. $character = substr($term, $i, 1); // Check whether it's a breacket. if ( ( $character == "(" ) || ( $character == "[" ) ) { // Increase the number of brackets. $brackets++; } elseif ( ( $character == ")" ) || ( $character == "]" ) ) { // Decrease the number of brackets. $brackets--; } elseif ( $brackets == 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 ( $brackets != 0 ) { // Display an error message. $this->display_error("While parsing an XPath expression, in the ". "predicate \"%s\", there was an invalid number of brackets.", str_replace($term, "".$term."", $this->xpath)); } // Nothing was found. return (-1); } /** * Checks for a valid function name. * * This method check whether an expression contains a valid name of an * XPath function. * * @access private * @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 is_function ( $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; } } /** * Evaluates a step of an XPath expression. * * This method tries to evaluate a step from an XPath expression at a * specific context. * * @access private * @author Michael P. Mehl * @param string $context Full document path of the context from * which starting the step should be evaluated. * @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 evaluate_step ( $context, $steps ) { // Create an empty array for saving the nodes found. $nodes = array(); // Check whether the context is an array of contexts. if ( is_array($context) ) { // Run through the array. foreach ( $context as $path ) { // Call this method for this single path. $nodes = array_merge($nodes, $this->evaluate_step($path, $steps)); } } else { // Get this step. $step = array_shift($steps); // Create an array to save the new contexts. $contexts = array(); // Get the axis of the current step. $axis = $this->get_axis($step, $context); // 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 = "handle_axis_".str_replace("-", "_", $axis["axis"]); // Check whether the axis handler is defined. if ( !method_exists(&$this, $method) ) { // Display an error message. $this->display_error("While parsing an XPath expression, ". "the axis \"%s\" could not be handled, because this ". "version does not support this axis.", $axis["axis"]); } // Perform an axis action. $contexts = call_user_method($method, &$this, $axis, $context); // Check whether there are predicates. if ( count($axis["predicate"]) > 0 ) { // Check whether each node fits the predicates. $contexts = $this->check_predicates($contexts, $axis["predicate"]); } } // Check whether there are more steps left. if ( count($steps) > 0 ) { // Continue the evaluation of the next steps. $nodes = $this->evaluate_step($contexts, $steps); } else { // Save the found contexts. $nodes = $contexts; } } // Return the nodes found. return $nodes; } /** * Evaluates an XPath function * * This method evaluates a given XPath function with its arguments on a * specific node of the document. * * @access private * @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 evaluate_function ( $function, $arguments, $node ) { // Remove whitespaces. $function = trim($function); $arguments = trim($arguments); // Create the name of the function handling function. $method = "handle_function_".str_replace("-", "_", $function); // Check whether the function handling function is available. if ( !method_exists(&$this, $method) ) { // Display an error message. $this->display_error("While parsing an XPath expression, ". "the function \"%s\" could not be handled, because this ". "version does not support this function.", $function); } // Return the result of the function. return call_user_method($method, &$this, $node, $arguments); } /** * Evaluates a predicate on a node. * * This method tries to evaluate a predicate on a given node. * * @access private * @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 evaluate_predicate ( $node, $predicate ) { // Set the default position and the type of the operator. $position = 0; $operator = ""; // Run through all operators and try to find them. foreach ( $this->operators as $expression ) { // Check whether a position was already found. if ( $position <= 0 ) { // Try to find the operator. $position = $this->search_string($predicate, $expression); // Check whether a operator was found. if ( $position > 0 ) { // Save the operator. $operator = $expression; // Check whether it's the equal operator. if ( $operator == "=" ) { // Also look for other operators containing the // equal sign. if ( $this->search_string($predicate, "!=") == ( $position - 1 ) ) { // Get the new position. $position = $this->search_string($predicate, "!="); // Save the new operator. $operator = "!="; } if ( $this->search_string($predicate, "<=") == ( $position - 1 ) ) { // Get the new position. $position = $this->search_string($predicate, "<="); // Save the new operator. $operator = "<="; } if ( $this->search_string($predicate, ">=") == ( $position - 1 ) ) { // Get the new position. $position = $this->search_string($predicate, ">="); // Save the new operator. $operator = ">="; } } } } } // Check whether the operator is a - sign. if ( $operator == "-" ) { // Check whether it's not a function containing a - in its name. foreach ( $this->functions as $function ) { // Check whether there's a - sign in the function name. if ( strpos($function,"-") !== false ) { // Get the position of the - in the function name. $sign = strpos($function, "-"); // Extract a substring from the predicate. $sub = substr($predicate, $position - $sign, strlen($function)); // Check whether it's the function. if ( $sub == $function ) { // Don't use the operator. $operator = ""; $position = -1; } } } } elseif ( $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; } } // Check whether an operator was found. if ( $position > 0 ) { // Get the left and the right part of the expression. $left = substr($predicate, 0, $position); $right = substr($predicate, $position + strlen($operator)); // Remove whitespaces. $left = trim($left); $right = trim($right); // Evaluate the left and the right part. $left = $this->evaluate_predicate($node, $left); $right = $this->evaluate_predicate($node, $right); // Check the kind of operator. switch ( $operator ) { case " or ": // Return the two results connected by an "or". return ( $left or $right ); case " and ": // Return the two results connected by an "and". return ( $left and $right ); case "=": // Compare the two results. return ( $left == $right ); case "!=": // Check whether the two results are not equal. return ( $left != $right ); case "<=": // Compare the two results. return ( $left <= $right ); case "<": // Compare the two results. return ( $left < $right ); case ">=": // Compare the two results. return ( $left >= $right ); case ">": // Compare the two results. return ( $left > $right ); case "+": // Return the result by adding one result to the other. return ( $left + $right ); case "-": // Return the result by decrease one result by the other. return ( $left - $right ); case "*": // Return a multiplication of the two results. return ( $left * $right ); case " div ": // Return a division of the two results. if ( $right == 0 ) { // Display an error message. $this->display_error("While parsing an XPath ". "predicate, a error due a division by zero ". "occured."); } else { // Return the result of the division. return ( $left / $right ); } break; case " mod ": // Return a modulo of the two results. return ( $left % $right ); } } // Check whether the predicate is a function. if ( preg_match('/'."\(".'/', $predicate) ) { // Get the position of the first bracket. $start = strpos($predicate, "("); $end = strpos($predicate, ")", $start); // 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); // Check whether there's something after the bracket. if ( !empty($after) ) { // Display an error message. $this->display_error("While parsing an XPath expression ". "there was found an error in the predicate \"%s\", ". "because after a closing bracket there was found ". "something unknown.", str_replace($predicate, "".$predicate."", $this->xpath)); } // Check whether it's a function. if ( empty($before) && empty($after) ) { // Evaluate the content of the brackets. return $this->evaluate_predicate($node, $between); } elseif ( $this->is_function($before) ) { // Return the evaluated function. return $this->evaluate_function($before, $between, $node); } else { // Display an error message. $this->display_error("While parsing a predicate in an XPath ". "expression, a function \"%s\" was found, which is not ". "yet supported by the parser.", str_replace($before, "".$before."", $this->xpath)); } } // Check whether the predicate is just a digit. if ( preg_match('/'."^[0-9]+(\.[0-9]+)?$".'/', $predicate) || preg_match('/'."^\.[0-9]+$".'/', $predicate) ) { // Return the value of the digit. return doubleval($predicate); } // Check whether it's an XPath expression. $result = $this->evaluate($predicate, $node); if ( count($result) > 0 ) { // Convert the array. $result = explode("|", implode("|", $result)); // Get the value of the first result. $value = $this->get_content($result[0]); // Return the value. return $value; } // Return the predicate as a string. return $predicate; } /** * Checks whether a node matches predicates. * * This method checks whether a list of nodes passed to this method match * a given list of predicates. * * @access private * @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 evaluate_step() */ function check_predicates ( $nodes, $predicates ) { // Create an empty set of nodes. $result = array(); // Run through all nodes. foreach ( $nodes as $node ) { // Create a variable whether to add this node to the node-set. $add = true; // Run through all predicates. foreach ( $predicates as $predicate ) { // Check whether the predicate is just an number. if ( preg_match('/'."^[0-9]+$".'/', $predicate) ) { // Enhance the predicate. $predicate .= "=position()"; } // Do the predicate check. $check = $this->evaluate_predicate($node, $predicate); // Check whether it's a string. if ( is_string($check) && ( ( $check == "" ) || ( $check == $predicate ) ) ) { // Set the result to false. $check = false; } // Check whether it's an integer. if ( is_int($check) ) { // Check whether it's the current position. if ( $check == $this->handle_function_position($node, "") ) { // Set it to true. $check = true; } else { // Set it to false. $check = false; } } // 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; } } // 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. * * @access private * @author Michael P. Mehl * @param string $context Full path of the node, which should be tested * for matching the node-test. * @param string $node_test 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 check_node_test ( $context, $node_test ) { // Check whether it's a function. if ( preg_match('/'."\(".'/', $node_test) ) { // Get the type of function to use. $function = $this->prestr($node_test, "("); // 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. if ( !empty($this->nodes[$context]["text"]) ) { // 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->display_error("While parsing an XPath ". "expression there was found an undefined ". "function called \"%s\".", str_replace($function, "".$function."", $this->xpath)); } } elseif ( $node_test == "*" ) { // Add this node to the node-set. return true; } elseif ( preg_match('/'."^[a-zA-Z0-9\-_]+".'/', $node_test) ) { // Check whether the node-test can be fulfilled. if ( $this->nodes[$context]["name"] == $node_test ) { // Add this node to the node-set. return true; } } else { // Display an error message. $this->display_error("While parsing the XPath expression \"%s\" ". "an empty and therefore invalid node-test has been found.", $this->xpath); } // Don't add this context. return false; } /** * Handles the XPath child axis. * * This method handles the XPath child axis. * * @access private * @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 handle_axis_child ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Get a list of all children. $children = $this->nodes[$context]["children"]; // Check whether there are children. if ( !empty($children) ) { // Run through all children. foreach ( $children as $child_name => $child_position ) { // Run through all childs with this name. for ( $i = 1; $i <= $child_position; $i++ ) { // Create the path of the child. $child = $context."/".$child_name."[".$i."]"; // Check whether if ( $this->check_node_test($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. * * @access private * @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 handle_axis_parent ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Check whether the parent matches the node-test. if ( $this->check_node_test($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. * * @access private * @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 handle_axis_attribute ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Check whether all nodes should be selected. if ( $axis["node-test"] == "*" ) { // Check whether there are attributes. if ( count($this->nodes[$context]["attributes"]) > 0 ) { // Run through the attributes. foreach ( $this->nodes[$context]["attributes"] as $key => $value ) { // Add this node to the node-set. $nodes[] = $context."/attribute::".$key; } } } elseif ( !empty($this->nodes[$context]["attributes"] [$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. * * @access private * @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 handle_axis_self ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Check whether the context match the node-test. if ( $this->check_node_test($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. * * @access private * @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 handle_axis_descendant ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Check whether the current node has children. if ( count($this->nodes[$context]["children"]) > 0 ) { // Get a list of children. $children = $this->nodes[$context]["children"]; // Run through all children. foreach ( $children as $child_name => $child_position ) { // Run through all children of this name. for ( $i = 1; $i <= $child_position; $i++ ) { // Create the full path for the children. $child = $context."/".$child_name."[".$i."]"; // Check whether the child matches the node-test. if ( $this->check_node_test($child, $axis["node-test"]) ) { // Add the child to the list of nodes. $nodes[] = $child; } // Recurse to the next level. $nodes = array_merge($nodes, $this->handle_axis_descendant($axis, $child)); } } } // Return the nodeset. return $nodes; } /** * Handles the XPath ancestor axis. * * This method handles the XPath ancestor axis. * * @access private * @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 handle_axis_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->check_node_test($parent, $axis["node-test"]) ) { // Add the parent to the list of nodes. $nodes[] = $parent; } // Handle all other ancestors. $nodes = array_merge($nodes, $this->handle_axis_ancestor($axis, $parent)); } // Return the nodeset. return $nodes; } /** * Handles the XPath namespace axis. * * This method handles the XPath namespace axis. * * @access private * @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 handle_axis_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. * * @access private * @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 handle_axis_following ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Get the current document position. $position = $this->nodes[$context]["document-position"]; // Create a flag, whether we already found the context node. $found = false; // Run through all nodes of the document. foreach ( $this->nodes as $node => $data ) { // Check whether the context node has already been found. if ( $found ) { // Check whether the position is correct. if ( $this->nodes[$node]["document-position"] == $position ) { // Check whether the node fits the node-test. if ( $this->check_node_test($node, $axis["node-test"]) ) { // Add the node to the list of nodes. $nodes[] = $node; } } } // Check whether this is the context node. if ( $node == $context ) { // After this we'll look for more nodes. $found = true; } } // Return the nodeset. return $nodes; } /** * Handles the XPath preceding axis. * * This method handles the XPath preceding axis. * * @access private * @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 handle_axis_preceding ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Get the current document position. $position = $this->nodes[$context]["document-position"]; // Create a flag, whether we already found the context node. $found = true; // Run through all nodes of the document. foreach ( $this->nodes as $node => $data ) { // Check whether this is the context node. if ( $node == $context ) { // After this we won't look for more nodes. $found = false; } // Check whether the context node has already been found. if ( $found ) { // Check whether the position is correct. if ( $this->nodes[$node]["document-position"] == $position ) { // Check whether the node fits the node-test. if ( $this->check_node_test($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. * * @access private * @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 handle_axis_following_sibling ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Get all children from the parent. $siblings = $this->handle_axis_child($axis, $this->nodes[$context]["parent"]); // Create a flag whether the context node was already found. $found = false; // Run through all siblings. foreach ( $siblings as $sibling ) { // Check whether the context node was already found. if ( $found ) { // Check whether the sibling is a real sibling. if ( $this->nodes[$sibling]["name"] == $this->nodes[$context]["name"] ) { // Check whether the sibling matches the node-test. if ( $this->check_node_test($sibling, $axis["node-test"]) ) { // Add the sibling to the list of nodes. $nodes[] = $sibling; } } } // Check whether this is the 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. * * @access private * @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 handle_axis_preceding_sibling ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Get all children from the parent. $siblings = $this->handle_axis_child($axis, $this->nodes[$context]["parent"]); // Create a flag whether the context node was already found. $found = true; // Run through all siblings. foreach ( $siblings as $sibling ) { // Check whether this is the context node. if ( $sibling == $context ) { // Don't continue looking for other siblings. $found = false; } // Check whether the context node was already found. if ( $found ) { // Check whether the sibling is a real sibling. if ( $this->nodes[$sibling]["name"] == $this->nodes[$context]["name"] ) { // Check whether the sibling matches the node-test. if ( $this->check_node_test($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. * * @access private * @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 handle_axis_descendant_or_self ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Read the nodes. $nodes = array_merge( $this->handle_axis_descendant($axis, $context), $this->handle_axis_self($axis, $context)); // Return the nodeset. return $nodes; } /** * Handles the XPath ancestor-or-self axis. * * This method handles the XPath ancestor-or-self axis. * * @access private * @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 handle_axis_ancestor_or_self ( $axis, $context ) { // Create an empty node-set. $nodes = array(); // Read the nodes. $nodes = array_merge( $this->handle_axis_ancestor($axis, $context), $this->handle_axis_self($axis, $context)); // Return the nodeset. return $nodes; } /** * Handles the XPath function last. * * This method handles the XPath function last. * * @access private * @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 handle_function_last ( $node, $arguments ) { // Calculate the size of the context. $parent = $this->nodes[$node]["parent"]; $children = $this->nodes[$parent]["children"]; $context = $children[$this->nodes[$node]["name"]]; // Return the size. return $context; } /** * Handles the XPath function position. * * This method handles the XPath function position. * * @access private * @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 handle_function_position ( $node, $arguments ) { // return the context-position. return $this->nodes[$node]["context-position"]; } /** * Handles the XPath function count. * * This method handles the XPath function count. * * @access private * @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 handle_function_count ( $node, $arguments ) { // Evaluate the argument of the method as an XPath and return // the number of results. return count($this->evaluate($arguments, $node)); } /** * Handles the XPath function id. * * This method handles the XPath function id. * * @access private * @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 handle_function_id ( $node, $arguments ) { // 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 document node. foreach ( $this->nodes as $node => $position ) { // 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. * * @access private * @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 handle_function_name ( $node, $arguments ) { // Return the name of the node. return $this->nodes[$node]["name"]; } /** * Handles the XPath function string. * * This method handles the XPath function string. * * @access private * @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 handle_function_string ( $node, $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); // 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->evaluate($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. * * @access private * @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 handle_function_concat ( $node, $arguments ) { // 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->evaluate_predicate($node, $arguments[$i]); } // 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. * * @access private * @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 handle_function_starts_with ( $node, $arguments ) { // Get the arguments. $first = trim($this->prestr($arguments, ",")); $second = trim($this->afterstr($arguments, ",")); // Evaluate each argument. $first = $this->evaluate_predicate($node, $first); $second = $this->evaluate_predicate($node, $second); // Check whether the first string starts with the second one. if ( preg_match('/'."^".$second.'/', $first) ) { // Return true. return true; } else { // Return false. return false; } } /** * Handles the XPath function contains. * * This method handles the XPath function contains. * * @access private * @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 handle_function_contains ( $node, $arguments ) { // Get the arguments. $first = trim($this->prestr($arguments, ",")); $second = trim($this->afterstr($arguments, ",")); // Evaluate each argument. $first = $this->evaluate_predicate($node, $first); $second = $this->evaluate_predicate($node, $second); // 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 substring-before. * * This method handles the XPath function substring-before. * * @access private * @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 handle_function_substring_before ( $node, $arguments ) { // Get the arguments. $first = trim($this->prestr($arguments, ",")); $second = trim($this->afterstr($arguments, ",")); // Evaluate each argument. $first = $this->evaluate_predicate($node, $first); $second = $this->evaluate_predicate($node, $second); // Return the substring. return $this->prestr(strval($first), strval($second)); } /** * Handles the XPath function substring-after. * * This method handles the XPath function substring-after. * * @access private * @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 handle_function_substring_after ( $node, $arguments ) { // Get the arguments. $first = trim($this->prestr($arguments, ",")); $second = trim($this->afterstr($arguments, ",")); // Evaluate each argument. $first = $this->evaluate_predicate($node, $first); $second = $this->evaluate_predicate($node, $second); // Return the substring. return $this->afterstr(strval($first), strval($second)); } /** * Handles the XPath function substring. * * This method handles the XPath function substring. * * @access private * @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 handle_function_substring ( $node, $arguments ) { // 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->evaluate_predicate($node, $arguments[$i]); } // 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. * * @access private * @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 handle_function_string_length ( $node, $arguments ) { // Trim the argument. $arguments = trim($arguments); // Evaluate the argument. $arguments = $this->evaluate_predicate($node, $arguments); // Return the length of the string. return strlen(strval($arguments)); } /** * Handles the XPath function translate. * * This method handles the XPath function translate. * * @access private * @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 handle_function_translate ( $node, $arguments ) { // 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->evaluate_predicate($node, $arguments[$i]); } // Return the translated string. return strtr($arguments[0], $arguments[1], $arguments[2]); } /** * Handles the XPath function boolean. * * This method handles the XPath function boolean. * * @access private * @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 handle_function_boolean ( $node, $arguments ) { // 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->evaluate($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. * * @access private * @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 handle_function_not ( $node, $arguments ) { // Trim the arguments. $arguments = trim($arguments); // Return the negative value of the content of the brackets. return !$this->evaluate_predicate($node, $arguments); } /** * Handles the XPath function true. * * This method handles the XPath function true. * * @access private * @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 handle_function_true ( $node, $arguments ) { // Return true. return true; } /** * Handles the XPath function false. * * This method handles the XPath function false. * * @access private * @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 handle_function_false ( $node, $arguments ) { // Return false. return false; } /** * Handles the XPath function lang. * * This method handles the XPath function lang. * * @access private * @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 handle_function_lang ( $node, $arguments ) { // 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. * * @access private * @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 handle_function_number ( $node, $arguments ) { // Check the type of argument. if ( preg_match('/'."^[0-9]+(\.[0-9]+)?$".'/', $arguments) || preg_match('/'."^\.[0-9]+$".'/', $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. * * @access private * @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 handle_function_sum ( $node, $arguments ) { // Trim the arguments. $arguments = trim($arguments); // Evaluate the arguments as an XPath expression. $results = $this->evaluate($arguments, $node); // Create a variable to save the sum. $sum = 0; // Run through all results. foreach ( $results as $result ) { // Get the value of the node. $result = $this->get_content($result); // Add it to the sum. $sum += doubleval($result); } // Return the sum. return $sum; } /** * Handles the XPath function floor. * * This method handles the XPath function floor. * * @access private * @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 handle_function_floor ( $node, $arguments ) { // Trim the arguments. $arguments = trim($arguments); // 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. * * @access private * @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 handle_function_ceiling ( $node, $arguments ) { // Trim the arguments. $arguments = trim($arguments); // 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. * * @access private * @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 handle_function_round ( $node, $arguments ) { // Trim the arguments. $arguments = trim($arguments); // Convert the arguments to a number. $arguments = doubleval($arguments); // Return the result return round($arguments); } /** * Handles the XPath function text. * * This method handles the XPath function text. * * @access private * @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 handle_function_text ( $node, $arguments ) { // Return the character data of the node. return $this->nodes[$node]["text"]; } /** * Retrieves a substring before a delimiter. * * This method retrieves everything from a string before a given delimiter, * not including the delimiter. * * @access private * @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 ) { // Return the substring. return substr($string, 0, strlen($string) - strlen(@strstr($string,$delimiter))); } /** * Retrieves a substring after a delimiter. * * This method retrieves everything from a string after a given delimiter, * not including the delimiter. * * @access private * @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 ) { // Return the substring. return substr($string, strpos($string, $delimiter) + strlen($delimiter)); } /** * Displays an error message. * * This method displays an error messages and stops the execution of the * script. This method is called exactly in the same way as the printf * function. The first argument contains the message and additional * arguments of various types may be passed to this method to be inserted * into the message. * * @access private * @author Michael P. Mehl * @param string $message Error message to be displayed. */ function display_error ( $message ) { // Check whether more than one argument was given. if ( func_num_args() > 1 ) { // Read all arguments. $arguments = func_get_args(); // Create a new string for the inserting command. $command = "\$message = sprintf(\$message, "; // Run through the array of arguments. for ( $i = 1; $i < sizeof($arguments); $i++ ) { // Add the number of the argument to the command. $command .= "\$arguments[".$i."], "; } // Replace the last separator. $command = preg_replace('/'.", $".'/i', ");", $command); // Execute the command. eval($command); } // Display the error message. echo "phpXML error: ".$message; // End the execution of this script. exit; } } ?>