[pLog-svn] r2951 - plog/trunk/class/xml

oscar at devel.lifetype.net oscar at devel.lifetype.net
Mon Feb 13 20:57:37 GMT 2006


Author: oscar
Date: 2006-02-13 20:57:36 +0000 (Mon, 13 Feb 2006)
New Revision: 2951

Removed:
   plog/trunk/class/xml/XPath.class.php
Log:
removed the XPath class, which is anyway not used anymore


Deleted: plog/trunk/class/xml/XPath.class.php
===================================================================
--- plog/trunk/class/xml/XPath.class.php	2006-02-13 17:50:40 UTC (rev 2950)
+++ plog/trunk/class/xml/XPath.class.php	2006-02-13 20:57:36 UTC (rev 2951)
@@ -1,5731 +0,0 @@
-<?php
-
-/**
- * \ingroup XML
- *
- * Php.XPath
- *
- * +======================================================================================================+
- * | A php class for searching an XML document using XPath, and making modifications using a DOM 
- * | style API. Does not require the DOM XML PHP library. 
- * |
- * +======================================================================================================+
- * | What Is XPath:
- * | --------------
- * | - "What SQL is for a relational database, XPath is for an XML document." -- Sam Blum
- * | - "The primary purpose of XPath is to address parts of an XML document. In support of this 
- * |    primary purpose, it also provides basic facilities for manipulting it." -- W3C
- * | 
- * | XPath in action and a very nice intro is under:
- * |    http://www.zvon.org/xxl/XPathTutorial/General/examples.html
- * | Specs Can be found under:
- * |    http://www.w3.org/TR/xpath     W3C XPath Recommendation 
- * |    http://www.w3.org/TR/xpath20   W3C XPath Recommendation 
- * |
- * | NOTE: Most of the XPath-spec has been realized, but not all. Usually this should not be
- * |       problem as the missing part is either rarely used or it's simpler to do with PHP itself.
- * +------------------------------------------------------------------------------------------------------+
- * | Requires PHP version  4.0.5 and up
- * +------------------------------------------------------------------------------------------------------+
- * | Main Active Authors:
- * | --------------------
- * | Nigel Swinson <nigelswinson at users.sourceforge.net>
- * |   Started around 2001-07, saved phpxml from near death and renamed to Php.XPath
- * |   Restructured XPath code to stay in line with XPath spec.
- * | Sam Blum <bs_php at infeer.com>
- * |   Started around 2001-09 1st major restruct (V2.0) and testbench initiator.   
- * |   2nd (V3.0) major rewrite in 2002-02
- * | Daniel Allen <bigredlinux at yahoo.com>
- * |   Started around 2001-10 working to make Php.XPath adhere to specs 
- * | Main Former Author: Michael P. Mehl <mpm at phpxml.org>
- * |   Inital creator of V 1.0. Stoped activities around 2001-03        
- * +------------------------------------------------------------------------------------------------------+
- * | Code Structure:
- * | --------------_
- * | The class is split into 3 main objects. To keep usability easy all 3 
- * | objects are in this file (but may be split in 3 file in future).
- * |   +-------------+ 
- * |   |  XPathBase  | XPathBase holds general and debugging functions. 
- * |   +------+------+
- * |          v      
- * |   +-------------+ XPathEngine is the implementation of the W3C XPath spec. It contains the 
- * |   | XPathEngine | XML-import (parser), -export  and can handle xPathQueries. It's a fully 
- * |   +------+------+ functional class but has no functions to modify the XML-document (see following).
- * |          v      
- * |   +-------------+ 
- * |   |    XPath    | XPath extends the functionality with actions to modify the XML-document.
- * |   +-------------+ We tryed to implement a DOM - like interface.
- * +------------------------------------------------------------------------------------------------------+
- * | Usage:
- * | ------
- * | Scroll to the end of this php file and you will find a short sample code to get you started
- * +------------------------------------------------------------------------------------------------------+
- * | Glossary:
- * | ---------
- * | To understand how to use the functions and to pass the right parameters, read following:
- * |     
- * | Document: (full node tree, XML-tree)
- * |     After a XML-source has been imported and parsed, it's stored as a tree of nodes sometimes 
- * |     refered to as 'document'.
- * |     
- * | AbsoluteXPath: (xPath, xPathSet)
- * |     A absolute XPath is a string. It 'points' to *one* node in the XML-document. We use the
- * |     term 'absolute' to emphasise that it is not an xPath-query (see xPathQuery). A valid xPath 
- * |     has the form like '/AAA[1]/BBB[2]/CCC[1]'. Usually functions that require a node (see Node) 
- * |     will also accept an abs. XPath.
- * |     
- * | Node: (node, nodeSet, node-tree)
- * |     Some funtions require or return a node (or a whole node-tree). Nodes are only used with the 
- * |     XPath-interface and have an internal structure. Every node in a XML document has a unique 
- * |     corresponding abs. xPath. That's why public functions that accept a node, will usually also 
- * |     accept a abs. xPath (a string) 'pointing' to an existing node (see absolutXPath).
- * |     
- * | XPathQuery: (xquery, query)
- * |     A xPath-query is a string that is matched against the XML-document. The result of the match 
- * |     is a xPathSet (vector of xPath's). It's always possible to pass a single absoluteXPath 
- * |     instead of a xPath-query. A valid xPathQuery could look like this:
- * |     '//XXX/*[contains(., "foo")]/..' (See the link in 'What Is XPath' to learn more).
- * |     
- * |     
- * +------------------------------------------------------------------------------------------------------+
- * | Internals:
- * | ----------
- * | - The Node Tree
- * |   -------------
- * | A central role of the package is how the XML-data is stored. The whole data is in a node-tree.
- * | A node can be seen as the equvalent to a tag in the XML soure with some extra info.
- * | For instance the following XML 
- * |                        <AAA foo="x">***<BBB/><CCC/>**<BBB/>*</AAA>
- * | Would produce folowing node-tree:
- * |                              'super-root'      <-- $nodeRoot (Very handy)  
- * |                                    |                                           
- * |             'depth' 0            AAA[1]        <-- top node. The 'textParts' of this node would be
- * |                                /   |   \                     'textParts' => array('***','','**','*')
- * |             'depth' 1     BBB[1] CCC[1] BBB[2]               (NOTE: Is always size of child nodes+1)
- * | - The Node
- * |   --------
- * | The node itself is an structure desiged mainly to be used in connection with the interface of PHP.XPath.
- * | That means it's possible for functions to return a sub-node-tree that can be used as input of an other 
- * | PHP.XPath function.
- * | 
- * | The main structure of a node is:
- * |   $node = array(
- * |     'name'        => '',      # The tag name. E.g. In <FOO bar="aaa"/> it would be 'FOO'
- * |     'attributes'  => array(), # The attributes of the tag E.g. In <FOO bar="aaa"/> it would be array('bar'=>'aaa')
- * |     'textParts'   => array(), # Array of text parts surrounding the children E.g. <FOO>aa<A>bb<B/>cc</A>dd</FOO> -> array('aa','bb','cc','dd')
- * |     'childNodes'  => array(), # Array of refences (pointers) to child nodes.
- * |     
- * | For optimisation reasions some additional data is stored in the node too:
- * |     'parentNode'  => NULL     # Reference (pointer) to the parent node (or NULL if it's 'super root')
- * |     'depth'       => 0,       # The tag depth (or tree level) starting with the root tag at 0.
- * |     'pos'         => 0,       # Is the zero-based position this node has in the parent's 'childNodes'-list.
- * |     'contextPos'  => 1,       # Is the one-based position this node has by counting the siblings tags (tags with same name)
- * |     'xpath'       => ''       # Is the abs. XPath to this node.
- * |     'generated_id'=> ''       # The id returned for this node by generate-id() (attribute and text nodes not supported)
- * | 
- * | - The NodeIndex
- * |   -------------
- * | Every node in the tree has an absolute XPath. E.g '/AAA[1]/BBB[2]' the $nodeIndex is a hash array
- * | to all the nodes in the node-tree. The key used is the absolute XPath (a string).
- * |    
- * +------------------------------------------------------------------------------------------------------+
- * | License:
- * | --------
- * | 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 <phpXML/>. 
- * | 
- * | 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.
- * |
- * | Contributor(s): N.Swinson / S.Blum / D.Allen
- * | 
- * | Alternatively, the contents of this file may be used under the terms of either of the GNU 
- * | General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public 
- * | License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the 
- * | LGPL License are applicable instead of those above.  If you wish to allow use of your version 
- * | of this file only under the terms of the GPL or the LGPL License and not to allow others to 
- * | use your version of this file under the MPL, indicate your decision by deleting the 
- * | provisions above and replace them with the notice and other provisions required by the 
- * | GPL or the LGPL License.  If you do not delete the provisions above, a recipient may use 
- * | your version of this file under either the MPL, the GPL or the LGPL License. 
- * | 
- * +======================================================================================================+
- *
- * @author  S.Blum / N.Swinson / D.Allen / (P.Mehl)
- * @link    http://sourceforge.net/projects/phpxpath/
- * @version 3.4
- */
-class XPathBase {
-  var $_lastError;
-  
-  // As debugging of the xml parse is spread across several functions, we need to make this a member.
-  var $bDebugXmlParse = FALSE;
-
-  // Used to help navigate through the begin/end debug calls
-  var $iDebugNextLinkNumber = 1;
-  var $aDebugOpenLinks = array();
-
-  /**
-   * Constructor
-   */
-  function XPathBase() {
-    # $this->bDebugXmlParse = TRUE;
-    $this->properties['verboseLevel'] = 0;  // 0=silent, 1 and above produce verbose output (an echo to screen). 
-    
-    if (!isSet($_ENV)) {  // Note: $_ENV introduced in 4.1.0. In earlier versions, use $HTTP_ENV_VARS.
-      $_ENV = $GLOBALS['HTTP_ENV_VARS'];
-    }
-    
-    // Windows 95/98 do not support file locking. Detecting OS (Operation System) and setting the 
-    // properties['OS_supports_flock'] to FALSE if win 95/98 is detected. 
-    // This will surpress the file locking error reported from win 98 users when exportToFile() is called.
-    // May have to add more OS's to the list in future (Macs?).
-    // ### Note that it's only the FAT and NFS file systems that are really a problem.  NTFS and
-    // the latest php libs do support flock()
-    $_ENV['OS'] = isSet($_ENV['OS']) ? $_ENV['OS'] : 'Unknown OS';
-    switch ($_ENV['OS']) { 
-      case 'Windows_95':
-      case 'Windows_98':
-      case 'Unknown OS':
-        // should catch Mac OS X compatible environment 
-        if (preg_match('/Darwin/',$_SERVER['SERVER_SOFTWARE'])) { 
-           // fall-through 
-        } else { 
-           $this->properties['OS_supports_flock'] = FALSE; 
-           break; 
-        }
-      default:
-        $this->properties['OS_supports_flock'] = TRUE;
-    }
-  }
-  
-  
-  /**
-   * Resets the object so it's able to take a new xml sting/file
-   *
-   * Constructing objects is slow.  If you can, reuse ones that you have used already
-   * by using this reset() function.
-   */
-  function reset() {
-    $this->_lastError   = '';
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathBase                    ------  Helpers  ------                                    
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * This method checks the right amount and match of brackets
-   *
-   * @param     $term (string) String in which is checked.
-   * @return          (bool)   TRUE: OK / FALSE: KO  
-   */
-  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 -- BUT the search-string must be located *outside* of any brackets.
-   *
-   * 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.
-   *
-   * @param     $term       (string) String in which the search shall take place.
-   * @param     $expression (string) 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.
-   */
-  function _searchString($term, $expression) {
-    $bracketCounter = 0; // Record where we are in the brackets. 
-    $leng = strlen($term);
-    $exprLeng = strlen($expression);
-    for ($i=0; $i<$leng; $i++) {
-      $char = $term[$i];
-      if ($char=='(' || $char=='[') {
-        $bracketCounter++;
-        continue;
-      }
-      elseif ($char==')' || $char==']') {
-        $bracketCounter--;
-      }
-      if ($bracketCounter == 0) {
-        // Check whether we can find the expression at this index.
-        if (substr($term, $i, $exprLeng) == $expression) return $i;
-      }
-    }
-    // Nothing was found.
-    return (-1);
-  }
-  
-  /**
-   * Split a string by a searator-string -- BUT the separator-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     $separator  (string) String that should be searched.
-   * @param     $term       (string) 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. 
-    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 determine 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 before 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;
-  }
-
-  /**
-   * Split a string at it's groups, ie bracketed expressions
-   * 
-   * Returns an array of strings, when concatenated together would produce the original
-   * string.  ie a(b)cde(f)(g) would map to:
-   * array ('a', '(b)', cde', '(f)', '(g)')
-   *
-   * @param     $string  (string) The string to process
-   * @param     $open    (string) The substring for the open of a group
-   * @param     $close   (string) The substring for the close of a group
-   * @return             (array)  The parsed string, see above
-   */
-  function _getEndGroups($string, $open='[', $close=']') {
-    // 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();
-    do { // BEGIN try block
-      // Check if we have both an open and a close tag      
-      if (empty($open) and empty($close)) { // no separator found so end now
-        $resultArr[] = $string;
-        break; // try-block
-      }
-
-      if (empty($string)) {
-        $resultArr[] = $string;
-        break; // try-block
-      }
-
-      
-      while (!empty($string)) {
-        // Now determine the first bracket '(' or '['.
-        $openPos = strpos($string, $open);
-        $closePos = strpos($string, $close);
-        if ($openPos===FALSE || $closePos===FALSE) {
-          // Oh, no more groups to be found then.  Quit
-          $resultArr[] = $string;
-          break;
-        }
-
-        // Sanity check
-        if ($openPos > $closePos) {
-          // Malformed string, dump the rest and quit.
-          $resultArr[] = $string;
-          break;
-        }
-
-        // Get prefix string part before the first bracket.
-        $preStr = substr($string, 0, $openPos);
-        // This is the first string that will go in our output
-        if (!empty($preStr))
-          $resultArr[] = $preStr;
-
-        // Skip over what we've proceed, including the open char
-        $string = substr($string, $openPos + 1 - strlen($string));
-
-        // Find the next open char and adjust our close char
-//echo "close: $closePos\nopen: $openPos\n\n";
-        $closePos -= $openPos + 1;
-        $openPos = strpos($string, $open);
-//echo "close: $closePos\nopen: $openPos\n\n";
-
-        // While we have found nesting...
-        while ($openPos && $closePos && ($closePos > $openPos)) {
-          // Find another close pos after the one we are looking at
-          $closePos = strpos($string, $close, $closePos + 1);
-          // And skip our open
-          $openPos = strpos($string, $open, $openPos + 1);
-        }
-//echo "close: $closePos\nopen: $openPos\n\n";
-
-        // If we now have a close pos, then it's the end of the group.
-        if ($closePos === FALSE) {
-          // We didn't... so bail dumping what was left
-          $resultArr[] = $open.$string;
-          break;
-        }
-
-        // We did, so we can extract the group
-        $resultArr[] = $open.substr($string, 0, $closePos + 1);
-        // Skip what we have processed
-        $string = substr($string, $closePos + 1);
-      }
-    } while (FALSE); // End try block
-    // Return the results that we found. May be a array with 1 entry.
-    return $resultArr;
-  }
-  
-  /**
-   * Retrieves a substring before a delimiter.
-   *
-   * This method retrieves everything from a string before a given delimiter,
-   * not including the delimiter.
-   *
-   * @param     $string     (string) String, from which the substring should be extracted.
-   * @param     $delimiter  (string) 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.
-    $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.
-   *
-   * @param     $string     (string) String, from which the substring should be extracted.
-   * @param     $delimiter  (string) 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));
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathBase                ------  Debug Stuff  ------                                    
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Alter the verbose (error) level reporting.
-   *
-   * Pass an int. >0 to turn on, 0 to turn off.  The higher the number, the 
-   * higher the level of verbosity. By default, the class has a verbose level 
-   * of 1.
-   *
-   * @param $levelOfVerbosity (int) default is 1 = on
-   */
-  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->properties['verboseLevel'] = $levelOfVerbosity;
-  }
-   
-  /**
-   * Returns the last occured error message.
-   *
-   * @access public
-   * @return string (may be empty if there was no error at all)
-   * @see    _setLastError(), _lastError
-   */
-  function getLastError() {
-    return $this->_lastError;
-  }
-  
-  /**
-   * 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.
-   * 
-   * You should call _displayError() rather than _setLastError() if you would like the message,
-   * dependant on their verbose settings, echoed to the screen.
-   * 
-   * @param $message (string) a textual error message default is ''
-   * @param $line    (int)    the line number where the error occured, use __LINE__
-   * @see getLastError()
-   */
-  function _setLastError($message='', $line='-', $file='-') {
-    $this->_lastError = 'XPath error in ' . basename($file) . ':' . $line . '. Message: ' . $message;
-  }
-  
-  /**
-   * Displays an error message.
-   *
-   * This method displays an error messages depending on the users verbose settings 
-   * and sets the last error message.  
-   *
-   * If also possibly stops the execution of the script.
-   * ### Terminate should not be allowed --fab.  Should it??  N.S.
-   *
-   * @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='-', $file='-', $terminate=TRUE) {
-    // Display the error message.
-    $err = '<b>XPath error in '.basename($file).':'.$lineNumber.'</b> '.$message."<br \>\n";
-    $this->_setLastError($message, $lineNumber, $file);
-    if (($this->properties['verboseLevel'] > 0) OR ($terminate)) echo $err;
-    // End the execution of this script.
-    if ($terminate) exit;
-  }
-
-  /**
-   * Displays a diagnostic message
-   *
-   * This method displays an error messages
-   *
-   * @param $message    (string)  Error message to be displayed.
-   * @param $lineNumber (int)     line number given by __LINE__
-   */
-  function _displayMessage($message, $lineNumber='-', $file='-') {
-    // Display the error message.
-    $err = '<b>XPath message from '.basename($file).':'.$lineNumber.'</b> '.$message."<br \>\n";
-    if ($this->properties['verboseLevel'] > 0) echo $err;
-  }
-  
-  /**
-   * Called to begin the debug run of a function.
-   *
-   * This method starts a <DIV><PRE> 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 <nigelswinson at users.sourceforge.net>
-   * @author    Sam   Blum    <bs_php at infeer.com>
-   * @param     $functionName (string) the name of the function we are beginning to debug
-   * @return                  (array)  the output from the microtime() function.
-   * @see       _closeDebugFunction()
-   */
-  function _beginDebugFunction($functionName) {
-    $fileName = basename(__FILE__);
-    static $color = array('green','blue','red','lime','fuchsia', 'aqua');
-    static $colIndex = -1;
-    $colIndex++;
-    $pre = '<pre STYLE="border:solid thin '. $color[$colIndex % 6] . '; padding:5">';
-    $out = '<div align="left"> ' . $pre . "<STRONG>{$fileName} : {$functionName}</STRONG>";
-    echo $out;
-    echo '<a style="float:right" name="'.$this->iDebugNextLinkNumber.'Open" href="#'.$this->iDebugNextLinkNumber.'Close">Function Close '.$this->iDebugNextLinkNumber.'</a>';
-    echo '<hr style="clear:both">';
-    array_push($this->aDebugOpenLinks, $this->iDebugNextLinkNumber);
-    $this->iDebugNextLinkNumber++;
-    return microtime();
-  }
-  
-  /**
-   * Called to end the debug run of a function.
-   *
-   * This method ends a <DIV><PRE> block and reports the time since $aStartTime
-   * is clear to the debugging user.
-   *
-   * @author    Nigel Swinson <nigelswinson at users.sourceforge.net>
-   * @param     $aStartTime   (array) the time that the function call was started.
-   * @param     $return_value (mixed) the return value from the function call that 
-   *                                  we are debugging
-   */
-  function _closeDebugFunction($aStartTime, $returnValue = "") {
-    echo "<hr>";
-    if (isSet($returnValue)) {
-      if (is_array($returnValue))
-        echo "Return Value: ".print_r($returnValue)."\n";
-      else if (is_numeric($returnValue)) 
-        echo "Return Value: '".(string)$returnValue."'\n";
-      else if (is_bool($returnValue)) 
-        echo "Return Value: ".($returnValue ? "TRUE" : "FALSE")."\n";
-      else 
-        echo "Return Value: \"".htmlspecialchars($returnValue)."\"\n";
-    }
-    $this->_profileFunction($aStartTime, "Function took");
-    $iOpenLinkNumber = array_pop($this->aDebugOpenLinks);
-    echo '<a style="float:right" name="'.$iOpenLinkNumber.'Close" href="#'.$iOpenLinkNumber.'Open">Function Open '.$iOpenLinkNumber.'</a>';
-    echo '<br style="clear:both">';
-    echo " \n</pre></div>";
-  }
-  
-  /**
-   * Call to return time since start of function for Profiling
-   *
-   * @param     $aStartTime  (array)  the time that the function call was started.
-   * @param     $alertString (string) the string to describe what has just finished happening
-   */
-  function _profileFunction($aStartTime, $alertString) {
-    // Print the time it took to call this function.
-    $now   = explode(' ', microtime());
-    $last  = explode(' ', $aStartTime);
-    $delta = (round( (($now[1] - $last[1]) + ($now[0] - $last[0]))*1000 ));
-    echo "\n{$alertString} <strong>{$delta} ms</strong>";
-  }
-
-  /**
-   * Echo an XPath context for diagnostic purposes
-   *
-   * @param $context   (array)   An XPath context
-   */
-  function _printContext($context) {
-    echo "{$context['nodePath']}({$context['pos']}/{$context['size']})";
-  }
-  
-  /**
-   * This is a debug helper function. It dumps the node-tree as HTML
-   *
-   * *QUICK AND DIRTY*. Needs some polishing.
-   *
-   * @param $node   (array)   A node 
-   * @param $indent (string) (optional, default=''). For internal recursive calls.
-   */
-  function _treeDump($node, $indent = '') {
-    $out = '';
-    
-    // Get rid of recursion
-    $parentName = empty($node['parentNode']) ? "SUPER ROOT" :  $node['parentNode']['name'];
-    unset($node['parentNode']);
-    $node['parentNode'] = $parentName ;
-    
-    $out .= "NODE[{$node['name']}]\n";
-    
-    foreach($node as $key => $val) {
-      if ($key === 'childNodes') continue;
-      if (is_Array($val)) {
-        $out .= $indent . "  [{$key}]\n" . arrayToStr($val, $indent . '    ');
-      } else {
-        $out .= $indent . "  [{$key}] => '{$val}' \n";
-      }
-    }
-    
-    if (!empty($node['childNodes'])) {
-      $out .= $indent . "  ['childNodes'] (Size = ".sizeOf($node['childNodes']).")\n";
-      foreach($node['childNodes'] as $key => $childNode) {
-        $out .= $indent . "     [$key] => " . $this->_treeDump($childNode, $indent . '       ') . "\n";
-      }
-    }
-    
-    if (empty($indent)) {
-      return "<pre>" . htmlspecialchars($out) . "</pre>";
-    }
-    return $out;
-  }
-} // END OF CLASS XPathBase
-
-
-/************************************************************************************************
-* ===============================================================================================
-*                             X P a t h E n g i n e  -  Class                                    
-* ===============================================================================================
-************************************************************************************************/
-
-class XPathEngine extends XPathBase {
-  
-  // List of supported XPath axes.
-  // What a stupid idea from W3C to take axes name containing a '-' (dash)
-  // NOTE: We replace the '-' with '_' to avoid the conflict with the minus operator.
-  //       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 query.
-  var $axes = array ( 'ancestor', 'ancestor_or_self', 'attribute', 'child', 'descendant', 
-                        'descendant_or_self', 'following', 'following_sibling',  
-                        'namespace', 'parent', 'preceding', 'preceding_sibling', 'self' 
-     );
-  
-  // List of supported XPath functions.
-  // What a stupid idea from W3C to take function name containing a '-' (dash)
-  // NOTE: We replace the '-' with '_' to avoid the conflict with the minus operator.
-  //       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 query.
-  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', 'x_lower', 'x_upper', 'generate_id' );
-    
-  // List of supported XPath operators.
-  //
-  // This array contains a list of all valid operators that can be evaluated
-  // in a predicate of an XPath query. The list is ordered by the
-  // precedence of the operators (lowest precedence first).
-  var $operators = array( ' or ', ' and ', '=', '!=', '<=', '<', '>=', '>',
-    '+', '-', '*', ' div ', ' mod ', ' | ');
-
-  // List of literals from the xPath string.
-  var $axPathLiterals = array();
-  
-  // The index and tree that is created during the analysis of an XML source.
-  var $nodeIndex = array();
-  var $nodeRoot  = array();
-  var $emptyNode = array(
-                     'name'        => '',       // The tag name. E.g. In <FOO bar="aaa"/> it would be 'FOO'
-                     'attributes'  => array(),  // The attributes of the tag E.g. In <FOO bar="aaa"/> it would be array('bar'=>'aaa')
-                     'childNodes'  => array(),  // Array of pointers to child nodes.
-                     'textParts'   => array(),  // Array of text parts between the cilderen E.g. <FOO>aa<A>bb<B/>cc</A>dd</FOO> -> array('aa','bb','cc','dd')
-                     'parentNode'   => NULL,     // Pointer to parent node or NULL if this node is the 'super root'
-                     //-- *!* Following vars are set by the indexer and is for optimisation only *!*
-                     'depth'       => 0,  // The tag depth (or tree level) starting with the root tag at 0.
-                     'pos'         => 0,  // Is the zero-based position this node has in the parents 'childNodes'-list.
-                     'contextPos'  => 1,  // Is the one-based position this node has by counting the siblings tags (tags with same name)
-                     'xpath'       => ''  // Is the abs. XPath to this node.
-                   );
-  var $_indexIsDirty = FALSE;
-
-  
-  // These variable used during the parse XML source
-  var $nodeStack       = array(); // The elements that we have still to close.
-  var $parseStackIndex = 0;       // The current element of the nodeStack[] that we are adding to while 
-                                  // parsing an XML source.  Corresponds to the depth of the xml node.
-                                  // in our input data.
-  var $parseOptions    = array(); // Used to set the PHP's XML parser options (see xml_parser_set_option)
-  var $parsedTextLocation   = ''; // A reference to where we have to put char data collected during XML parsing
-  var $parsInCData     = 0 ;      // Is >0 when we are inside a CDATA section.  
-  var $parseSkipWhiteCache = 0;   // A cache of the skip whitespace parse option to speed up the parse.
-
-  // This is the array of error strings, to keep consistency.
-  var $errorStrings = array(
-    'AbsoluteXPathRequired' => "The supplied xPath '%s' does not *uniquely* describe a node in the xml document.",
-    'NoNodeMatch'           => "The supplied xPath-query '%s' does not match *any* node in the xml document.",
-    'RootNodeAlreadyExists' => "An xml document may have only one root node."
-    );
-    
-  /**
-   * Constructor
-   *
-   * Optionally you may call this constructor with the XML-filename to parse and the 
-   * XML option vector. Each of the entries in the option vector will be passed to
-   * xml_parser_set_option().
-   *
-   * A option vector sample: 
-   *   $xmlOpt = array(XML_OPTION_CASE_FOLDING => FALSE, 
-   *                   XML_OPTION_SKIP_WHITE => TRUE);
-   *
-   * @param  $userXmlOptions (array) (optional) Vector of (<optionID>=><value>, 
-   *                                 <optionID>=><value>, ...).  See PHP's
-   *                                 xml_parser_set_option() docu for a list of possible
-   *                                 options.
-   * @see   importFromFile(), importFromString(), setXmlOptions()
-   */
-  function XPathEngine($userXmlOptions=array()) {
-    parent::XPathBase();
-    // Default to not folding case
-    $this->parseOptions[XML_OPTION_CASE_FOLDING] = FALSE;
-    // And not skipping whitespace
-    $this->parseOptions[XML_OPTION_SKIP_WHITE] = FALSE;
-    
-    // Now merge in the overrides.
-    // Don't use PHP's array_merge!
-    if (is_array($userXmlOptions)) {
-      foreach($userXmlOptions as $key => $val) $this->parseOptions[$key] = $val;
-    }
-  }
-  
-  /**
-   * Resets the object so it's able to take a new xml sting/file
-   *
-   * Constructing objects is slow.  If you can, reuse ones that you have used already
-   * by using this reset() function.
-   */
-  function reset() {
-    parent::reset();
-    $this->properties['xmlFile']  = ''; 
-    $this->parseStackIndex = 0;
-    $this->parsedTextLocation = '';
-    $this->parsInCData   = 0;
-    $this->nodeIndex     = array();
-    $this->nodeRoot      = array();
-    $this->nodeStack     = array();
-    $this->aLiterals     = array();
-    $this->_indexIsDirty = FALSE;
-  }
-  
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine              ------  Get / Set Stuff  ------                                
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Returns the property/ies you want.
-   * 
-   * if $param is not given, all properties will be returned in a hash.
-   *
-   * @param  $param (string) the property you want the value of, or NULL for all the properties
-   * @return        (mixed)  string OR hash of all params, or NULL on an unknown parameter.
-   */
-  function getProperties($param=NULL) {
-    $this->properties['hasContent']      = !empty($this->nodeRoot);
-    $this->properties['caseFolding']     = $this->parseOptions[XML_OPTION_CASE_FOLDING];
-    $this->properties['skipWhiteSpaces'] = $this->parseOptions[XML_OPTION_SKIP_WHITE];
-    
-    if (empty($param)) return $this->properties;
-    
-    if (isSet($this->properties[$param])) {
-      return $this->properties[$param];
-    } else {
-      return NULL;
-    }
-  }
-  
-  /**
-   * Set an xml_parser_set_option()
-   *
-   * @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->parseOptions[$optionID] = $value;
-  }
-
-  /**
-   * Sets a number of xml_parser_set_option()s
-   *
-   * @param  $userXmlOptions (array) An array of parser options.
-   * @see setXmlOption
-   */
-  function setXmlOptions($userXmlOptions=array()) {
-    if (!is_array($userXmlOptions)) return;
-    foreach($userXmlOptions as $key => $val) {
-      $this->setXmlOption($key, $val);
-    }
-  }
-  
-  /**
-   * Alternative way to control whether case-folding is enabled for this XML parser.
-   *
-   * Short cut to setXmlOptions(XML_OPTION_CASE_FOLDING, TRUE/FALSE)
-   *
-   * When it comes to XML, case-folding simply means uppercasing all tag- 
-   * and attribute-names (NOT the content) if set to TRUE.  Note if you
-   * have this option set, then your XPath queries will also be case folded 
-   * for you.
-   *
-   * @param $onOff (bool) (default TRUE) 
-   * @see XML parser functions in PHP doc
-   */
-  function setCaseFolding($onOff=TRUE) {
-    $this->parseOptions[XML_OPTION_CASE_FOLDING] = $onOff;
-  }
-  
-  /**
-   * Alternative way to control whether skip-white-spaces is enabled for this XML parser.
-   *
-   * Short cut to setXmlOptions(XML_OPTION_SKIP_WHITE, TRUE/FALSE)
-   *
-   * When it comes to XML, skip-white-spaces will trim the tag content.
-   * An XML file with no whitespace will be faster to process, but will make 
-   * your data less human readable when you come to write it out.
-   *
-   * Running with this option on will slow the class down, so if you want to 
-   * speed up your XML, then run it through once skipping white-spaces, then
-   * write out the new version of your XML without whitespace, then use the
-   * new XML file with skip whitespaces turned off.
-   *
-   * @param $onOff (bool) (default TRUE) 
-   * @see XML parser functions in PHP doc
-   */
-  function setSkipWhiteSpaces($onOff=TRUE) {
-    $this->parseOptions[XML_OPTION_SKIP_WHITE] = $onOff;
-  }
-   
-  /**
-   * Get the node defined by the $absoluteXPath.
-   *
-   * @param   $absoluteXPath (string) (optional, default is 'super-root') xpath to the node.
-   * @return                 (array)  The node, or FALSE if the node wasn't found.
-   */
-  function &getNode($absoluteXPath='') {
-    if ($absoluteXPath==='/') $absoluteXPath = '';
-    if (!isSet($this->nodeIndex[$absoluteXPath])) return FALSE;
-    if ($this->_indexIsDirty) $this->reindexNodeTree();
-    return $this->nodeIndex[$absoluteXPath];
-  }
-  
-  /**
-   * Get a the content of a node text part or node attribute.
-   * 
-   * If the absolute Xpath references an attribute (Xpath ends with @ or attribute::), 
-   * then the text value of that node-attribute is returned.
-   * Otherwise the Xpath is referencing a text part of the node. This can be either a 
-   * direct reference to a text part (Xpath ends with text()[<nr>]) or indirect reference 
-   * (a simple abs. Xpath to a node).
-   * 1) Direct Reference (xpath ends with text()[<part-number>]):
-   *   If the 'part-number' is omitted, the first text-part is assumed; starting by 1.
-   *   Negative numbers are allowed, where -1 is the last text-part a.s.o.
-   * 2) Indirect Reference (a simple abs. Xpath to a node):
-   *   Default is to return the *whole text*; that is the concated text-parts of the matching
-   *   node. (NOTE that only in this case you'll only get a copy and changes to the returned  
-   *   value wounld have no effect). Optionally you may pass a parameter 
-   *   $textPartNr to define the text-part you want;  starting by 1.
-   *   Negative numbers are allowed, where -1 is the last text-part a.s.o.
-   *
-   * NOTE I : The returned value can be fetched by reference
-   *          E.g. $text =& wholeText(). If you wish to modify the text.
-   * NOTE II: text-part numbers out of range will return FALSE
-   * SIDENOTE:The function name is a suggestion from W3C in the XPath specification level 3.
-   *
-   * @param   $absoluteXPath  (string)  xpath to the node (See above).
-   * @param   $textPartNr     (int)     If referring to a node, specifies which text part 
-   *                                    to query.
-   * @return                  (&string) A *reference* to the text if the node that the other 
-   *                                    parameters describe or FALSE if the node is not found.
-   */
-  function &wholeText($absoluteXPath, $textPartNr=NULL) {
-    $status = FALSE;
-    $text   = NULL;
-    if ($this->_indexIsDirty) $this->reindexNodeTree();
-    
-    do { // try-block
-      if (preg_match(";(.*)/(attribute::|@)([^/]*)$;U", $absoluteXPath, $matches)) {
-        $absoluteXPath = $matches[1];
-        $attribute = $matches[3];
-        if (!isSet($this->nodeIndex[$absoluteXPath]['attributes'][$attribute])) {
-          $this->_displayError("The $absoluteXPath/attribute::$attribute value isn't a node in this document.", __LINE__, __FILE__, FALSE);
-          break; // try-block
-        }
-        $text =& $this->nodeIndex[$absoluteXPath]['attributes'][$attribute];
-        $status = TRUE;
-        break; // try-block
-      }
-            
-      // Xpath contains a 'text()'-function, thus goes right to a text node. If so interpete the Xpath.
-      if (preg_match(":(.*)/text\(\)(\[(.*)\])?$:U", $absoluteXPath, $matches)) {
-        $absoluteXPath = $matches[1];
- 
-        if (!isSet($this->nodeIndex[$absoluteXPath])) {
-            $this->_displayError("The $absoluteXPath value isn't a node in this document.", __LINE__, __FILE__, FALSE);
-            break; // try-block
-        }
-
-        // Get the amount of the text parts in the node.
-        $textPartSize = sizeOf($this->nodeIndex[$absoluteXPath]['textParts']);
-
-        // default to the first text node if a text node was not specified
-        $textPartNr = isSet($matches[2]) ? substr($matches[2],1,-1) : 1;
-
-        // Support negative indexes like -1 === last a.s.o.
-        if ($textPartNr < 0) $textPartNr = $textPartSize + $textPartNr +1;
-        if (($textPartNr <= 0) OR ($textPartNr > $textPartSize)) {
-          $this->_displayError("The $absoluteXPath/text()[$textPartNr] value isn't a NODE in this document.", __LINE__, __FILE__, FALSE);
-          break; // try-block
-        }
-        $text =& $this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr - 1];
-        $status = TRUE;
-        break; // try-block
-      }
-      
-      // At this point we have been given an xpath with neither a 'text()' nor 'attribute::' axis at the end
-      // So we assume a get to text is wanted and use the optioanl fallback parameters $textPartNr
-     
-      if (!isSet($this->nodeIndex[$absoluteXPath])) {
-          $this->_displayError("The $absoluteXPath value isn't a node in this document.", __LINE__, __FILE__, FALSE);
-          break; // try-block
-      }
-
-      // Get the amount of the text parts in the node.
-      $textPartSize = sizeOf($this->nodeIndex[$absoluteXPath]['textParts']);
-
-      // If $textPartNr == NULL we return a *copy* of the whole concated text-parts
-      if (is_null($textPartNr)) {
-        unset($text);
-        $text = implode('', $this->nodeIndex[$absoluteXPath]['textParts']);
-        $status = TRUE;
-        break; // try-block
-      }
-      
-      // Support negative indexes like -1 === last a.s.o.
-      if ($textPartNr < 0) $textPartNr = $textPartSize + $textPartNr +1;
-      if (($textPartNr <= 0) OR ($textPartNr > $textPartSize)) {
-        $this->_displayError("The $absoluteXPath has no text part at pos [$textPartNr] (Note: text parts start with 1).", __LINE__, __FILE__, FALSE);
-        break; // try-block
-      }
-      $text =& $this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr -1];
-      $status = TRUE;
-    } while (FALSE); // END try-block
-    
-    if (!$status) return FALSE;
-    return $text;
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine           ------ Export the XML Document ------                             
-  //-----------------------------------------------------------------------------------------
-   
-  /**
-   * Returns the containing XML as marked up HTML with specified nodes hi-lighted
-   *
-   * @param $absoluteXPath    (string) The address of the node you would like to export.
-   *                                   If empty the whole document will be exported.
-   * @param $hilighXpathList  (array)  A list of nodes that you would like to highlight
-   * @return                  (mixed)  The Xml document marked up as HTML so that it can
-   *                                   be viewed in a browser, including any XML headers.
-   *                                   FALSE on error.
-   * @see _export()    
-   */
-  function exportAsHtml($absoluteXPath='', $hilightXpathList=array()) {
-    $htmlString = $this->_export($absoluteXPath, $xmlHeader=NULL, $hilightXpathList);
-    if (!$htmlString) return FALSE;
-    return "<pre>\n" . $htmlString . "\n</pre>"; 
-  }
-  
-  /**
-   * Given a context this function returns the containing XML
-   *
-   * @param $absoluteXPath  (string) The address of the node you would like to export.
-   *                                 If empty the whole document will be exported.
-   * @param $xmlHeader      (array)  The string that you would like to appear before
-   *                                 the XML content.  ie before the <root></root>.  If you
-   *                                 do not specify this argument, the xmlHeader that was 
-   *                                 found in the parsed xml file will be used instead.
-   * @return                (mixed)  The Xml fragment/document, suitable for writing
-   *                                 out to an .xml file or as part of a larger xml file, or
-   *                                 FALSE on error.
-   * @see _export()    
-   */
-  function exportAsXml($absoluteXPath='', $xmlHeader=NULL) {
-    $this->hilightXpathList = NULL;
-    return $this->_export($absoluteXPath, $xmlHeader); 
-  }
-    
-  /**
-   * Generates a XML string with the content of the current document and writes it to a file.
-   *
-   * Per default includes a <?xml ...> tag at the start of the data too. 
-   *
-   * @param     $fileName       (string) 
-   * @param     $absoluteXPath  (string) The path to the parent node you want(see text above)
-   * @param     $xmlHeader      (array)  The string that you would like to appear before
-   *                                     the XML content.  ie before the <root></root>.  If you
-   *                                     do not specify this argument, the xmlHeader that was 
-   *                                     found in the parsed xml file will be used instead.
-   * @return                    (string) The returned string contains well-formed XML data 
-   *                                     or FALSE on error.
-   * @see       exportAsXml(), exportAsHtml()
-   */
-  function exportToFile($fileName, $absoluteXPath='', $xmlHeader=NULL) {   
-    $status = FALSE;
-    do { // try-block
-      if (!($hFile = fopen($fileName, "wb"))) {   // Did we open the file ok?
-        $errStr = "Failed to open the $fileName xml file.";
-        break; // try-block
-      }
-      
-      if ($this->properties['OS_supports_flock']) {
-        if (!flock($hFile, LOCK_EX + LOCK_NB)) {  // Lock the file
-          $errStr = "Couldn't get an exclusive lock on the $fileName file.";
-          break; // try-block
-        }
-      }
-      if (!($xmlOut = $this->_export($absoluteXPath, $xmlHeader))) {
-        $errStr = "Export failed";
-        break; // try-block
-      }
-      
-	  $iBytesWritten = fwrite($hFile, $xmlOut);
-      if ($iBytesWritten != strlen($xmlOut)) {
-        $errStr = "Write error when writing back the $fileName file.";
-        break; // try-block
-      }
-      
-      // Flush and unlock the file
-      @fflush($hFile);
-      $status = TRUE;
-    } while(FALSE);
-    
-    @flock($hFile, LOCK_UN);
-    @fclose($hFile);
-    // Sanity check the produced file.
-    if (filesize($fileName) < strlen($xmlOut)) {
-      $errStr = "Write error when writing back the $fileName file.";
-      $status = FALSE;
-    }
-    
-    if (!$status)  $this->_displayError($errStr, __LINE__, __FILE__, FALSE);
-    return $status;
-  }
-
-  /**
-   * Generates a XML string with the content of the current document.
-   *
-   * This is the start for extracting the XML-data from the node-tree. We do some preperations
-   * and then call _InternalExport() to fetch the main XML-data. You optionally may pass 
-   * xpath to any node that will then be used as top node, to extract XML-parts of the 
-   * document. Default is '', meaning to extract the whole document.
-   *
-   * You also may pass a 'xmlHeader' (usually something like <?xml version="1.0"? > that will
-   * overwrite any other 'xmlHeader', if there was one in the original source.  If there
-   * wasn't one in the original source, and you still don't specify one, then it will
-   * use a default of <?xml version="1.0"? >
-   * Finaly, when exporting to HTML, you may pass a vector xPaths you want to hi-light.
-   * The hi-lighted tags and attributes will receive a nice color. 
-   * 
-   * NOTE I : The output can have 2 formats:
-   *       a) If "skip white spaces" is/was set. (Not Recommended - slower)
-   *          The output is formatted by adding indenting and carriage returns.
-   *       b) If "skip white spaces" is/was *NOT* set.
-   *          'as is'. No formatting is done. The output should the same as the 
-   *          the original parsed XML source. 
-   *
-   * @param  $absoluteXPath (string) (optional, default is root) The node we choose as top-node
-   * @param  $xmlHeader     (string) (optional) content before <root/> (see text above)
-   * @param  $hilightXpath  (array)  (optional) a vector of xPaths to nodes we wat to 
-   *                                 hi-light (see text above)
-   * @return                (mixed)  The xml string, or FALSE on error.
-   */
-  function _export($absoluteXPath='', $xmlHeader=NULL, $hilightXpathList='') {
-    // Check whether a root node is given.
-    if (empty($absoluteXpath)) $absoluteXpath = '';
-    if ($absoluteXpath == '/') $absoluteXpath = '';
-    if ($this->_indexIsDirty) $this->reindexNodeTree();
-    if (!isSet($this->nodeIndex[$absoluteXpath])) {
-      // If the $absoluteXpath was '' and it didn't exist, then the document is empty
-      // and we can safely return ''.
-      if ($absoluteXpath == '') return '';
-      $this->_displayError("The given xpath '{$absoluteXpath}' isn't a node in this document.", __LINE__, __FILE__, FALSE);
-      return FALSE;
-    }
-    
-    $this->hilightXpathList = $hilightXpathList;
-    $this->indentStep = '  ';
-    $hilightIsActive = is_array($hilightXpathList);
-    if ($hilightIsActive) {
-      $this->indentStep = '&nbsp;&nbsp;&nbsp;&nbsp;';
-    }    
-    
-    // Cache this now
-    $this->parseSkipWhiteCache = isSet($this->parseOptions[XML_OPTION_SKIP_WHITE]) ? $this->parseOptions[XML_OPTION_SKIP_WHITE] : FALSE;
-
-    ///////////////////////////////////////
-    // Get the starting node and begin with the header
-
-    // Get the start node.  The super root is a special case.
-    $startNode = NULL;
-    if (empty($absoluteXPath)) {
-      $superRoot = $this->nodeIndex[''];
-      // If they didn't specify an xml header, use the one in the object
-      if (is_null($xmlHeader)) {
-        $xmlHeader = $this->parseSkipWhiteCache ? trim($superRoot['textParts'][0]) : $superRoot['textParts'][0];
-        // If we still don't have an XML header, then use a suitable default
-        if (empty($xmlHeader)) {
-            $xmlHeader = '<?xml version="1.0"?>';
-        }
-      }
-
-      if (isSet($superRoot['childNodes'][0])) $startNode = $superRoot['childNodes'][0];
-    } else {
-      $startNode = $this->nodeIndex[$absoluteXPath];
-    }
-
-    if (!empty($xmlHeader)) { 
-      $xmlOut = $this->parseSkipWhiteCache ? $xmlHeader."\n" : $xmlHeader;
-    } else {
-      $xmlOut = '';
-    }
-
-    ///////////////////////////////////////
-    // Output the document.
-
-    if (($xmlOut .= $this->_InternalExport($startNode)) === FALSE) {
-      return FALSE;
-    }
-    
-    ///////////////////////////////////////
-
-    // Convert our markers to hi-lights.
-    if ($hilightIsActive) {
-      $from = array('<', '>', chr(2), chr(3));
-      $to = array('&lt;', '&gt;', '<font color="#FF0000"><b>', '</b></font>');
-      $xmlOut = str_replace($from, $to, $xmlOut);
-    }
-    return $xmlOut; 
-  }  
-
-  /**
-   * Export the xml document starting at the named node.
-   *
-   * @param $node (node)   The node we have to start exporting from
-   * @return      (string) The string representation of the node.
-   */
-  function _InternalExport($node) {
-    $bDebugThisFunction = FALSE;
-
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction("_InternalExport");
-      echo "Exporting node: ".$node['xpath']."<br>\n";
-    }
-
-    ////////////////////////////////
-
-    // Quick out.
-    if (empty($node)) return '';
-
-    // The output starts as empty.
-    $xmlOut = '';
-    // This loop will output the text before the current child of a parent then the 
-    // current child.  Where the child is a short tag we output the child, then move
-    // onto the next child.  Where the child is not a short tag, we output the open tag, 
-    // then queue up on currentParentStack[] the child.  
-    //
-    // When we run out of children, we then output the last text part, and close the 
-    // 'parent' tag before popping the stack and carrying on.
-    //
-    // To illustrate, the numbers in this xml file indicate what is output on each
-    // pass of the while loop:
-    //
-    // 1
-    // <1>2
-    //  <2>3
-    //   <3/>4
-    //  </4>5
-    //  <5/>6
-    // </6>
-
-    // Although this is neater done using recursion, there's a 33% performance saving
-    // to be gained by using this stack mechanism.
-
-    // Only add CR's if "skip white spaces" was set. Otherwise leave as is.
-    $CR = ($this->parseSkipWhiteCache) ? "\n" : '';
-    $currentIndent = '';
-    $hilightIsActive = is_array($this->hilightXpathList);
-
-    // To keep track of where we are in the document we use a node stack.  The node 
-    // stack has the following parallel entries:
-    //   'Parent'     => (array) A copy of the parent node that who's children we are 
-    //                           exporting
-    //   'ChildIndex' => (array) The child index of the corresponding parent that we
-    //                           are currently exporting.
-    //   'Highlighted'=> (bool)  If we are highlighting this node.  Only relevant if
-    //                           the hilight is active.
-
-    // Setup our node stack.  The loop is designed to output children of a parent, 
-    // not the parent itself, so we must put the parent on as the starting point.
-    $nodeStack['Parent'] = array($node['parentNode']);
-    // And add the childpos of our node in it's parent to our "child index stack".
-    $nodeStack['ChildIndex'] = array($node['pos']);
-    // We start at 0.
-    $nodeStackIndex = 0;
-
-    // We have not to output text before/after our node, so blank it.  We will recover it
-    // later
-    $OldPreceedingStringValue = $nodeStack['Parent'][0]['textParts'][$node['pos']];
-    $OldPreceedingStringRef =& $nodeStack['Parent'][0]['textParts'][$node['pos']];
-    $OldPreceedingStringRef = "";
-    $currentXpath = "";
-
-    // While we still have data on our stack
-    while ($nodeStackIndex >= 0) {
-      // Count the children and get a copy of the current child.
-      $iChildCount = count($nodeStack['Parent'][$nodeStackIndex]['childNodes']);
-      $currentChild = $nodeStack['ChildIndex'][$nodeStackIndex];
-      // Only do the auto indenting if the $parseSkipWhiteCache flag was set.
-      if ($this->parseSkipWhiteCache)
-        $currentIndent = str_repeat($this->indentStep, $nodeStackIndex);
-
-      if ($bDebugThisFunction)
-        echo "Exporting child ".($currentChild+1)." of node {$nodeStack['Parent'][$nodeStackIndex]['xpath']}\n";
-
-      ///////////////////////////////////////////
-      // Add the text before our child.
-
-      // Add the text part before the current child
-      $tmpTxt =& $nodeStack['Parent'][$nodeStackIndex]['textParts'][$currentChild];
-      if (isSet($tmpTxt) AND ($tmpTxt!="")) {
-        // Only add CR indent if there were children
-        if ($iChildCount)
-          $xmlOut .= $CR.$currentIndent;
-        // Hilight if necessary.
-        $highlightStart = $highlightEnd = '';
-        if ($hilightIsActive) {
-          $currentXpath = $nodeStack['Parent'][$nodeStackIndex]['xpath'].'/text()['.($currentChild+1).']';
-          if (in_array($currentXpath, $this->hilightXpathList)) {
-           // Yes we hilight
-            $highlightStart = chr(2);
-            $highlightEnd   = chr(3);
-          }
-        }
-        $xmlOut .= $highlightStart.$nodeStack['Parent'][$nodeStackIndex]['textParts'][$currentChild].$highlightEnd;
-      }
-      if ($iChildCount && $nodeStackIndex) $xmlOut .= $CR;
-
-      ///////////////////////////////////////////
-
-      // Are there any more children?
-      if ($iChildCount <= $currentChild) {
-        // Nope, so output the last text before the closing tag
-        $tmpTxt =& $nodeStack['Parent'][$nodeStackIndex]['textParts'][$currentChild+1];
-        if (isSet($tmpTxt) AND ($tmpTxt!="")) {
-          // Hilight if necessary.
-          $highlightStart = $highlightEnd = '';
-          if ($hilightIsActive) {
-            $currentXpath = $nodeStack['Parent'][$nodeStackIndex]['xpath'].'/text()['.($currentChild+2).']';
-            if (in_array($currentXpath, $this->hilightXpathList)) {
-             // Yes we hilight
-              $highlightStart = chr(2);
-              $highlightEnd   = chr(3);
-            }
-          }
-          $xmlOut .= $highlightStart
-                .$currentIndent.$nodeStack['Parent'][$nodeStackIndex]['textParts'][$currentChild+1].$CR
-                .$highlightEnd;
-        }
-
-        // Now close this tag, as we are finished with this child.
-
-        // Potentially output an (slightly smaller indent).
-        if ($this->parseSkipWhiteCache
-          && count($nodeStack['Parent'][$nodeStackIndex]['childNodes'])) {
-          $xmlOut .= str_repeat($this->indentStep, $nodeStackIndex - 1);
-        }
-
-        // Check whether the xml-tag is to be hilighted.
-        $highlightStart = $highlightEnd = '';
-        if ($hilightIsActive) {
-          $currentXpath = $nodeStack['Parent'][$nodeStackIndex]['xpath'];
-          if (in_array($currentXpath, $this->hilightXpathList)) {
-            // Yes we hilight
-            $highlightStart = chr(2);
-            $highlightEnd   = chr(3);
-          }
-        }
-        $xmlOut .=  $highlightStart
-                     .'</'.$nodeStack['Parent'][$nodeStackIndex]['name'].'>'
-                     .$highlightEnd;
-        // Decrement the $nodeStackIndex to go back to the next unfinished parent.
-        $nodeStackIndex--;
-
-        // If the index is 0 we are finished exporting the last node, as we may have been
-        // exporting an internal node.
-        if ($nodeStackIndex == 0) break;
-
-        // Indicate to the parent that we are finished with this child.
-        $nodeStack['ChildIndex'][$nodeStackIndex]++;
-
-        continue;
-      }
-
-      ///////////////////////////////////////////
-      // Ok, there are children still to process.
-
-      // Queue up the next child (I can copy because I won't modify and copying is faster.)
-      $nodeStack['Parent'][$nodeStackIndex + 1] = $nodeStack['Parent'][$nodeStackIndex]['childNodes'][$currentChild];
-
-      // Work out if it is a short child tag.
-      $iGrandChildCount = count($nodeStack['Parent'][$nodeStackIndex + 1]['childNodes']);
-      $shortGrandChild = (($iGrandChildCount == 0) AND (implode('',$nodeStack['Parent'][$nodeStackIndex + 1]['textParts'])==''));
-
-      ///////////////////////////////////////////
-      // Assemble the attribute string first.
-      $attrStr = '';
-      foreach($nodeStack['Parent'][$nodeStackIndex + 1]['attributes'] as $key=>$val) {
-        // Should we hilight the attribute?
-        if ($hilightIsActive AND in_array($currentXpath.'/attribute::'.$key, $this->hilightXpathList)) {
-          $hiAttrStart = chr(2);
-          $hiAttrEnd   = chr(3);
-        } else {
-          $hiAttrStart = $hiAttrEnd = '';
-        }
-        $attrStr .= ' '.$hiAttrStart.$key.'="'.$val.'"'.$hiAttrEnd;
-      }
-
-      ///////////////////////////////////////////
-      // Work out what goes before and after the tag content
-
-      $beforeTagContent = $currentIndent;
-      if ($shortGrandChild) $afterTagContent = '/>';
-      else                  $afterTagContent = '>';
-
-      // Check whether the xml-tag is to be hilighted.
-      if ($hilightIsActive) {
-        $currentXpath = $nodeStack['Parent'][$nodeStackIndex + 1]['xpath'];
-        if (in_array($currentXpath, $this->hilightXpathList)) {
-          // Yes we hilight
-          $beforeTagContent .= chr(2);
-          $afterTagContent  .= chr(3);
-        }
-      }
-      $beforeTagContent .= '<';
-//      if ($shortGrandChild) $afterTagContent .= $CR;
-      
-      ///////////////////////////////////////////
-      // Output the tag
-
-      $xmlOut .= $beforeTagContent
-                  .$nodeStack['Parent'][$nodeStackIndex + 1]['name'].$attrStr
-                  .$afterTagContent;
-
-      ///////////////////////////////////////////
-      // Carry on.            
-
-      // If it is a short tag, then we've already done this child, we just move to the next
-      if ($shortGrandChild) {
-        // Move to the next child, we need not go deeper in the tree.
-        $nodeStack['ChildIndex'][$nodeStackIndex]++;
-        // But if we are just exporting the one node we'd go no further.
-        if ($nodeStackIndex == 0) break;
-      } else {
-        // Else queue up the child going one deeper in the stack
-        $nodeStackIndex++;
-        // Start with it's first child
-        $nodeStack['ChildIndex'][$nodeStackIndex] = 0;
-      }
-    }
-
-    $result = $xmlOut;
-
-    // Repair what we "undid"
-    $OldPreceedingStringRef = $OldPreceedingStringValue;
-
-    ////////////////////////////////////////////
-
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-
-    return $result;
-  }
-     
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine           ------ Import the XML Source ------                               
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Reads a file or URL and parses the XML data.
-   *
-   * Parse the XML source and (upon success) store the information into an internal structure.
-   *
-   * @param     $fileName (string) Path and name (or URL) of the file to be read and parsed.
-   * @return              (bool)   TRUE on success, FALSE on failure (check getLastError())
-   * @see       importFromString(), getLastError(), 
-   */
-  function importFromFile($fileName) {
-    $status = FALSE;
-    $errStr = '';
-    do { // try-block
-      // Remember file name. Used in error output to know in which file it happend
-      $this->properties['xmlFile'] = $fileName;
-      // If we already have content, then complain.
-      if (!empty($this->nodeRoot)) {
-        $errStr = 'Called when this object already contains xml data. Use reset().';
-        break; // try-block
-      }
-      // The the source is an url try to fetch it.
-      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
-        $xmlString = @implode('', @file($fileName));
-        if (!empty($xmlString)) {
-          $status = TRUE;
-        } else {
-          $errStr = "The url '{$fileName}' could not be found or read.";
-        }
-        break; // try-block
-      } 
-      
-      // Reaching this point we're dealing with a real file (not an url). Check if the file exists and is readable.
-      if (!is_readable($fileName)) { // Read the content from the file
-        $errStr = "File '{$fileName}' could not be found or read.";
-        break; // try-block
-      }
-      if (is_dir($fileName)) {
-        $errStr = "'{$fileName}' is a directory.";
-        break; // try-block
-      }
-      // Read the file
-      if (!($fp = @fopen($fileName, 'rb'))) {
-        $errStr = "Failed to open '{$fileName}' for read.";
-        break; // try-block
-      }
-      $xmlString = fread($fp, filesize($fileName));
-      @fclose($fp);
-      
-      $status = TRUE;
-    } while (FALSE);
-    
-    if (!$status) {
-      $this->_displayError('In importFromFile(): '. $errStr, __LINE__, __FILE__, FALSE);
-      return FALSE;
-    }
-    return $this->importFromString($xmlString);
-  }
-  
-  /**
-   * Reads a string and parses the XML data.
-   *
-   * Parse the XML source and (upon success) store the information into an internal structure.
-   * If a parent xpath is given this means that XML data is to be *appended* to that parent.
-   *
-   * ### If a function uses setLastError(), then say in the function header that getLastError() is useful.
-   *
-   * @param  $xmlString           (string) Name of the string to be read and parsed.
-   * @param  $absoluteParentPath  (string) Node to append data too (see above)
-   * @return                      (bool)   TRUE on success, FALSE on failure 
-   *                                       (check getLastError())
-   */
-  function importFromString($xmlString, $absoluteParentPath = '') {
-    $bDebugThisFunction = FALSE;
-
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction("importFromString");
-      echo "Importing from string of length ".strlen($xmlString)." to node '$absoluteParentPath'\n<br>";
-      echo "Parser options:\n<br>";
-      print_r($this->parseOptions);
-    }
-
-    $status = FALSE;
-    $errStr = '';
-    do { // try-block
-      // If we already have content, then complain.
-      if (!empty($this->nodeRoot) AND empty($absoluteParentPath)) {
-        $errStr = 'Called when this object already contains xml data. Use reset() or pass the parent Xpath as 2ed param to where tie data will append.';
-        break; // try-block
-      }
-      // Check whether content has been read.
-      if (empty($xmlString)) {
-        // Nothing to do!!
-        $status = TRUE;
-        // If we were importing to root, build a blank root.
-        if (empty($absoluteParentPath)) {
-          $this->_createSuperRoot();
-        }
-        $this->reindexNodeTree();
-//        $errStr = 'This xml document (string) was empty';
-        break; // try-block
-      } else {
-        $xmlString = $this->_translateAmpersand($xmlString);
-      }
-      
-      // Restart our node index with a root entry.
-      $nodeStack = array();
-      $this->parseStackIndex = 0;
-
-      // If a parent xpath is given this means that XML data is to be *appended* to that parent.
-      if (!empty($absoluteParentPath)) {
-        // Check if parent exists
-        if (!isSet($nodeIndex[$absoluteParentPath])) {
-          $errStr = "You tried to append XML data to a parent '$absoluteParentPath' that does not exist.";
-          break; // try-block
-        } 
-        // Add it as the starting point in our array.
-        $this->nodeStack[0] =& $nodeIndex[$absoluteParentPath];
-      } else {
-        // Build a 'super-root'
-        $this->_createSuperRoot();
-        // Put it in as the start of our node stack.
-        $this->nodeStack[0] =& $this->nodeRoot;
-      }
-
-      // Point our text buffer reference at the next text part of the root
-      $this->parsedTextLocation =& $this->nodeStack[0]['textParts'][];
-      $this->parsInCData = 0;
-      // We cache this now.
-      $this->parseSkipWhiteCache = isSet($this->parseOptions[XML_OPTION_SKIP_WHITE]) ? $this->parseOptions[XML_OPTION_SKIP_WHITE] : FALSE;
-      
-      // Create an XML parser.
-      $parser = xml_parser_create();
-      // Set default XML parser options.
-      if (is_array($this->parseOptions)) {
-        foreach($this->parseOptions as $key => $val) {
-          xml_parser_set_option($parser, $key, $val);
-        }
-      }
-      
-      // Set the object and the element handlers for the XML parser.
-      xml_set_object($parser, $this);
-      xml_set_element_handler($parser, '_handleStartElement', '_handleEndElement');
-      xml_set_character_data_handler($parser, '_handleCharacterData');
-      xml_set_default_handler($parser, '_handleDefaultData');
-      xml_set_processing_instruction_handler($parser, '_handlePI');
-     
-      if ($bDebugThisFunction)
-       $this->_profileFunction($aStartTime, "Setup for parse");
-
-      // Parse the XML source and on error generate an error message.
-      if (!xml_parse($parser, $xmlString, TRUE)) {
-        $source = empty($this->properties['xmlFile']) ? 'string' : 'file ' . basename($this->properties['xmlFile']) . "'";
-        $errStr = "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));
-        break; // try-block
-      }
-      
-      // Free the parser.
-      @xml_parser_free($parser);
-      // And we don't need this any more.
-      $this->nodeStack = array();
-
-      if ($bDebugThisFunction)
-        $this->_profileFunction($aStartTime, "Parse Object");
-
-      $this->reindexNodeTree();
-
-      if ($bDebugThisFunction) {
-        print_r(array_keys($this->nodeIndex));
-      }
-
-      if ($bDebugThisFunction)
-       $this->_profileFunction($aStartTime, "Reindex Object");
-      
-      $status = TRUE;
-    } while (FALSE);
-    
-    if (!$status) {
-      $this->_displayError('In importFromString(): '. $errStr, __LINE__, __FILE__, FALSE);
-      $bResult = FALSE;
-    } else {
-      $bResult = TRUE;
-    }
-
-    ////////////////////////////////////////////
-
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $bResult);
-    }
-
-    return $bResult;
-  }
-  
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine               ------  XML Handlers  ------                                  
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * 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.
-   *
-   * @param $parser     (int)    Handler for accessing the current XML parser.
-   * @param $name       (string) Name of the opening tag found in the document.
-   * @param $attributes (array)  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__, __FILE__);
-      return;
-    }
-
-    // Trim accumulated text if necessary.
-    if ($this->parseSkipWhiteCache) {
-      $iCount = count($this->nodeStack[$this->parseStackIndex]['textParts']);
-      $this->nodeStack[$this->parseStackIndex]['textParts'][$iCount-1] = rtrim($this->parsedTextLocation);
-    } 
-
-    if ($this->bDebugXmlParse) {
-      echo "<blockquote>" . htmlspecialchars("Start node: <".$nodeName . ">")."<br>";
-      echo "Appended to stack entry: $this->parseStackIndex<br>\n";
-      echo "Text part before element is: ".htmlspecialchars($this->parsedTextLocation);
-      /*
-      echo "<pre>";
-      $dataPartsCount = count($this->nodeStack[$this->parseStackIndex]['textParts']);
-      for ($i = 0; $i < $dataPartsCount; $i++) {
-        echo "$i:". htmlspecialchars($this->nodeStack[$this->parseStackIndex]['textParts'][$i])."\n";
-      }
-      echo "</pre>";
-      */
-    }
-
-    // Add a node and set path to current.
-    if (!$this->_internalAppendChild($this->parseStackIndex, $nodeName)) {
-      $this->_displayError('Internal error during parse of XML file at line'. xml_get_current_line_number($parser) .'. Empty name.', __LINE__, __FILE__);
-      return;
-    }    
-
-    // We will have gone one deeper then in the stack.
-    $this->parseStackIndex++;
-
-    // Point our parseTxtBuffer reference at the new node.
-    $this->parsedTextLocation =& $this->nodeStack[$this->parseStackIndex]['textParts'][0];
-    
-    // Set the attributes.
-    if (!empty($attributes)) {
-      if ($this->bDebugXmlParse) {
-        echo 'Attributes: <br>';
-        print_r($attributes);
-        echo '<br>';
-      }
-      $this->nodeStack[$this->parseStackIndex]['attributes'] = $attributes;
-    }
-  }
-  
-  /**
-   * Handles closing XML tags while parsing.
-   *
-   * While parsing a XML document for each closing tag this method is called.
-   *
-   * @param $parser (int)    Handler for accessing the current XML parser.
-   * @param $name   (string) Name of the closing tag found in the document.
-   * @see       _handleStartElement(), _handleCharacterData()
-   */
-  function _handleEndElement($parser, $name) {
-    if (($this->parsedTextLocation=='') 
-        && empty($this->nodeStack[$this->parseStackIndex]['textParts'])) {
-      // We reach this point when parsing a tag of format <foo/>. The 'textParts'-array 
-      // should stay empty and not have an empty string in it.
-    } else {
-      // Trim accumulated text if necessary.
-      if ($this->parseSkipWhiteCache) {
-        $iCount = count($this->nodeStack[$this->parseStackIndex]['textParts']);
-        $this->nodeStack[$this->parseStackIndex]['textParts'][$iCount-1] = rtrim($this->parsedTextLocation);
-      }
-    }
-
-    if ($this->bDebugXmlParse) {
-      echo "Text part after element is: ".htmlspecialchars($this->parsedTextLocation)."<br>\n";
-      echo htmlspecialchars("Parent:<{$this->parseStackIndex}>, End-node:</$name> '".$this->parsedTextLocation) . "'<br>Text nodes:<pre>\n";
-      $dataPartsCount = count($this->nodeStack[$this->parseStackIndex]['textParts']);
-      for ($i = 0; $i < $dataPartsCount; $i++) {
-        echo "$i:". htmlspecialchars($this->nodeStack[$this->parseStackIndex]['textParts'][$i])."\n";
-      }
-      var_dump($this->nodeStack[$this->parseStackIndex]['textParts']);
-      echo "</pre></blockquote>\n";
-    }
-
-    // Jump back to the parent element.
-    $this->parseStackIndex--;
-
-    // Set our reference for where we put any more whitespace
-    $this->parsedTextLocation =& $this->nodeStack[$this->parseStackIndex]['textParts'][];
-
-    // Note we leave the entry in the stack, as it will get blanked over by the next element
-    // at this level.  The safe thing to do would be to remove it too, but in the interests 
-    // of performance, we will not bother, as were it to be a problem, then it would be an
-    // internal bug anyway.
-    if ($this->parseStackIndex < 0) {
-      $this->_displayError('Internal error during parse of XML file at line'. xml_get_current_line_number($parser) .'. Empty name.', __LINE__, __FILE__);
-      return;
-    }    
-  }
-  
-  /**
-   * 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.
-   *
-   * @param $parser (int)    Handler for accessing the current XML parser.
-   * @param $text   (string) Character data found in the document.
-   * @see       _handleStartElement(), _handleEndElement()
-   */
-  function _handleCharacterData($parser, $text) {
-  
-    if ($this->parsInCData >0) $text = $this->_translateAmpersand($text, $reverse=TRUE);
-    
-    if ($this->bDebugXmlParse) echo "Handling character data: '".htmlspecialchars($text)."'<br>";
-    if ($this->parseSkipWhiteCache AND !empty($text) AND !$this->parsInCData) {
-      // Special case CR. CR always comes in a separate data. Trans. it to '' or ' '. 
-      // If txtBuffer is already ending with a space use '' otherwise ' '.
-      $bufferHasEndingSpace = (empty($this->parsedTextLocation) OR substr($this->parsedTextLocation, -1) === ' ') ? TRUE : FALSE;
-      if ($text=="\n") {
-        $text = $bufferHasEndingSpace ? '' : ' ';
-      } else {
-        if ($bufferHasEndingSpace) {
-          $text = ltrim(preg_replace('/\s+/', ' ', $text));
-        } else {
-          $text = preg_replace('/\s+/', ' ', $text);
-        }
-      }
-      if ($this->bDebugXmlParse) echo "'Skip white space' is ON. reduced to : '" .htmlspecialchars($text) . "'<br>";
-    }
-    $this->parsedTextLocation .= $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 $parser (int)    Handler for accessing the current XML parser.
-   * @param $text   (string) Character data found in the document.
-   * @see       _handleStartElement(), _handleEndElement()
-   */
-  function _handleDefaultData($parser, $text) {
-    do { // try-block
-      if (!strcmp($text, '<![CDATA[')) {
-        $this->parsInCData++;
-      } elseif (!strcmp($text, ']]>')) {
-        $this->parsInCData--;
-        if ($this->parsInCData < 0) $this->parsInCData = 0;
-      }
-      $this->parsedTextLocation .= $this->_translateAmpersand($text, $reverse=TRUE);
-      if ($this->bDebugXmlParse) echo "Default handler data: ".htmlspecialchars($text)."<br>";    
-      break; // try-block
-    } while (FALSE); // END try-block
-  }
-  
-  /**
-   * Handles processing instruction (PI)
-   *
-   * A processing instruction has the following format: 
-   * <?  target data  ? > e.g.  <? dtd version="1.0" ? >
-   *
-   * Currently I have no bether idea as to left it 'as is' and treat the PI data as normal 
-   * text (and adding the surrounding PI-tags <? ? >). 
-   *
-   * @param     $parser (int)    Handler for accessing the current XML parser.
-   * @param     $target (string) Name of the PI target. E.g. XML, PHP, DTD, ... 
-   * @param     $data   (string) Associative array containing a list of
-   * @see       PHP's manual "xml_set_processing_instruction_handler"
-   */
-  function _handlePI($parser, $target, $data) {
-    //echo("pi data=".$data."end"); exit;
-    $data = $this->_translateAmpersand($data, $reverse=TRUE);
-    $this->parsedTextLocation .= "<?{$target} {$data}?>";
-    return TRUE;
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine          ------  Node Tree Stuff  ------                                    
-  //-----------------------------------------------------------------------------------------
-
-  /**
-   * Creates a super root node.
-   */
-  function _createSuperRoot() {
-    // Build a 'super-root'
-    $this->nodeRoot = $this->emptyNode;
-    $this->nodeRoot['name']      = '';
-    $this->nodeRoot['parentNode'] = NULL;
-    $this->nodeIndex[''] =& $this->nodeRoot;
-  }
-
-  /**
-   * Adds a new node to the XML document tree during xml parsing.
-   *
-   * 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.  This method is a much watered down
-   * version of appendChild(), used in parsing an xml file only.
-   * 
-   * It is assumed that adding starts with root and progresses through the
-   * document in parse order.  New nodes must have a corresponding parent. And
-   * once we have read the </> tag for the element we will never need to add
-   * any more data to that node.  Otherwise the add will be ignored or fail.
-   *
-   * The function is faciliated by a nodeStack, which is an array of nodes that
-   * we have yet to close.
-   *
-   * @param   $stackParentIndex (int)    The index into the nodeStack[] of the parent
-   *                                     node to which the new node should be added as 
-   *                                     a child. *READONLY*
-   * @param   $nodeName         (string) Name of the new node. *READONLY*
-   * @return                    (bool)   TRUE if we successfully added a new child to 
-   *                                     the node stack at index $stackParentIndex + 1,
-   *                                     FALSE on error.
-   */
-  function _internalAppendChild($stackParentIndex, $nodeName) {
-    // This call is likely to be executed thousands of times, so every 0.01ms counts.
-    // If you want to debug this function, you'll have to comment the stuff back in
-    //$bDebugThisFunction = FALSE;
-    
-    /*
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction("_internalAppendChild");
-      echo "Current Node (parent-index) and the child to append : '{$stackParentIndex}' +  '{$nodeName}' \n<br>";
-    }
-    */
-     //////////////////////////////////////
-
-    if (!isSet($this->nodeStack[$stackParentIndex])) {
-      $errStr = "Invalid parent. You tried to append the tag '{$nodeName}' to an non-existing parent in our node stack '{$stackParentIndex}'.";
-      $this->_displayError('In _internalAppendChild(): '. $errStr, __LINE__, __FILE__, FALSE); 
-
-      /*
-      if ($bDebugThisFunction)
-        $this->_closeDebugFunction($aStartTime, FALSE);
-      */
-
-      return FALSE;
-    }
-
-    // Retrieve the parent node from the node stack.  This is the last node at that 
-    // depth that we have yet to close.  This is where we should add the text/node.
-    $parentNode =& $this->nodeStack[$stackParentIndex];
-          
-    // Brand new node please
-    $newChildNode = $this->emptyNode;
-    
-    // Save the vital information about the node.
-    $newChildNode['name'] = $nodeName;
-    $parentNode['childNodes'][] =& $newChildNode;
-    
-    // Add to our node stack
-    $this->nodeStack[$stackParentIndex + 1] =& $newChildNode;
-
-    /*
-    if ($bDebugThisFunction) {
-      echo "The new node received index: '".($stackParentIndex + 1)."'\n";
-      foreach($this->nodeStack as $key => $val) echo "$key => ".$val['name']."\n"; 
-      $this->_closeDebugFunction($aStartTime, TRUE);
-    }
-    */
-
-    return TRUE;
-  }
-  
-  /**
-   * Update nodeIndex and every node of the node-tree. 
-   *
-   * Call after you have finished any tree modifications other wise a match with 
-   * an xPathQuery will produce wrong results.  The $this->nodeIndex[] is recreated 
-   * and every nodes optimization data is updated.  The optimization data is all the
-   * data that is duplicate information, would just take longer to find. Child nodes 
-   * with value NULL are removed from the tree.
-   *
-   * By default the modification functions in this component will automatically re-index
-   * the nodes in the tree.  Sometimes this is not the behaver you want. To surpress the 
-   * reindex, set the functions $autoReindex to FALSE and call reindexNodeTree() at the 
-   * end of your changes.  This sometimes leads to better code (and less CPU overhead).
-   *
-   * Sample:
-   * =======
-   * Given the xml is <AAA><B/>.<B/>.<B/></AAA> | Goal is <AAA>.<B/>.</AAA>  (Delete B[1] and B[3])
-   *   $xPathSet = $xPath->match('//B'); # Will result in array('/AAA[1]/B[1]', '/AAA[1]/B[2]', '/AAA[1]/B[3]');
-   * Three ways to do it.
-   * 1) Top-Down  (with auto reindexing) - Safe, Slow and you get easily mix up with the the changing node index
-   *    removeChild('/AAA[1]/B[1]'); // B[1] removed, thus all B[n] become B[n-1] !!
-   *    removeChild('/AAA[1]/B[2]'); // Now remove B[2] (That originaly was B[3])
-   * 2) Bottom-Up (with auto reindexing) -  Safe, Slow and the changing node index (caused by auto-reindex) can be ignored.
-   *    for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-   *      if ($i==1) continue; 
-   *      removeChild($xPathSet[$i]);
-   *    }
-   * 3) // Top-down (with *NO* auto reindexing) - Fast, Safe as long as you call reindexNodeTree()
-   *    foreach($xPathSet as $xPath) {
-   *      // Specify no reindexing
-   *      if ($xPath == $xPathSet[1]) continue; 
-   *      removeChild($xPath, $autoReindex=FALSE);
-   *      // The object is now in a slightly inconsistent state.
-   *    }
-   *    // Finally do the reindex and the object is consistent again
-   *    reindexNodeTree();
-   *
-   * @return (bool) TRUE on success, FALSE otherwise.
-   * @see _recursiveReindexNodeTree()
-   */
-  function reindexNodeTree() {
-    //return;
-    $this->_indexIsDirty = FALSE;
-    $this->nodeIndex = array();
-    $this->nodeIndex[''] =& $this->nodeRoot;
-    // Quick out for when the tree has no data.
-    if (empty($this->nodeRoot)) return TRUE;
-    return $this->_recursiveReindexNodeTree('');
-  }
-  
-
-  /**
-   * Create the ids that are accessable through the generate-id() function
-   */
-  function _generate_ids() {
-    // If we have generated them already, then bail.
-    if (isset($this->nodeIndex['']['generate_id'])) return;
-
-    // keys generated are the string 'id0' . hexatridecimal-based (0..9,a-z) index
-    $aNodeIndexes = array_keys($this->nodeIndex);
-    $idNumber = 0;
-    foreach($aNodeIndexes as $index => $key) {
-//      $this->nodeIndex[$key]['generated_id'] = 'id' . base_convert($index,10,36);
-      // Skip attribute and text nodes.
-      // ### Currently don't support attribute and text nodes.
-      if (strstr($key, 'text()') !== FALSE) continue;
-      if (strstr($key, 'attribute::') !== FALSE) continue;
-      $this->nodeIndex[$key]['generated_id'] = 'idPhpXPath' . $idNumber;
-
-      // Make the id's sequential so that we can test predictively.
-      $idNumber++;
-    }
-  }
-
-  /**
-   * Here's where the work is done for reindexing (see reindexNodeTree)
-   *
-   * @param  $absoluteParentPath (string) the xPath to the parent node
-   * @return                     (bool)   TRUE on success, FALSE otherwise.
-   * @see reindexNodeTree()
-   */
-  function _recursiveReindexNodeTree($absoluteParentPath) {
-    $parentNode =& $this->nodeIndex[$absoluteParentPath];
-    
-    // Check for any 'dead' child nodes first and concate the text parts if found.
-    for ($iChildIndex=sizeOf($parentNode['childNodes'])-1; $iChildIndex>=0; $iChildIndex--) {
-      // Check if the child node still exits (it may have been removed).
-      if (!empty($parentNode['childNodes'][$iChildIndex])) continue;
-      // Child node was removed. We got to merge the text parts then.
-      $parentNode['textParts'][$iChildIndex] .= $parentNode['textParts'][$iChildIndex+1];
-      array_splice($parentNode['textParts'], $iChildIndex+1, 1); 
-      array_splice($parentNode['childNodes'], $iChildIndex, 1);
-    }
-
-    // Now start a reindex.
-    $contextHash = array();
-    $childSize = sizeOf($parentNode['childNodes']);
-
-    // If there are no children, we have to treat this specially:
-    if ($childSize == 0) {
-      // Add a dummy text node.
-      $this->nodeIndex[$absoluteParentPath.'/text()[1]'] =& $parentNode;
-    } else {
-      for ($iChildIndex=0; $iChildIndex<$childSize; $iChildIndex++) {
-        $childNode =& $parentNode['childNodes'][$iChildIndex];
-        // Make sure that there is a text-part in front of every node. (May be empty)
-        if (!isSet($parentNode['textParts'][$iChildIndex])) $parentNode['textParts'][$iChildIndex] = '';
-        // Count the nodes with same name (to determine their context position)
-        $childName = $childNode['name'];
-        if (empty($contextHash[$childName])) { 
-          $contextPos = $contextHash[$childName] = 1;
-        } else {
-          $contextPos = ++$contextHash[$childName];
-        }
-        // Make the node-index hash
-        $newPath = $absoluteParentPath . '/' . $childName . '['.$contextPos.']';
-
-        // ### Note ultimately we will end up supporting text nodes as actual nodes.
-
-        // Preceed with a dummy entry for the text node.
-        $this->nodeIndex[$absoluteParentPath.'/text()['.($childNode['pos']+1).']'] =& $childNode;
-        // Then the node itself
-        $this->nodeIndex[$newPath] =& $childNode;
-
-        // Now some dummy nodes for each of the attribute nodes.
-        $iAttributeCount = sizeOf($childNode['attributes']);
-        if ($iAttributeCount > 0) {
-          $aAttributesNames = array_keys($childNode['attributes']);
-          for ($iAttributeIndex = 0; $iAttributeIndex < $iAttributeCount; $iAttributeIndex++) {
-            $attribute = $aAttributesNames[$iAttributeIndex];
-            $newAttributeNode = $this->emptyNode;
-            $newAttributeNode['name'] = $attribute;
-            $newAttributeNode['textParts'] = array($childNode['attributes'][$attribute]);
-            $newAttributeNode['contextPos'] = $iAttributeIndex;
-            $newAttributeNode['xpath'] = "$newPath/attribute::$attribute";
-            $newAttributeNode['parentNode'] =& $childNode;
-            $newAttributeNode['depth'] =& $parentNode['depth'] + 2;
-            // Insert the node as a master node, not a reference, otherwise there will be 
-            // variable "bleeding".
-            $this->nodeIndex["$newPath/attribute::$attribute"] = $newAttributeNode;
-          }
-        }
-
-        // Update the node info (optimisation)
-        $childNode['parentNode'] =& $parentNode;
-        $childNode['depth'] = $parentNode['depth'] + 1;
-        $childNode['pos'] = $iChildIndex;
-        $childNode['contextPos'] = $contextHash[$childName];
-        $childNode['xpath'] = $newPath;
-        $this->_recursiveReindexNodeTree($newPath);
-
-        // Follow with a dummy entry for the text node.
-        $this->nodeIndex[$absoluteParentPath.'/text()['.($childNode['pos']+2).']'] =& $childNode;
-      }
-
-      // Make sure that their is a text-part after the last node.
-      if (!isSet($parentNode['textParts'][$iChildIndex])) $parentNode['textParts'][$iChildIndex] = '';
-    }
-
-    return TRUE;
-  }
-  
-  /** 
-   * Clone a node and it's child nodes.
-   *
-   * NOTE: If the node has children you *MUST* use the reference operator!
-   *       E.g. $clonedNode =& cloneNode($node);
-   *       Otherwise the children will not point back to the parent, they will point 
-   *       back to your temporary variable instead.
-   *
-   * @param   $node (mixed)  Either a node (hash array) or an abs. Xpath to a node in 
-   *                         the current doc
-   * @return        (&array) A node and it's child nodes.
-   */
-  function &cloneNode($node, $recursive=FALSE) {
-    if (is_string($node) AND isSet($this->nodeIndex[$node])) {
-      $node = $this->nodeIndex[$node];
-    }
-    // Copy the text-parts ()
-    $textParts = $node['textParts'];
-    $node['textParts'] = array();
-    foreach ($textParts as $key => $val) {
-      $node['textParts'][] = $val;
-    }
-    
-    $childSize = sizeOf($node['childNodes']);
-    for ($i=0; $i<$childSize; $i++) {
-      $childNode =& $this->cloneNode($node['childNodes'][$i], TRUE);  // copy child 
-      $node['childNodes'][$i] =& $childNode; // reference the copy
-      $childNode['parentNode'] =& $node;      // child references the parent.
-    }
-    
-    if (!$recursive) {
-      //$node['childNodes'][0]['parentNode'] = null;
-      //print "<pre>";
-      //var_dump($node);
-    }
-    return $node;
-  }
-  
-  
-/** Nice to have but __sleep() has a bug. 
-    (2002-2 PHP V4.1. See bug #15350)
-  
-  /**
-   * PHP cals this function when you call PHP's serialize. 
-   *
-   * It prevents cyclic referencing, which is why print_r() of an XPath object doesn't work.
-   *
-  function __sleep() {
-    // Destroy recursive pointers
-    $keys = array_keys($this->nodeIndex);
-    $size = sizeOf($keys);
-    for ($i=0; $i<$size; $i++) {
-      unset($this->nodeIndex[$keys[$i]]['parentNode']);
-    }
-    unset($this->nodeIndex);
-  }
-  
-  /**
-   * PHP cals this function when you call PHP's unserialize. 
-   *
-   * It reindexes the node-tree
-   *
-  function __wakeup() {
-    $this->reindexNodeTree();
-  }
-  
-*/
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath            ------  XPath Query / Evaluation Handlers  ------                      
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Matches (evaluates) an XPath query
-   *
-   * This method tries to evaluate an XPath query by parsing it. A XML source must 
-   * have been imported before this method is able to work.
-   *
-   * @param     $xPathQuery  (string) XPath query to be evaluated.
-   * @param     $baseXPath   (string) (default is super-root) XPath query to a single document node, 
-   *                                  from which the XPath query should  start evaluating.
-   * @return                 (mixed)  The result of the XPath expression.  Either:
-   *                                    node-set (an ordered collection of absolute references to nodes without duplicates) 
-   *                                    boolean (true or false) 
-   *                                    number (a floating-point number) 
-   *                                    string (a sequence of UCS characters) 
-   */
-  function match($xPathQuery, $baseXPath='') {
-    if ($this->_indexIsDirty) $this->reindexNodeTree();
-    
-    // Replace a double slashes, because they'll cause problems otherwise.
-    static $slashes2descendant = array(
-        '//@' => '/descendant_or_self::*/attribute::', 
-        '//'  => '/descendant_or_self::node()/', 
-        '/@'  => '/attribute::');
-    // Stupid idea from W3C to take axes name containing a '-' (dash) !!!
-    // We replace the '-' with '_' to avoid the conflict with the minus operator.
-    static $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',
-        'x-lower'          => 'x_lower',
-        'x-upper'          => 'x_upper',
-        'generate-id'      => 'generate_id');
-    
-    if (empty($xPathQuery)) return array();
-
-    // Special case for when document is empty.
-    if (empty($this->nodeRoot)) return array();
-
-    if (!isSet($this->nodeIndex[$baseXPath])) {
-            $xPathSet = $this->_resolveXPathQuery($baseXPath,'match');
-            if (sizeOf($xPathSet) !== 1) {
-                $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-                return FALSE;
-            }
-            $baseXPath = $xPathSet[0];
-    }
-
-    // We should possibly do a proper syntactical parse, but instead we will cheat and just
-    // remove any literals that could make things very difficult for us, and replace them with
-    // special tags.  Then we can treat the xPathQuery much more easily as JUST "syntax".  Provided 
-    // there are no literals in the string, then we can guarentee that most of the operators and 
-    // syntactical elements are indeed elements and not just part of a literal string.
-    $processedxPathQuery = $this->_removeLiterals($xPathQuery);
-    
-    // Replace a double slashes, and '-' (dash) in axes names.
-    $processedxPathQuery = strtr($processedxPathQuery, $slashes2descendant);
-    $processedxPathQuery = strtr($processedxPathQuery, $dash2underscoreHash);
-
-    // Build the context
-    $context = array('nodePath' => $baseXPath, 'pos' => 1, 'size' => 1);
-
-    // The primary syntactic construct in XPath is the expression.
-    $result = $this->_evaluateExpr($processedxPathQuery, $context);
-
-    // We might have been returned a string.. If so convert back to a literal
-    $literalString = $this->_asLiteral($result);
-    if ($literalString != FALSE) return $literalString;
-    else return $result;
-  }
-
-  /**
-   * Alias for the match function
-   *
-   * @see match()
-   */
-  function evaluate($xPathQuery, $baseXPath='') {
-    return $this->match($xPathQuery, $baseXPath);
-  }
-
-  /**
-   * Parse out the literals of an XPath expression.
-   *
-   * Instead of doing a full lexical parse, we parse out the literal strings, and then
-   * Treat the sections of the string either as parts of XPath or literal strings.  So
-   * this function replaces each literal it finds with a literal reference, and then inserts
-   * the reference into an array of strings that we can access.  The literals can be accessed
-   * later from the literals associative array.
-   *
-   * Example:
-   *  XPathExpr = /AAA[@CCC = "hello"]/BBB[DDD = 'world'] 
-   *  =>  literals: array("hello", "world")
-   *      return value: /AAA[@CCC = $1]/BBB[DDD = $2] 
-   *
-   * Note: This does not interfere with the VariableReference syntactical element, as these 
-   * elements must not start with a number.
-   *
-   * @param  $xPathQuery  (string) XPath expression to be processed
-   * @return              (string) The XPath expression without the literals.
-   *                              
-   */
-  function _removeLiterals($xPathQuery) {
-    // What comes first?  A " or a '?
-    if (!preg_match(":^([^\"']*)([\"'].*)$:", $xPathQuery, $aMatches)) {
-      // No " or ' means no more literals.
-      return $xPathQuery;
-    }
-    
-    $result = $aMatches[1];
-    $remainder = $aMatches[2];
-    // What kind of literal?
-    if (preg_match(':^"([^"]*)"(.*)$:', $remainder, $aMatches)) {
-      // A "" literal.
-      $literal = $aMatches[1];
-      $remainder = $aMatches[2];
-    } else if (preg_match(":^'([^']*)'(.*)$:", $remainder, $aMatches)) {
-      // A '' literal.
-      $literal = $aMatches[1];
-      $remainder = $aMatches[2];
-    } else {
-      $this->_displayError("The '$xPathQuery' argument began a literal, but did not close it.", __LINE__, __FILE__);
-    }
-
-    // Store the literal
-    $literalNumber = count($this->axPathLiterals);
-    $this->axPathLiterals[$literalNumber] = $literal;
-    $result .= '$'.$literalNumber;
-    return $result.$this->_removeLiterals($remainder);
-  }
-
-  /**
-   * Returns the given string as a literal reference.
-   *
-   * @param $string (string) The string that we are processing
-   * @return        (mixed)  The literal string.  FALSE if the string isn't a literal reference.
-   */
-  function _asLiteral($string) {
-    if (empty($string)) return FALSE;
-    if (empty($string[0])) return FALSE;
-    if ($string[0] == '$') {
-      $remainder = substr($string, 1);
-      if (is_numeric($remainder)) {
-        // We have a string reference then.
-        $stringNumber = (int)$remainder;
-        if ($stringNumber >= count($this->axPathLiterals)) {
-            $this->_displayError("Internal error.  Found a string reference that we didn't set in xPathQuery: '$xPathQuery'.", __LINE__, __FILE__);
-            return FALSE;
-        }
-        return $this->axPathLiterals[$stringNumber];
-      }
-    }
-
-    // It's not a reference then.
-    return FALSE;
-  }
-  
-  /**
-   * Adds a literal to our array of literals
-   *
-   * In order to make sure we don't interpret literal strings as XPath expressions, we have to
-   * encode literal strings so that we know that they are not XPaths.
-   *
-   * @param $string (string) The literal string that we need to store for future access
-   * @return        (mixed)  A reference string to this literal.
-   */
-  function _addLiteral($string) {
-    // Store the literal
-    $literalNumber = count($this->axPathLiterals);
-    $this->axPathLiterals[$literalNumber] = $string;
-    $result = '$'.$literalNumber;
-    return $result;
-  }
-
-  /**
-   * 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  $xPathQuery  (string)   XPath query to be evaluated.
-   * @param  $context     (array)    An associative array the describes the context from which
-   *                                 to evaluate the XPath Expr.  Contains three members:
-   *                                  'nodePath' => The absolute XPath expression to the context node
-   *                                  'size' => The context size
-   *                                  'pos' => The context position
-   * @return              (mixed)    The result of the XPath expression.  Either:
-   *                                 node-set (an ordered collection of nodes without duplicates) 
-   *                                 boolean (true or false) 
-   *                                 number (a floating-point number) 
-   *                                 string (a sequence of UCS characters) 
-   * @see    evaluate()
-   */
-  function _evaluateExpr($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) {
-      $aStartTime = $this->_beginDebugFunction("_evaluateExpr");
-      echo "Path: $xPathQuery\n";
-      echo "Context:";
-      $this->_printContext($context);
-      echo "\n";
-    }
-    
-    // Numpty check
-    if (!isset($xPathQuery) || ($xPathQuery == '')) {
-      $this->_displayError("The \$xPathQuery argument must have a value.", __LINE__, __FILE__);
-      return FALSE;
-    }
-
-    // At the top level we deal with booleans.  Only if the Expr is just an AdditiveExpr will 
-    // the result not be a boolean.
-    //
-    // [14]    Expr               ::= OrExpr 
-    // [21]    OrExpr             ::= AndExpr  
-    //                                | OrExpr 'or' AndExpr  
-    // [22]    AndExpr            ::= EqualityExpr  
-    //                                | AndExpr 'and' EqualityExpr  
-    // [23]    EqualityExpr       ::= RelationalExpr  
-    //                                | EqualityExpr '=' RelationalExpr  
-    //                                | EqualityExpr '!=' RelationalExpr  
-    // [24]    RelationalExpr     ::= AdditiveExpr  
-    //                                | RelationalExpr '<' AdditiveExpr  
-    //                                | RelationalExpr '>' AdditiveExpr  
-    //                                | RelationalExpr '<=' AdditiveExpr  
-    //                                | RelationalExpr '>=' AdditiveExpr  
-    // [25]    AdditiveExpr       ::= MultiplicativeExpr  
-    //                                | AdditiveExpr '+' MultiplicativeExpr  
-    //                                | AdditiveExpr '-' MultiplicativeExpr  
-    // [26]    MultiplicativeExpr ::= UnaryExpr  
-    //                                | MultiplicativeExpr MultiplyOperator UnaryExpr  
-    //                                | MultiplicativeExpr 'div' UnaryExpr  
-    //                                | MultiplicativeExpr 'mod' UnaryExpr  
-    // [27]    UnaryExpr          ::= UnionExpr  
-    //                                | '-' UnaryExpr 
-    // [18]    UnionExpr          ::= PathExpr  
-    //                                | UnionExpr '|' PathExpr 
-    //
-    // NOTE: The effect of the above grammar is that the order of precedence is 
-    // (lowest precedence first): 
-    // 1) or 
-    // 2) and 
-    // 3) =, != 
-    // 4) <=, <, >=, > 
-    // 5) +, -
-    // 6) *, div, mod
-    // 7) - (negate)
-    // 8) |
-    //
-    // Between these syntactical elements we get PathExprs.
-
-    // Do while false loop
-    do {
-      // An expression can be one of these, and we should catch these "first".
-      //
-      // [15]    PrimaryExpr    ::= VariableReference  
-      //                            | '(' Expr ')'  
-      //                            | Literal  
-      //                            | Number  
-      //                            | FunctionCall 
-
-      // If it is surrounded by () then trim the brackets
-      while (preg_match(":^\((.*)\):", $xPathQuery, $aMatches)) {
-        $xPathQuery = $aMatches[1];
-      }
-
-      /////////////////////////////////////////////
-      // Easy outs
-
-      // Is it a number?
-      if (is_numeric($xPathQuery)) {
-        $result = $xPathQuery;
-        break;
-      }
-
-      // If it starts with $, and the remainder is a number, then it's a string.
-      $literal = $this->_asLiteral($xPathQuery);
-      if ($literal !== FALSE) {
-          $result = $xPathQuery;
-          break;
-      }
-
-      // Is it a function?
-      {
-        // 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, $xPathQuery, $aMatch) && $xPathQuery != "text()") {
-          $function = $aMatch[1];
-          $data     = $aMatch[2];
-          // It is possible that we will get "a() or b()" which will match as function "a" with
-          // arguments ") or b(" which is clearly wrong... _bracketsCheck() should catch this.
-          if ($this->_bracketsCheck($data)) {
-            if ($bDebugThisFunction) echo "XPathExpr: $xPathQuery is a $function() function call:\n";
-            if (in_array($function, $this->functions)) {
-              $result = $this->_evaluateFunction($function, $data, $context);
-              break;
-            } 
-          }
-        }
-      }
-
-      /////////////////////////////////////////////
-      // 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.
-      $opSize = sizeOf($this->operators);
-      for ($i=0; $i<$opSize; $i++) {
-        // Have we found an operator yet?
-        if ($position >0) break;
-        $operator = $this->operators[$i];
-        // Quickcheck. If not present don't wast time searching 'the hard way'
-        if (strpos($xPathQuery, $operator)===FALSE) continue;
-        // Special check
-        $position = $this->_searchString($xPathQuery, $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 ($xPathQuery[$position-1]) {
-            case '<' : 
-              $position--;
-              $operator = '<=';
-              break;
-            case '>' : 
-              $position--;
-              $operator = '>=';
-              break;
-            case '!' : 
-              $position--;
-              $operator = '!=';
-              break;
-            default:
-              // It's a pure = operator then.
-          }
-        }
-        if ($operator == '*') {
-          // http://www.w3.org/TR/xpath#exprlex:
-          // "If there is a preceding token and the preceding token is not one of @, ::, (, [, 
-          // or an Operator, then a * must be recognized as a MultiplyOperator and an NCName must 
-          // be recognized as an OperatorName."
-
-          // Get some substrings.
-          $character = substr($xPathQuery, $position - 1, 1);
-        
-          // Check whether it's a multiply operator or a name test.
-          if (strchr('/@:([', $character) != FALSE) {
-            // Don't use the operator.
-            $operator = '';
-            $position = -1;
-            continue;
-          }
-        }
-
-        // Extremely annoyingly, we could have a node name like "for-each" and we should not
-        // parse this as a "-" operator.  So if the first char of the right operator is alphabetic,
-        // then this is NOT an interger operator.
-        if (strchr('-+*', $operator) != FALSE) {
-          $rightOperand = trim(substr($xPathQuery, $position + strlen($operator)));
-          if (strlen($rightOperand) > 1) {
-            if (preg_match(':^\D$:', $rightOperand[0])) {
-              // Don't use the operator.
-              $operator = '';
-              $position = -1;
-              continue;
-            }
-          }
-        }
-
-      } // end while each($this->operators)
-      
-      /////////////////////////////////////////////
-      // Check whether an operator was found.
-      if ($position <= 0) {
-        // No operator.  Means we have a PathExpr then.  Go to the next level.
-        $result = $this->_evaluatePathExpr($xPathQuery, $context);
-        break;
-      }
-
-      /////////////////////////////////////////////
-      // Recursively process the operator
-
-      // Check the kind of operator.
-      switch ($operator) {
-        case ' or ': 
-        case ' and ':
-          $operatorType = 'Boolean';
-          break;
-        case '<=':
-        case '<': 
-        case '>=':
-        case '>':
-        case '+': 
-        case '-': 
-        case '*':
-        case ' div ':
-        case ' mod ':
-          $operatorType = 'Integer';
-          break;
-        case ' | ':
-          $operatorType = 'NodeSet';
-          break;
-        case '=': 
-        case '!=':
-          $operatorType = 'Multi';
-          break;
-        default:
-            $this->_displayError("Internal error.  Default case of switch statement reached.", __LINE__, __FILE__);
-      }
-
-      if ($bDebugThisFunction) echo "\nOperator is a [$operator]($operatorType operator) at pos '$position'";
-
-      /////////////////////////////////////////////
-      // Get the operands
-
-      // Get the left and the right part of the expression.
-      $leftOperand  = trim(substr($xPathQuery, 0, $position));
-      $rightOperand = trim(substr($xPathQuery, $position + strlen($operator)));
-      if ($bDebugThisFunction) echo "\nLEFT:[$leftOperand]  oper:[$operator]  RIGHT:[$rightOperand]";
-    
-      // Remove whitespaces.
-      $leftOperand  = trim($leftOperand);
-      $rightOperand = trim($rightOperand);
-
-      /////////////////////////////////////////////
-      // Evaluate the operands
-
-      // Evaluate the left and the right part.
-      if (!empty($leftOperand)) {
-        if ($bDebugThisFunction) echo "\nEvaluating LEFT:[$leftOperand]\n";
-        $left = $this->_evaluateExpr($leftOperand, $context);
-        if ($bDebugThisFunction) {echo "$leftOperand evals as:\n"; print_r($left); }
-      }
-      
-      // If it is a boolean operator, it's possible we don't need to evaluate the right part.
-
-      // Only evaluate the right part if we need to.
-      $bEvaluateRightPart = TRUE;
-      $right = '';
-      if ($operatorType == 'Boolean') {
-        $right = FALSE;
-        // Is the left part false?
-        $left = $this->_handleFunction_boolean($left, $context);
-        if (!$left and ($operator == ' and ')) {
-          $bEvaluateRightPart = FALSE;
-          $right = FALSE;
-        } else if ($left and ($operator == ' or ')) {
-          $bEvaluateRightPart = FALSE;
-          $right = TRUE;
-        } 
-      } 
-      
-      // And do we need to?
-      if ($bEvaluateRightPart) {
-        if ($bDebugThisFunction) echo "\nEvaluating RIGHT:[$rightOperand]\n";
-        $right = $this->_evaluateExpr($rightOperand, $context);
-        if ($bDebugThisFunction) {echo "$rightOperand evals as:\n"; print_r($right); }
-      } else {
-        if ($bDebugThisFunction) echo "\nNo point in evaluating the right predicate: [$rightOperand]";
-      }
-
-      /////////////////////////////////////////////
-      // Combine the operands
-
-      // Work out how to treat the multi operator
-      if ($operatorType == 'Multi') {
-        if (is_bool($left) || is_bool($right)) {
-          $operatorType = 'Boolean';
-        } elseif (is_int($left) || is_int($right)) {
-          $operatorType = 'Integer';
-        } elseif (!is_array($left) || !is_array($right)) {
-          $operatorType = 'String';
-        } elseif (is_array($left) || is_array($right)) {
-          $operatorType = 'Integer';
-        } else {
-          $operatorType = 'String';
-        }
-        if ($bDebugThisFunction) echo "Equals operator is a $operatorType operator\n";
-      }
-
-      // Handle the operator depending on the operator type.
-      switch ($operatorType) {
-        case 'Boolean':
-          {
-            // Boolify the arguments.  (The left arg is already a bool)
-            $right = $this->_handleFunction_boolean($right, $context);
-            switch ($operator) {
-              case '=': // Compare the two results.
-                $result = (bool)($left == $right); 
-                break;
-              case ' or ': // Return the two results connected by an 'or'.
-                $result = (bool)( $left or $right );
-                break;
-              case ' and ': // Return the two results connected by an 'and'.
-                $result = (bool)( $left and $right );
-                break;
-              case '!=': // Check whether the two results are not equal.
-                $result = (bool)( $left != $right );
-                break;
-              default:
-                $this->_displayError("Internal error.  Default case of switch statement reached.", __LINE__, __FILE__);
-            }
-          }
-          break;
-        case 'Integer':
-          {
-            // Convert both left and right operands into numbers.
-            if (empty($left) && ($operator == '-')) {
-              // There may be no left operator if the op is '-'
-              $left = 0;
-            } else {
-              $left = $this->_handleFunction_number($left, $context);
-            }
-            $right = $this->_handleFunction_number($right, $context);
-            if ($bDebugThisFunction) echo "\nLeft is $left, Right is $right\n";
-            switch ($operator) {
-              case '=': // Compare the two results.
-                $result = (bool)($left == $right); 
-                break;
-              case '!=': // Compare the two results.
-                $result = (bool)($left != $right); 
-                break;
-              case '+': // Return the result by adding one result to the other.
-                $result = $left + $right;
-                break;
-              case '-': // Return the result by decrease one result by the other.
-                $result = $left - $right;
-                break;
-              case '*': // Return a multiplication of the two results.
-                $result =  $left * $right;
-                break;
-              case ' div ': // Return a division of the two results.
-                $result = $left / $right;
-                break;
-              case ' mod ': // Return a modulo division of the two results.
-                $result = $left % $right;
-                break;
-              case '<=': // Compare the two results.
-                $result = (bool)( $left <= $right );
-                break;
-              case '<': // Compare the two results.
-                $result = (bool)( $left < $right );
-                break;
-              case '>=': // Compare the two results.
-                $result = (bool)( $left >= $right );
-                break;
-              case '>': // Compare the two results.
-                $result = (bool)( $left > $right );
-                break;
-              default:
-                $this->_displayError("Internal error.  Default case of switch statement reached.", __LINE__, __FILE__);
-            }
-          }
-          break;
-        case 'NodeSet':
-          // Add the nodes to the result set
-          $result = array_merge($left, $right);
-          // Remove duplicated nodes.
-          $result = array_unique($result);
-
-          // Preserve doc order if there was more than one query.
-          if (count($result) > 1) {
-            $result = $this->_sortByDocOrder($result);
-          }
-          break;
-        case 'String':
-            $left = $this->_handleFunction_string($left, $context);
-            $right = $this->_handleFunction_string($right, $context);
-            if ($bDebugThisFunction) echo "\nLeft is $left, Right is $right\n";
-            switch ($operator) {
-              case '=': // Compare the two results.
-                $result = (bool)($left == $right); 
-                break;
-              case '!=': // Compare the two results.
-                $result = (bool)($left != $right); 
-                break;
-              default:
-                $this->_displayError("Internal error.  Default case of switch statement reached.", __LINE__, __FILE__);
-            }
-          break;
-        default:
-          $this->_displayError("Internal error.  Default case of switch statement reached.", __LINE__, __FILE__);
-      }
-    } while (FALSE);
-
-    //////////////////////////////////////////////
-
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-    // Return the result.
-    return $result;
-  }
-  
-  /**
-   * Internal recursive evaluate an Path expression.
-   *
-   * @param  $PathExpr   (string) PathExpr syntactical element
-   * @param  $context    (array)  The context from which to evaluate
-   * @return             (mixed)  The result of the XPath expression.  Either:
-   *                               node-set (an ordered collection of nodes without duplicates) 
-   *                               boolean (true or false) 
-   *                               number (a floating-point number) 
-   *                               string (a sequence of UCS characters) 
-   * @see    evaluate()
-   */
-  function _evaluatePathExpr($PathExpr, $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) {
-      $aStartTime = $this->_beginDebugFunction("_evaluatePathExpr");
-      echo "PathExpr: $PathExpr\n";
-      echo "Context:";
-      $this->_printContext($context);
-      echo "\n";
-    }
-    
-    // Numpty check
-    if (empty($PathExpr)) {
-      $this->_displayError("The \$PathExpr argument must have a value.", __LINE__, __FILE__);
-      return FALSE;
-    }
-    //////////////////////////////////////////////
-
-    // mini syntax check
-    if (!$this->_bracketsCheck($PathExpr)) {
-      $this->_displayError('While parsing an XPath query, in the PathExpr "' .
-      $PathExpr.
-      '", there was an invalid number of brackets or a bracket mismatch.', __LINE__, __FILE__);
-    }
-    // Save the current path.
-    $this->currentXpathQuery = $PathExpr;
-    // Split the path at every slash *outside* a bracket.
-    $steps = $this->_bracketExplode('/', $PathExpr);
-    if ($bDebugThisFunction) { echo "<hr>Split the path '$PathExpr' 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.
-    $result = $this->_evaluateStep($steps, $context);
-
-    // Preserve doc order if there was more than one result
-    if (count($result) > 1) {
-      $result = $this->_sortByDocOrder($result);
-    }
-    //////////////////////////////////////////////
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-    // Return the result.
-    return $result;
-  }
-
-  /**
-   * Sort an xPathSet by doc order.
-   *
-   * @param  $xPathSet (array) Array of full paths to nodes that need to be sorted
-   * @return           (array) Array containing the same contents as $xPathSet, but
-   *                           with the contents in doc order
-   */
-  function _sortByDocOrder($xPathSet) {
-    // 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) {
-      $aStartTime = $this->_beginDebugFunction(__LINE__.":_sortByDocOrder(xPathSet:[".count($xPathSet)."])");
-      echo "xPathSet:\n";
-      print_r($xPathSet);
-      echo "<hr>\n";
-    }
-    //////////////////////////////////////////////
-
-    $aResult = array();
-
-    // Spot some common shortcuts.
-    if (count($xPathSet) < 1) {
-      $aResult = $xPathSet;
-    } else {
-      // Build an array of doc-pos indexes.
-      $aDocPos = array();
-      $nodeCount = count($this->nodeIndex);
-      $aPaths = array_keys($this->nodeIndex);
-      if ($bDebugThisFunction) {
-        echo "searching for path indices in array_keys(this->nodeIndex)...\n";
-        //print_r($aPaths);
-      }
-
-      // The last index we found.  In general the elements will be in groups
-      // that are themselves in order.
-      $iLastIndex = 0;
-      foreach ($xPathSet as $path) {
-        // Cycle round the nodes, starting at the last index, looking for the path.
-        $foundNode = FALSE;
-        for ($iIndex = $iLastIndex; $iIndex < $nodeCount + $iLastIndex; $iIndex++) {
-          $iThisIndex = $iIndex % $nodeCount;
-          if (!strcmp($aPaths[$iThisIndex],$path)) {
-            // we have found the doc-position index of the path 
-            $aDocPos[] = $iThisIndex;
-            $iLastIndex = $iThisIndex;
-            $foundNode = TRUE;
-            break;
-          }
-        }
-        if ($bDebugThisFunction) {
-          if (!$foundNode)
-            echo "Error: $path not found in \$this->nodeIndex\n";
-          else 
-            echo "Found node after ".($iIndex - $iLastIndex)." iterations\n";
-        }
-      }
-      // Now count the number of doc pos we have and the number of results and
-      // confirm that we have the same number of each.
-      $iDocPosCount = count($aDocPos);
-      $iResultCount = count($xPathSet);
-      if ($iDocPosCount != $iResultCount) {
-        if ($bDebugThisFunction) {
-          echo "count(\$aDocPos)=$iDocPosCount; count(\$result)=$iResultCount\n";
-          print_r(array_keys($this->nodeIndex));
-        }
-        $this->_displayError('Results from _InternalEvaluate() are corrupt.  '.
-                                      'Do you need to call reindexNodeTree()?', __LINE__, __FILE__);
-      }
-
-      // Now sort the indexes.
-      sort($aDocPos);
-
-      // And now convert back to paths.
-      $iPathCount = count($aDocPos);
-      for ($iIndex = 0; $iIndex < $iPathCount; $iIndex++) {
-        $aResult[] = $aPaths[$aDocPos[$iIndex]];
-      }
-    }
-
-    // Our result from the function is this array.
-    $result = $aResult;
-
-    //////////////////////////////////////////////
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-    // Return the result.
-    return $result;
-  }
-
-  /**
-   * Evaluate a step from a XPathQuery expression at a specific contextPath.
-   *
-   * Steps are the arguments of a XPathQuery when divided by a '/'. A contextPath is a 
-   * absolute XPath (or vector of XPaths) to a starting node(s) from which the step should 
-   * be evaluated.
-   *
-   * @param  $steps        (array) Vector containing the remaining steps of the current 
-   *                               XPathQuery expression.
-   * @param  $context      (array) The context from which to evaluate
-   * @return               (array) Vector of absolute XPath's as a result of the step 
-   *                               evaluation.  The results will not necessarily be in doc order
-   * @see    evaluate()
-   */
-  function _evaluateStep($steps, $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) {
-      $aStartTime = $this->_beginDebugFunction(__LINE__.":_evaluateStep");
-      echo "Context:";
-      $this->_printContext($context);
-      echo "\n";
-      echo "Steps: ";
-      print_r($steps);
-      echo "<hr>\n";
-    }
-    //////////////////////////////////////////////
-
-    $result = array(); // Create an empty array for saving the abs. XPath's found.
-
-    $contextPaths = array();   // Create an array to save the new contexts.
-    $step = trim(array_shift($steps)); // Get this step.
-    if ($bDebugThisFunction) echo __LINE__.":Evaluating step $step\n";
-    
-    $axis = $this->_getAxis($step, $context); // Get the axis of the current step.
-    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'])) {
-        $contextPaths = array_merge($contextPaths, $axis['node-test']);  // Add the results to the list of contexts.
-      } else {
-        $contextPaths[] = $axis['node-test']; // Add the result to the list of contexts.
-      }
-    } else {
-      $method = '_handleAxis_' . $axis['axis']; // Create the name of the method.
-    
-      // Check whether the axis handler is defined. If not display an error message.
-      if (!method_exists($this, $method)) {
-        $this->_displayError('While parsing an XPath query, the axis ' .
-        $axis['axis'] . ' could not be handled, because this version does not support this axis.', __LINE__, __FILE__);
-      }
-      if ($bDebugThisFunction) echo __LINE__.":Calling user method $method\n";        
-      
-      // Perform an axis action.
-      $contextPaths = $this->$method($axis, $context['nodePath']);
-      if ($bDebugThisFunction) { echo __LINE__.":We found these contexts from this step:\n"; print_r( $contextPaths ); echo "\n";}
-      
-      // Check whether there are predicates.
-      if (count($contextPaths) > 0 && count($axis['predicate']) > 0) {
-        if ($bDebugThisFunction) echo __LINE__.":Filtering contexts by predicate...\n";
-        
-        // Check whether each node fits the predicates.
-        $contextPaths = $this->_checkPredicates($contextPaths, $axis['predicate']);
-      }
-    }
-    
-    // Check whether there are more steps left.
-    if (count($steps) > 0) {
-      if ($bDebugThisFunction) echo __LINE__.":Evaluating next step given the context of the first step...\n";        
-      
-      // Continue the evaluation of the next steps.
-
-      // Run through the array.
-      $size = sizeOf($contextPaths);
-      for ($pos=0; $pos<$size; $pos++) {
-        // Build new context
-        $newContext = array('nodePath' => $contextPaths[$pos], 'size' => $size, 'pos' => $pos + 1);
-        if ($bDebugThisFunction) echo __LINE__.":Evaluating step for the {$contextPaths[$pos]} context...\n";
-        // Call this method for this single path.
-        $xPathSetNew = $this->_evaluateStep($steps, $newContext);
-        if ($bDebugThisFunction) {echo "New results for this context:\n"; print_r($xPathSetNew);}
-        $result = array_merge($result, $xPathSetNew);
-      }
-
-      // Remove duplicated nodes.
-      $result = array_unique($result);
-    } else {
-      $result = $contextPaths; // Save the found contexts.
-    }
-    
-    //////////////////////////////////////////////
-    if ($bDebugThisFunction) $this->_closeDebugFunction($aStartTime, $result);
-    
-    // Return the result.
-    return $result;
-  }
-  
-  /**
-   * Checks whether a node matches predicates.
-   *
-   * This method checks whether a list of nodes passed to this method match
-   * a given list of predicates. 
-   *
-   * @param  $xPathSet   (array)  Array of full paths of all nodes to be tested.
-   * @param  $predicates (array)  Array of predicates to use.
-   * @return             (array)  Vector of absolute XPath's that match the given predicates.
-   * @see    _evaluateStep()
-   */
-  function _checkPredicates($xPathSet, $predicates) {
-    // 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) {
-      $aStartTime = $this->_beginDebugFunction("_checkPredicates(Nodes:[$xPathSet], Predicates:[$predicates])");
-      echo "XPathSet:";
-      print_r($xPathSet);
-      echo "Predicates:";
-      print_r($predicates);
-      echo "<hr>";
-    }
-    //////////////////////////////////////////////
-    // Create an empty set of nodes.
-    $result = array();
-
-    // Run through all predicates.
-    $pSize = sizeOf($predicates);
-    for ($j=0; $j<$pSize; $j++) {
-      $predicate = $predicates[$j]; 
-      if ($bDebugThisFunction) echo "Evaluating predicate \"$predicate\"\n";
-
-      // This will contain all the nodes that match this predicate
-      $aNewSet = array();
-      
-      // Run through all nodes.
-      $contextSize = count($xPathSet);
-      for ($contextPos=0; $contextPos<$contextSize; $contextPos++) {
-        $xPath = $xPathSet[$contextPos];
-
-        // Build the context for this predicate
-        $context = array('nodePath' => $xPath, 'size' => $contextSize, 'pos' => $contextPos + 1);
-      
-        // Check whether the predicate is just an number.
-        if (preg_match('/^\d+$/', $predicate)) {
-          if ($bDebugThisFunction) echo "Taking short cut and calling _handleFunction_position() directly.\n";
-          // Take a short cut.  If it is just a position, then call 
-          // _handleFunction_position() directly.  70% of the
-          // time this will be the case. ## N.S
-//          $check = (bool) ($predicate == $context['pos']);
-          $check = (bool) ($predicate == $this->_handleFunction_position('', $context));
-        } else {                
-          // Else do the predicate check the long and through way.
-          $check = $this->_evaluateExpr($predicate, $context);
-        }
-        if ($bDebugThisFunction) {
-          echo "Evaluating the predicate returned "; 
-          var_dump($check); 
-          echo "\n";
-        }
-
-        if (is_int($check)) { // Check whether it's an integer.
-          // Check whether it's the current position.
-          $check = (bool) ($check == $this->_handleFunction_position('', $context));
-        } else {
-          $check = (bool) ($this->_handleFunction_boolean($check, $context));
-//          if ($bDebugThisFunction) {echo $this->_handleFunction_string($check, $context);}
-        }
-
-        if ($bDebugThisFunction) echo "Node $xPath matches predicate $predicate: " . (($check) ? "TRUE" : "FALSE") ."\n";
-
-        // Do we add it?
-        if ($check) $aNewSet[] = $xPath;
-      }
-       
-      // Use the newly filtered list.
-      $xPathSet = $aNewSet;
-
-      if ($bDebugThisFunction) {echo "Node set now contains : "; print_r($xPathSet); }
-    }
-
-    $result = $xPathSet;
-
-    //////////////////////////////////////////////
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-    // Return the array of nodes.
-    return $result;
-  }
-  
-  /**
-   * Evaluates an XPath function
-   *
-   * This method evaluates a given XPath function with its arguments on a
-   * specific node of the document.
-   *
-   * @param  $function      (string) Name of the function to be evaluated.
-   * @param  $arguments     (string) String containing the arguments being
-   *                                 passed to the function.
-   * @param  $context       (array)  The context from which to evaluate
-   * @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, $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) {
-      $aStartTime = $this->_beginDebugFunction("_evaluateFunction");
-      if (is_array($arguments)) {
-        echo "Arguments:\n";
-        print_r($arguments);
-      } else {
-        echo "Arguments: $arguments\n";
-      }
-      echo "Context:";
-      $this->_printContext($context);
-      echo "\n";
-      echo "<hr>\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 query, ".
-        "the function \"$function\" could not be handled, because this ".
-        "version does not support this function.", __LINE__, __FILE__);
-    }
-    if ($bDebugThisFunction) echo "Calling function $method($arguments)\n"; 
-    
-    // Return the result of the function.
-    $result = $this->$method($arguments, $context);
-    
-    //////////////////////////////////////////////
-    // Return the nodes found.
-    if ($bDebugThisFunction) {
-      $this->_closeDebugFunction($aStartTime, $result);
-    }
-    // Return the result.
-    return $result;
-  }
-    
-  /**
-   * Checks whether a node matches a node-test.
-   *
-   * This method checks whether a node in the document matches a given node-test.
-   * A node test is something like text(), node(), or an element name.
-   *
-   * @param  $contextPath (string)  Full xpath of the node, which should be tested for 
-   *                                matching the node-test.
-   * @param  $nodeTest    (string)  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($contextPath, $nodeTest) {
-    if ($nodeTest == '*') {
-      // * matches all element nodes.
-      return (!preg_match(':/[^/]+\(\)\[\d+\]$:U', $contextPath));
-    }
-    elseif (preg_match('/^[\w-:]+$/', $nodeTest)) {
-       // It's just a node name test.  It should end with "/$nodeTest[x]"
-       return (preg_match('"/'.$nodeTest.'\[\d+\]$"', $contextPath));
-    }
-    elseif (preg_match('/\(/U', $nodeTest)) { // Check whether it's a function.
-      // 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->nodeIndex[$contextPath]['textParts']);
-          if (!empty($tmp)) {
-            return TRUE; // Add this node to the list of nodes.
-          }
-          break;
-/******** NOT supported (yet?)          
-        case 'comment':  // Check whether the node has some comment.
-          if (!empty($this->nodeIndex[$contextPath]['comment'])) {
-            return TRUE; // Add this node to the list of nodes.
-          }
-          break;
-        case 'processing-instruction':
-          $literal = $this->_afterstr($axis['node-test'], '('); // Get the literal argument.
-          $literal = substr($literal, 0, strlen($literal) - 1); // Cut the literal.
-          
-          // Check whether a literal was given.
-          if (!empty($literal)) {
-            // Check whether the node's processing instructions are matching the literals given.
-            if ($this->nodeIndex[$context]['processing-instructions'] == $literal) {
-              return TRUE; // Add this node to the node-set.
-            }
-          } else {
-            // Check whether the node has processing instructions.
-            if (!empty($this->nodeIndex[$contextPath]['processing-instructions'])) {
-              return TRUE; // Add this node to the node-set.
-            }
-          }
-          break;
-***********/            
-        default:  // Display an error message.
-          $this->_displayError('While parsing an XPath query there was an undefined function called "' .
-             str_replace($function, '<b>'.$function.'</b>', $this->currentXpathQuery) .'"', __LINE__, __FILE__);
-      }
-    }
-    else { // Display an error message.
-      $this->_displayError("While parsing the XPath query \"{$this->currentXpathQuery}\" ".
-        "an empty and therefore invalid node-test has been found.", __LINE__, __FILE__, FALSE);
-    }
-    
-    return FALSE; // Don't add this context.
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                    ------  XPath AXIS Handlers  ------                            
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Retrieves axis information from an XPath query step.
-   *
-   * This method tries to extract the name of the axis and its node-test
-   * from a given step of an XPath query at a given node.
-   *
-   * @param  $step     (string) String containing a step of an XPath query.
-   * @param  $context  (array)  The context from which to evaluate
-   * @return           (array)  Contains information about the axis found in the step.
-   * @see    _evaluateStep()
-   */
-  function _getAxis($step, $context) {
-    // Create an array to save the axis information.
-    $axis = array(
-      'axis'      => '',
-      'node-test' => '',
-      'predicate' => array()
-    );
-    
-    do { // parse block
-      $parseBlock = 1;
-
-      ///////////////////////////////////////////////////
-      // Spot the steps that won't come with an axis
-
-      // 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;
-      }
-
-      if ($step == '..') {
-        // Select the parent axis.
-        $axis['axis']      = 'parent';
-        $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;
-      }
-
-      ///////////////////////////////////////////////////
-      // Pull off the predicates
-
-      // Check whether there are predicates and add the predicate to the list 
-      // of predicates without []. Get contents of every [] found.
-      $groups = $this->_getEndGroups($step);
-//print_r($groups);
-      $groupCount = count($groups);
-      while (($groupCount > 0) && ($groups[$groupCount - 1][0] == '[')) {
-        // Remove the [] and add the predicate to the top of the list
-        $predicate = substr($groups[$groupCount - 1], 1, -1);
-        array_unshift($axis['predicate'], $predicate);
-        // Pop a group off the end of the list
-        array_pop($groups);
-        $groupCount--;
-      }
-
-      // Finally stick the rest back together and this is the rest of our step
-      if ($groupCount > 0) {
-        $step = implode('', $groups);
-      }
-
-      ///////////////////////////////////////////////////
-      // Pull off the axis
-
-      // Check for abbreviated syntax
-      if ($step[0] == '@') {
-        // Use the attribute axis and select the attribute.
-        $axis['axis']      = 'attribute';
-        $step = substr($step, 1);
-      } else {
-        // Check whether the axis is given in plain text.
-        if (preg_match("/^([^:]*)::(.*)$/", $step, $match)) {
-          // Split the step to extract axis and node-test.
-          $axis['axis'] = $match[1];
-          $step         = $match[2];
-        } else {
-          // The default axis is child
-          $axis['axis'] = 'child';
-        }
-      }
-
-      ///////////////////////////////////////////////////
-      // Process the rest which will either be a function or a node name
-
-      if ($step == "text()") {
-        // Handle the text node
-        $axis["node-test"] = "cdata";
-        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 (in_array($function, $this->functions)) {
-          // Save the evaluated function.
-          $axis['axis']      = 'function';
-          $axis['node-test'] = $this->_evaluateFunction($function, $data, $context);
-        } 
-        else {
-          $axis['node-test'] = $step;
-        }
-        break $parseBlock;
-      }
-
-      // We have removed the axis and the predicates, all that is left is the node test.
-      $axis['node-test'] = $step;
-      if (!empty($this->parseOptions[XML_OPTION_CASE_FOLDING])) {
-        // Case in-sensitive
-        $axis['node-test'] = strtoupper($axis['node-test']);
-      }
-      
-    } 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 query, in the step ' .
-        str_replace($step, '<b>'.$step.'</b>', $this->currentXpathQuery) .
-        ' the invalid axis ' . $axis['axis'] . ' was found.', __LINE__, __FILE__, FALSE);
-    }
-    // Return the axis information.
-    return $axis;
-  }
-   
-
-  /**
-   * 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 '/'.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should 
-   *                               be processed.
-   * @return              (array)  A vector containing all nodes that were found, during 
-   *                               the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_child($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set to hold the results of the child matches
-    if ($axis["node-test"] == "cdata") {
-      if (!isSet($this->nodeIndex[$contextPath]['textParts']) ) return '';
-      $tSize = sizeOf($this->nodeIndex[$contextPath]['textParts']);
-      for ($i=1; $i<=$tSize; $i++) { 
-        $xPathSet[] = $contextPath . '/text()['.$i.']';
-      }
-    }
-    else {
-      // Get a list of all children.
-      $allChildren = $this->nodeIndex[$contextPath]['childNodes'];
-      
-      // Run through all children in the order they where set.
-      $cSize = sizeOf($allChildren);
-      for ($i=0; $i<$cSize; $i++) {
-        $childPath = $contextPath .'/'. $allChildren[$i]['name'] .'['. $allChildren[$i]['contextPos']  .']';
-        $textChildPath = $contextPath.'/text()['.($i + 1).']';
-        // Check the text node
-        if ($this->_checkNodeTest($textChildPath, $axis['node-test'])) { // node test check
-          $xPathSet[] = $textChildPath; // Add the child to the node-set.
-        }
-        // Check the actual node
-        if ($this->_checkNodeTest($childPath, $axis['node-test'])) { // node test check
-          $xPathSet[] = $childPath; // Add the child to the node-set.
-        }
-      }
-
-      // Finally there will be one more text node to try
-     $textChildPath = $contextPath.'/text()['.($cSize + 1).']';
-     // Check the text node
-     if ($this->_checkNodeTest($textChildPath, $axis['node-test'])) { // node test check
-       $xPathSet[] = $textChildPath; // Add the child to the node-set.
-     }
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath parent axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the 
-   *                               evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_parent($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Check whether the parent matches the node-test.
-    $parentPath = $this->getParentXPath($contextPath);
-    if ($this->_checkNodeTest($parentPath, $axis['node-test'])) {
-      $xPathSet[] = $parentPath; // Add this node to the list of nodes.
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath attribute axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_attribute($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Check whether all nodes should be selected.
-    $nodeAttr = $this->nodeIndex[$contextPath]['attributes'];
-    if ($axis['node-test'] == '*'  
-        || $axis['node-test'] == 'node()') {
-      foreach($nodeAttr as $key=>$dummy) { // Run through the attributes.
-        $xPathSet[] = $contextPath.'/attribute::'.$key; // Add this node to the node-set.
-      }
-    }
-    elseif (isset($nodeAttr[$axis['node-test']])) {
-      $xPathSet[] = $contextPath . '/attribute::'. $axis['node-test']; // Add this node to the node-set.
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-   
-  /**
-   * Handles the XPath self axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_self($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Check whether the context match the node-test.
-    if ($this->_checkNodeTest($contextPath, $axis['node-test'])) {
-      $xPathSet[] = $contextPath; // Add this node to the node-set.
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath descendant axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_descendant($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Get a list of all children.
-    $allChildren = $this->nodeIndex[$contextPath]['childNodes'];
-    
-    // Run through all children in the order they where set.
-    $cSize = sizeOf($allChildren);
-    for ($i=0; $i<$cSize; $i++) {
-      $childPath = $allChildren[$i]['xpath'];
-      // Check whether the child matches the node-test.
-      if ($this->_checkNodeTest($childPath, $axis['node-test'])) {
-        $xPathSet[] = $childPath; // Add the child to the list of nodes.
-      }
-      // Recurse to the next level.
-      $xPathSet = array_merge($xPathSet, $this->_handleAxis_descendant($axis, $childPath));
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath ancestor axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_ancestor($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-        
-    $parentPath = $this->getParentXPath($contextPath); // Get the parent of the current node.
-    
-    // Check whether the parent isn't super-root.
-    if (!empty($parentPath)) {
-      // Check whether the parent matches the node-test.
-      if ($this->_checkNodeTest($parentPath, $axis['node-test'])) {
-        $xPathSet[] = $parentPath; // Add the parent to the list of nodes.
-      }
-      // Handle all other ancestors.
-      $xPathSet = array_merge($this->_handleAxis_ancestor($axis, $parentPath), $xPathSet);
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath namespace axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_namespace($axis, $contextPath) {
-    $this->_displayError("The axis 'namespace is not suported'", __LINE__, __FILE__, FALSE);
-  }
-  
-  /**
-   * Handles the XPath following axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_following($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    do { // try-block
-      $node = $this->nodeIndex[$contextPath]; // Get the current node
-      $position = $node['pos'];               // Get the current tree position.
-      $parent = $node['parentNode'];
-      // Check if there is a following sibling at all; if not end.
-      if ($position >= sizeOf($parent['childNodes'])) break; // try-block
-      // Build the starting abs. XPath
-      $startXPath = $parent['childNodes'][$position+1]['xpath'];
-      // Run through all nodes of the document.
-      $nodeKeys = array_keys($this->nodeIndex);
-      $nodeSize = sizeOf($nodeKeys);
-      for ($k=0; $k<$nodeSize; $k++) {
-        if ($nodeKeys[$k] == $startXPath) break; // Check whether this is the starting abs. XPath
-      }
-      for (; $k<$nodeSize; $k++) {
-        // Check whether the node fits the node-test.
-        if ($this->_checkNodeTest($nodeKeys[$k], $axis['node-test'])) {
-          $xPathSet[] = $nodeKeys[$k]; // Add the node to the list of nodes.
-        }
-      }
-    } while(FALSE);
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath preceding axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_preceding($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Run through all nodes of the document.
-    foreach ($this->nodeIndex as $xPath=>$dummy) {
-      if (empty($xPath)) continue; // skip super-Root
-      
-      // Check whether this is the context node.
-      if ($xPath == $contextPath) {
-        break; // After this we won't look for more nodes.
-      }
-      if (!strncmp($xPath, $contextPath, strLen($xPath))) {
-        continue;
-      }
-      // Check whether the node fits the node-test.
-      if ($this->_checkNodeTest($xPath, $axis['node-test'])) {
-        $xPathSet[] = $xPath; // Add the node to the list of nodes.
-      }
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath following-sibling axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_following_sibling($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Get all children from the parent.
-    $siblings = $this->_handleAxis_child($axis, $this->getParentXPath($contextPath));
-    // 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'])) {
-          $xPathSet[] = $sibling; // Add the sibling to the list of nodes.
-        }
-      }
-      // Check if we reached *this* context node.
-      if ($sibling == $contextPath) {
-        $found = TRUE; // Continue looking for other siblings.
-      }
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath preceding-sibling axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_preceding_sibling($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Get all children from the parent.
-    $siblings = $this->_handleAxis_child($axis, $this->getParentXPath($contextPath));
-    
-    // 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 == $contextPath) {
-        break; // Don't continue looking for other siblings.
-      }
-      // Check whether the sibling matches the node-test.
-      if ($this->_checkNodeTest($sibling, $axis['node-test'])) {
-        $xPathSet[] = $sibling; // Add the sibling to the list of nodes.
-      }
-    }
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath descendant-or-self axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_descendant_or_self($axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Read the nodes.
-    $xPathSet = array_merge(
-                 $this->_handleAxis_self($axis, $contextPath),
-                 $this->_handleAxis_descendant($axis, $contextPath)
-               );
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  /**
-   * Handles the XPath ancestor-or-self axis.
-   *
-   * This method handles the XPath ancestor-or-self axis.
-   *
-   * @param  $axis        (array)  Array containing information about the axis.
-   * @param  $contextPath (string) xpath to starting node from which the axis should be processed.
-   * @return              (array)  A vector containing all nodes that were found, during the evaluation of the axis.
-   * @see    evaluate()
-   */
-  function _handleAxis_ancestor_or_self ( $axis, $contextPath) {
-    $xPathSet = array(); // Create an empty node-set.
-    
-    // Read the nodes.
-    $xPathSet = array_merge(
-                 $this->_handleAxis_ancestor($axis, $contextPath),
-                 $this->_handleAxis_self($axis, $contextPath)
-               );
-    return $xPathSet; // Return the nodeset.
-  }
-  
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                  ------  XPath FUNCTION Handlers  ------                          
-  //-----------------------------------------------------------------------------------------
-  
-   /**
-    * Handles the XPath function last.
-    *    
-    * @param  $arguments     (string) String containing the arguments that were passed to the function.
-    * @param  $context       (array)  The context from which to evaluate the function
-    * @return                (mixed)  Depending on the type of function being processed
-    * @see    evaluate()
-    */
-  function _handleFunction_last($arguments, $context) {
-    return $context['size'];
-  }
-  
-  /**
-   * Handles the XPath function position.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_position($arguments, $context) {
-    return $context['pos'];
-  }
-  
-  /**
-   * Handles the XPath function count.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_count($arguments, $context) {
-    // Evaluate the argument of the method as an XPath and return the number of results.
-    return count($this->_evaluateExpr($arguments, $context));
-  }
-  
-  /**
-   * Handles the XPath function id.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_id($arguments, $context) {
-    $arguments = trim($arguments);         // Trim the arguments.
-    $arguments = explode(' ', $arguments); // Now split the arguments into an array.
-    // Create a list of nodes.
-    $resultXPaths = array();
-    // Run through all nodes of the document.
-    $keys = array_keys($this->nodeIndex);
-    $kSize = $sizeOf($keys);
-    for ($i=0; $i<$kSize; $i++) {
-      if (empty($keys[$i])) continue; // skip super-Root
-      if (in_array($this->nodeIndex[$keys[$i]]['attributes']['id'], $arguments)) {
-        $resultXPaths[] = $context['nodePath']; // Add this node to the list of nodes.
-      }
-    }
-    return $resultXPaths; // Return the list of nodes.
-  }
-  
-  /**
-   * Handles the XPath function name.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_name($arguments, $context) {
-    // If the argument it omitted, it defaults to a node-set with the context node as its only member.
-    if (empty($arguments)) {
-      return $this->_addLiteral($this->nodeIndex[$context['nodePath']]['name']);
-    }
-
-    // Evaluate the argument to get a node set.
-    $nodeSet = $this->_evaluateExpr($arguments, $context);
-    if (!is_array($nodeSet)) return '';
-    if (count($nodeSet) < 1) return '';
-    if (!isset($this->nodeIndex[$nodeSet[0]])) return '';
-     // Return a reference to the name of the node.
-    return $this->_addLiteral($this->nodeIndex[$nodeSet[0]]['name']);
-  }
-  
-  /**
-   * Handles the XPath function string.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_string($arguments, $context) {
-    // Check what type of parameter is given
-    if (is_array($arguments)) {
-      // 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
-      if (!count($arguments)) $result = '';
-      else $result = $this->decodeEntities($this->wholeText($arguments[0]));
-    }
-    // Is it a literal string?
-    elseif (preg_match('/^[0-9]+(\.[0-9]+)?$/', $arguments) OR preg_match('/^\.[0-9]+$/', $arguments)) {
-      $number = doubleval($arguments); // Convert the digits to a number.
-      $result = strval($number); // Return the number.
-    }
-    elseif (is_bool($arguments)) { // Check whether it's TRUE or FALSE and return as string.
-      if ($arguments === TRUE)  $result = 'TRUE'; else $result = 'FALSE';
-    }
-    // a string is true if and only if its length is non-zero
-    elseif (($literal = $this->_asLiteral($arguments)) !== FALSE) {
-      return $literal;
-    }
-    elseif (!empty($arguments)) {
-      // Use the argument as an XPath.
-      $result = $this->_evaluateExpr($arguments, $context);
-      $result = $this->_handleFunction_string($result, $context);
-    }
-    else {
-      $result = '';  // Return an empty string.
-    }
-    return $result;
-  }
-  
-  /**
-   * Handles the XPath function concat.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_concat($arguments, $context) {
-    // Split the arguments.
-    $arguments = explode(',', $arguments);
-    // Run through each argument and evaluate it.
-    $size = sizeof($arguments);
-    for ($i=0; $i<$size; $i++) {
-      $arguments[$i] = trim($arguments[$i]);  // Trim each argument.
-      // Evaluate it.
-      $arguments[$i] = $this->_handleFunction_string($arguments[$i], $context);
-    }
-    $arguments = implode('', $arguments);  // Put the string together and return it.
-    return $this->_addLiteral($arguments);
-  }
-  
-  /**
-   * Handles the XPath function starts-with.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_starts_with($arguments, $context) {
-    // Get the arguments.
-    $first  = trim($this->_prestr($arguments, ','));
-    $second = trim($this->_afterstr($arguments, ','));
-    // Evaluate each argument.
-    $first  = $this->_handleFunction_string($first, $context);
-    $second = $this->_handleFunction_string($second, $context);
-    // Check whether the first string starts with the second one.
-    return  (bool) ereg('^'.$second, $first);
-  }
-  
-  /**
-   * Handles the XPath function contains.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_contains($arguments, $context) {
-    // 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->_handleFunction_string($first, $context);
-    $second = $this->_handleFunction_string($second, $context);
-    //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.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_substring_before($arguments, $context) {
-    // Get the arguments.
-    $first  = trim($this->_prestr($arguments, ','));
-    $second = trim($this->_afterstr($arguments, ','));
-    // Evaluate each argument.
-    $first  = $this->_handleFunction_string($first, $context);
-    $second = $this->_handleFunction_string($second, $context);
-    // Return the substring.
-    return $this->_addLiteral($this->_prestr(strval($first), strval($second)));
-  }
-  
-  /**
-   * Handles the XPath function substring-after.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_substring_after($arguments, $context) {
-    // Get the arguments.
-    $first  = trim($this->_prestr($arguments, ','));
-    $second = trim($this->_afterstr($arguments, ','));
-    // Evaluate each argument.
-    $first  = $this->_handleFunction_string($first, $context);
-    $second = $this->_handleFunction_string($second, $context);
-    // Return the substring.
-    return $this->_addLiteral($this->_afterstr(strval($first), strval($second)));
-  }
-  
-  /**
-   * Handles the XPath function substring.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_substring($arguments, $context) {
-    // Split the arguments.
-    $arguments = explode(",", $arguments);
-    $size = sizeOf($arguments);
-    for ($i=0; $i<$size; $i++) { // Run through all arguments.
-      $arguments[$i] = trim($arguments[$i]); // Trim the string.
-      // Evaluate each argument.
-      $arguments[$i] = $this->_handleFunction_string($arguments[$i], $context);
-    }
-    // Check whether a third argument was given and return the substring..
-    if (!empty($arguments[2])) {
-      return $this->_addLiteral(substr(strval($arguments[0]), $arguments[1] - 1, $arguments[2]));
-    } else {
-      return $this->_addLiteral(substr(strval($arguments[0]), $arguments[1] - 1));
-    }
-  }
-  
-  /**
-   * Handles the XPath function string-length.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_string_length($arguments, $context) {
-    $arguments = trim($arguments); // Trim the argument.
-    // Evaluate the argument.
-    $arguments = $this->_handleFunction_string($arguments, $context);
-    return strlen(strval($arguments)); // Return the length of the string.
-  }
-
-  /**
-   * 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  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                 (stri)g trimed string
-   * @see    evaluate()
-   */
-  function _handleFunction_normalize_space($arguments, $context) {
-    if (empty($arguments)) {
-      $arguments = $this->getParentXPath($context['nodePath']).'/'.$this->nodeIndex[$context['nodePath']]['name'].'['.$this->nodeIndex[$context['nodePath']]['contextPos'].']';
-    } else {
-       $arguments = $this->_handleFunction_string($arguments, $context);
-    }
-    $arguments = trim(preg_replace (";[[:space:]]+;s",' ',$arguments));
-    return $this->_addLiteral($arguments);
-  }
-
-  /**
-   * Handles the XPath function translate.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_translate($arguments, $context) {
-    $arguments = explode(',', $arguments); // Split the arguments.
-    $size = sizeOf($arguments);
-    for ($i=0; $i<$size; $i++) { // Run through all arguments.
-      $arguments[$i] = trim($arguments[$i]); // Trim the argument.
-      // Evaluate the argument.
-      $arguments[$i] = $this->_handleFunction_string($arguments[$i], $context);
-    }
-    // Return the translated string.
-    return $this->_addLiteral(strtr($arguments[0], $arguments[1], $arguments[2]));
-  }
-
-  /**
-   * Handles the XPath function boolean.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_boolean($arguments, $context) {
-    if (empty($arguments)) {
-      return FALSE; // Sorry, there were no arguments.
-    }
-    // a bool is dead obvious
-    elseif (is_bool($arguments)) {
-      return $arguments;
-    }
-    // a node-set is true if and only if it is non-empty
-    elseif (is_array($arguments)) {
-      return (count($arguments) > 0);
-    }
-    // a number is true if and only if it is neither positive or negative zero nor NaN 
-    // (Straight out of the XPath spec.. makes no sense?????)
-    elseif (preg_match('/^[0-9]+(\.[0-9]+)?$/', $arguments) || preg_match('/^\.[0-9]+$/', $arguments)) {
-      $number = doubleval($arguments);  // Convert the digits to a number.
-      // If number zero return FALSE else TRUE.
-      if ($number == 0) return FALSE; else return TRUE;
-    }
-    // a string is true if and only if its length is non-zero
-    elseif (($literal = $this->_asLiteral($arguments)) !== FALSE) {
-      return (strlen($literal) != 0);
-    }
-    // an object of a type other than the four basic types is converted to a boolean in a 
-    // way that is dependent on that type
-    else {
-      // Try to evaluate the argument as an XPath.
-      $result = $this->_evaluateExpr($arguments, $context);
-      if (is_string($result) && is_string($arguments) && (!strcmp($result, $arguments))) {
-        $this->_displayError("Loop detected in XPath expression.  Probably an internal error :o/.  _handleFunction_boolean($result)", __LINE__, __FILE__, FALSE);
-        return FALSE;
-      } else {
-        return $this->_handleFunction_boolean($result, $context);
-      }
-    }
-  }
-  
-  /**
-   * Handles the XPath function not.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_not($arguments, $context) {
-    // Return the negative value of the content of the brackets.
-    $bArgResult = $this->_handleFunction_boolean($arguments, $context);
-//echo "Before inversion: ".($bArgResult?"TRUE":"FALSE")."\n";
-    return !$bArgResult;
-  }
-  
-  /**
-   * Handles the XPath function TRUE.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_true($arguments, $context) {
-    return TRUE; // Return TRUE.
-  }
-  
-  /**
-   * Handles the XPath function FALSE.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_false($arguments, $context) {
-    return FALSE; // Return FALSE.
-  }
-  
-  /**
-   * Handles the XPath function lang.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_lang($arguments, $context) {
-    $arguments = trim($arguments); // Trim the arguments.
-    $currentNode = $this->nodeIndex[$context['nodePath']];
-    while (!empty($currentNode['name'])) { // Run through the ancestors.
-      // Check whether the node has an language attribute.
-      if (isSet($currentNode['attributes']['xml:lang'])) {
-        // Check whether it's the language, the user asks for; if so return TRUE else FALSE
-        return eregi('^'.$arguments, $currentNode['attributes']['xml:lang']);
-      }
-      $currentNode = $currentNode['parentNode']; // Move up to parent
-    } // End while
-    return FALSE;
-  }
-  
-  /**
-   * Handles the XPath function number.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_number($arguments, $context) {
-    // Check the type of argument.
-
-    // A string that is a number
-    if (is_numeric($arguments)) {
-      return doubleval($arguments); // Return the argument as a number.
-    }
-    // A bool
-    elseif (is_bool($arguments)) {  // Return TRUE/FALSE as a number.
-      if ($arguments === TRUE) return 1; else return 0;  
-    }
-    // A node set
-    elseif (is_array($arguments)) {
-      // Is converted to a string then handled like a string
-      $string = $this->_handleFunction_string($arguments, $context);
-      if (is_numeric($string))
-        return doubleval($string);
-    }
-    else {
-      // Try to evaluate the argument as an XPath.
-      $result = $this->_evaluateExpr($arguments, $context);
-      return $this->_handleFunction_number($result, $context);
-    }
-  }
-
-  /**
-   * Handles the XPath function sum.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_sum($arguments, $context) {
-    $arguments = trim($arguments); // Trim the arguments.
-    // Evaluate the arguments as an XPath query.
-    $result = $this->_evaluateExpr($arguments, $context);
-    $sum = 0; // Create a variable to save the sum.
-    // The sum function expects a node set as an argument.
-    if (is_array($result)) {
-      // Run through all results.
-      $size = sizeOf($result);
-      for ($i=0; $i<$size; $i++) {
-        $value = $this->_handleFunction_number($result[$i], $context);
-        $sum += doubleval($value); // Add it to the sum.
-      }
-    }
-    return $sum; // Return the sum.
-  }
-
-  /**
-   * Handles the XPath function floor.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_floor($arguments, $context) {
-    if (!is_numeric($arguments)) {
-      $arguments = $this->_handleFunction_number($arguments, $context);
-    }
-    $arguments = doubleval($arguments); // Convert the arguments to a number.
-    return floor($arguments);           // Return the result
-  }
-  
-  /**
-   * Handles the XPath function ceiling.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_ceiling($arguments, $context) {
-    if (!is_numeric($arguments)) {
-      $arguments = $this->_handleFunction_number($arguments, $context);
-    }
-    $arguments = doubleval($arguments); // Convert the arguments to a number.
-    return ceil($arguments);            // Return the result
-  }
-  
-  /**
-   * Handles the XPath function round.
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_round($arguments, $context) {
-    if (!is_numeric($arguments)) {
-      $arguments = $this->_handleFunction_number($arguments, $context);
-    }
-    $arguments = doubleval($arguments); // Convert the arguments to a number.
-    return round($arguments);           // Return the result
-  }
-
-  //-----------------------------------------------------------------------------------------
-  // XPath                  ------  XPath Extension FUNCTION Handlers  ------                          
-  //-----------------------------------------------------------------------------------------
-
-  /**
-   * Handles the XPath function x-lower.
-   *
-   * lower case a string.
-   *    string x-lower(string) 
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_x_lower($arguments, $context) {
-    // Evaluate the argument.
-    $string = $this->_handleFunction_string($arguments, $context);
-     // Return a reference to the lowercased string
-    return $this->_addLiteral(strtolower(strval($string)));
-  }
-
-  /**
-   * Handles the XPath function x-upper.
-   *
-   * upper case a string.
-   *    string x-upper(string) 
-   *   
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @see    evaluate()
-   */
-  function _handleFunction_x_upper($arguments, $context) {
-    // Evaluate the argument.
-    $string = $this->_handleFunction_string($arguments, $context);
-     // Return a reference to the lowercased string
-    return $this->_addLiteral(strtoupper(strval($string)));
-  }
-
-  /**
-   * Handles the XPath function generate-id.
-   *
-   * Produce a unique id for the first node of the node set.
-   * 
-   * Example usage, produces an index of all the nodes in an .xml document, where the content of each
-   * "section" is the exported node as XML.
-   *
-   *   $aFunctions = $xPath->match('//');
-   *   
-   *   foreach ($aFunctions as $Function) {
-   *       $id = $xPath->match("generate-id($Function)");
-   *       echo "<a href='#$id'>$Function</a><br>";
-   *   }
-   *   
-   *   foreach ($aFunctions as $Function) {
-   *       $id = $xPath->match("generate-id($Function)");
-   *       echo "<h2 id='$id'>$Function</h2>";
-   *       echo htmlspecialchars($xPath->exportAsXml($Function));
-   *   }
-   * 
-   * @param  $arguments     (string) String containing the arguments that were passed to the function.
-   * @param  $context       (array)  The context from which to evaluate the function
-   * @return                (mixed)  Depending on the type of function being processed
-   * @author Ricardo Garcia
-   * @see    evaluate()
-   */
-  function _handleFunction_generate_id($arguments, $context) {
-    // If the argument is omitted, it defaults to a node-set with the context node as its only member.
-    if (is_string($arguments) && empty($arguments)) {
-      // We need ids then
-      $this->_generate_ids();
-      return $this->_addLiteral($this->nodeIndex[$context['nodePath']]['generated_id']);
-    }
-
-    // Evaluate the argument to get a node set.
-    $nodeSet = $this->_evaluateExpr($arguments, $context);
-
-    if (!is_array($nodeSet)) return '';
-    if (count($nodeSet) < 1) return '';
-    if (!isset($this->nodeIndex[$nodeSet[0]])) return '';
-     // Return a reference to the name of the node.
-    // We need ids then
-    $this->_generate_ids();
-    return $this->_addLiteral($this->nodeIndex[$nodeSet[0]]['generated_id']);
-  }
-
-  //-----------------------------------------------------------------------------------------
-  // XPathEngine                ------  Help Stuff  ------                                   
-  //-----------------------------------------------------------------------------------------
-
-  /**
-   * Decodes the character set entities in the given string.
-   *
-   * This function is given for convenience, as all text strings or attributes
-   * are going to come back to you with their entities still encoded.  You can
-   * use this function to remove these entites.
-   *
-   * It makes use of the get_html_translation_table(HTML_ENTITIES) php library 
-   * call, so is limited in the same ways.  At the time of writing this seemed
-   * be restricted to iso-8859-1
-   *
-   * ### Provide an option that will do this by default.
-   *
-   * @param $encodedData (mixed) The string or array that has entities you would like to remove
-   * @param $reverse     (bool)  If TRUE entities will be encoded rather than decoded, ie
-   *                             < to &lt; rather than &lt; to <.
-   * @return             (mixed) The string or array returned with entities decoded.
-   */
-  function decodeEntities($encodedData, $reverse=FALSE) {
-    static $aEncodeTbl;
-    static $aDecodeTbl;
-    // Get the translation entities, but we'll cache the result to enhance performance.
-    if (empty($aDecodeTbl)) {
-      // Get the translation entities.
-      $aEncodeTbl = get_html_translation_table(HTML_ENTITIES);
-      $aDecodeTbl = array_flip($aEncodeTbl);
-    }
-
-    // If it's just a single string.
-    if (!is_array($encodedData)) {
-      if ($reverse) {
-        return strtr($encodedData, $aEncodeTbl);
-      } else {
-        return strtr($encodedData, $aDecodeTbl);
-      }
-    }
-
-    $result = array();
-    foreach($encodedData as $string) {
-      if ($reverse) {
-        $result[] = strtr($string, $aEncodeTbl);
-      } else {
-        $result[] = strtr($string, $aDecodeTbl);
-      }
-    }
-
-    return $result;
-  }
-  
-  /**
-   * Compare two nodes to see if they are equal (point to the same node in the doc)
-   *
-   * 2 nodes are considered equal if the absolute XPath is equal.
-   * 
-   * @param  $node1 (mixed) Either an absolute XPath to an node OR a real tree-node (hash-array)
-   * @param  $node2 (mixed) Either an absolute XPath to an node OR a real tree-node (hash-array)
-   * @return        (bool)  TRUE if equal (see text above), FALSE if not (and on error).
-   */
-  function equalNodes($node1, $node2) {
-    $xPath_1 = is_string($node1) ? $node1 : $this->getNodePath($node1);
-    $xPath_2 = is_string($node2) ? $node2 : $this->getNodePath($node2);
-    return (strncasecmp ($xPath_1, $xPath_2, strLen($xPath_1)) == 0);
-  }
-  
-  /**
-   * Get the absolute XPath of a node that is in a document tree.
-   *
-   * @param $node (array)  A real tree-node (hash-array)   
-   * @return      (string) The string path to the node or FALSE on error.
-   */
-  function getNodePath($node) {
-    if (!empty($node['xpath'])) return $node['xpath'];
-    $pathInfo = array();
-    do {
-      if (empty($node['name']) OR empty($node['parentNode'])) break; // End criteria
-      $pathInfo[] = array('name' => $node['name'], 'contextPos' => $node['contextPos']);
-      $node = $node['parentNode'];
-    } while (TRUE);
-    
-    $xPath = '';
-    for ($i=sizeOf($pathInfo)-1; $i>=0; $i--) {
-      $xPath .= '/' . $pathInfo[$i]['name'] . '[' . $pathInfo[$i]['contextPos'] . ']';
-    }
-    if (empty($xPath)) return FALSE;
-    return $xPath;
-  }
-  
-  /**
-   * Retrieves the absolute parent XPath query.
-   *
-   * The parents stored in the tree are only relative parents...but all the parent
-   * information is stored in the XPath query itself...so instead we use a function
-   * to extract the parent from the absolute Xpath query
-   *
-   * @param  $childPath (string) String containing an absolute XPath query
-   * @return            (string) returns the absolute XPath of the parent
-   */
-   function getParentXPath($absoluteXPath) {
-     $lastSlashPos = strrpos($absoluteXPath, '/'); 
-     if ($lastSlashPos == 0) { // it's already the root path
-       return ''; // 'super-root'
-     } else {
-       return (substr($absoluteXPath, 0, $lastSlashPos));
-     }
-   }
-  
-  /**
-   * Returns TRUE if the given node has child nodes below it
-   *
-   * @param  $absoluteXPath (string) full path of the potential parent node
-   * @return                (bool)   TRUE if this node exists and has a child, FALSE otherwise
-   */
-  function hasChildNodes($absoluteXPath) {
-    if ($this->_indexIsDirty) $this->reindexNodeTree();
-    return (bool) (isSet($this->nodeIndex[$absoluteXPath]) 
-                   AND sizeOf($this->nodeIndex[$absoluteXPath]['childNodes']));
-  }
-  
-  /**
-   * Translate all ampersands to it's literal entities '&amp;' and back.
-   *
-   * I wasn't aware of this problem at first but it's important to understand why we do this.
-   * At first you must know:
-   * a) PHP's XML parser *translates* all entities to the equivalent char E.g. &lt; is returned as '<'
-   * b) PHP's XML parser (in V 4.1.0) has problems with most *literal* entities! The only one's that are 
-   *    recognized are &amp;, &lt; &gt; and &quot;. *ALL* others (like &nbsp; &copy; a.s.o.) cause an 
-   *    XML_ERROR_UNDEFINED_ENTITY error. I reported this as bug at http://bugs.php.net/bug.php?id=15092
-   *    (It turned out not to be a 'real' bug, but one of those nice W3C-spec things).
-   * 
-   * Forget position b) now. It's just for info. Because the way we will solve a) will also solve b) too. 
-   *
-   * THE PROBLEM
-   * To understand the problem, here a sample:
-   * Given is the following XML:    "<AAA> &lt; &nbsp; &gt; </AAA>"
-   *   Try to parse it and PHP's XML parser will fail with a XML_ERROR_UNDEFINED_ENTITY becaus of 
-   *   the unknown litteral-entity '&nbsp;'. (The numeric equivalent '&#160;' would work though). 
-   * Next try is to use the numeric equivalent 160 for '&nbsp;', thus  "<AAA> &lt; &#160; &gt; </AAA>"
-   *   The data we receive in the tag <AAA> is  " <   > ". So we get the *translated entities* and 
-   *   NOT the 3 entities &lt; &#160; &gt. Thus, we will not even notice that there were entities at all!
-   *   In *most* cases we're not able to tell if the data was given as entity or as 'normal' char.
-   *   E.g. When receiving a quote or a single space were not able to tell if it was given as 'normal' char
-   *   or as &nbsp; or &quot;. Thus we loose the entity-information of the XML-data!
-   * 
-   * THE SOLUTION
-   * The better solution is to keep the data 'as is' by replacing the '&' before parsing begins.
-   * E.g. Taking the original input from above, this would result in "<AAA> &amp;lt; &amp;nbsp; &amp;gt; </AAA>"
-   * The data we receive now for the tag <AAA> is  " &lt; &nbsp; &gt; ". and that's what we want.
-   * 
-   * The bad thing is, that a global replace will also replace data in section that are NOT translated by the 
-   * PHP XML-parser. That is comments (<!-- -->), IP-sections (stuff between <? ? >) and CDATA-block too.
-   * So all data comming from those sections must be reversed. This is done during the XML parse phase.
-   * So:
-   * a) Replacement of all '&' in the XML-source.
-   * b) All data that is not char-data or in CDATA-block have to be reversed during the XML-parse phase.
-   *
-   * @param  $xmlSource (string) The XML string
-   * @return            (string) The XML string with translated ampersands.
-   */
-  function _translateAmpersand($xmlSource, $reverse=FALSE) {
-    return ($reverse ? str_replace('&amp;', '&', $xmlSource) : str_replace('&', '&amp;', $xmlSource));
-  }
-
-} // END OF CLASS XPathEngine
-
-
-/************************************************************************************************
-* ===============================================================================================
-*                                     X P a t h  -  Class                                        
-* ===============================================================================================
-************************************************************************************************/
-
-define('XPATH_QUERYHIT_ALL'   , 1);
-define('XPATH_QUERYHIT_FIRST' , 2);
-define('XPATH_QUERYHIT_UNIQUE', 3);
-
-class XPath extends XPathEngine {
-    
-  /**
-   * Constructor of the class
-   *
-   * Optionally you may call this constructor with the XML-filename to parse and the 
-   * XML option vector. A option vector sample: 
-   *   $xmlOpt = array(XML_OPTION_CASE_FOLDING => FALSE, XML_OPTION_SKIP_WHITE => TRUE);
-   *
-   * @param  $userXmlOptions (array)  (optional) Vector of (<optionID>=><value>, <optionID>=><value>, ...)
-   * @param  $fileName       (string) (optional) Filename of XML file to load from.
-   *                                  It is recommended that you call importFromFile()
-   *                                  instead as you will get an error code.  If the
-   *                                  import fails, the object will be set to FALSE.
-   * @see    parent::XPathEngine()
-   */
-  function XPath($fileName='', $userXmlOptions=array()) {
-    parent::XPathEngine($userXmlOptions);
-    $this->properties['modMatch'] = XPATH_QUERYHIT_ALL;
-    if ($fileName) {
-      if (!$this->importFromFile($fileName)) {
-        $this = FALSE;
-      }
-    }
-  }
-  
-  /**
-   * Resets the object so it's able to take a new xml sting/file
-   *
-   * Constructing objects is slow.  If you can, reuse ones that you have used already
-   * by using this reset() function.
-   */
-  function reset() {
-    parent::reset();
-    $this->properties['modMatch'] = XPATH_QUERYHIT_ALL;
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                    ------  Get / Set Stuff  ------                                
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Resolves and xPathQuery array depending on the property['modMatch']
-   *
-   * Most of the modification functions of XPath will also accept a xPathQuery (instead 
-   * of an absolute Xpath). The only problem is that the query could match more the one 
-   * node. The question is, if the none, the fist or all nodes are to be modified.
-   * The behaver can be set with setModMatch()  
-   *
-   * @param $modMatch (int) One of the following:
-   *                        - XPATH_QUERYHIT_ALL (default) 
-   *                        - XPATH_QUERYHIT_FIRST
-   *                        - XPATH_QUERYHIT_UNIQUE // If the query matches more then one node. 
-   * @see  _resolveXPathQuery()
-   */
-  function setModMatch($modMatch = XPATH_QUERYHIT_ALL) {
-    switch($modMatch) {
-      case XPATH_QUERYHIT_UNIQUE : $this->properties['modMatch'] =  XPATH_QUERYHIT_UNIQUE; break;
-      case XPATH_QUERYHIT_FIRST: $this->properties['modMatch'] =  XPATH_QUERYHIT_FIRST; break;
-      default: $this->properties['modMatch'] = XPATH_QUERYHIT_ALL;
-    }
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                    ------  DOM Like Modification  ------                          
-  //-----------------------------------------------------------------------------------------
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                  ------  Child (Node)  Set/Get  ------                           
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Retrieves the name(s) of a node or 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  $xPathQuery (mixed) Array or single full document path(s) of the node(s), 
-   *                             from which the names should be retrieved.
-   * @return             (mixed) Array or single string of the names of the specified 
-   *                             nodes, or just the individual name.  If the node did 
-   *                             not exist, then returns FALSE.
-   */
-  function nodeName($xPathQuery) {
-    if (is_array($xPathQuery)) {
-      $xPathSet = $xPathQuery;
-    } else {
-      // Check for a valid xPathQuery
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'nodeName');
-    }
-    if (count($xPathSet) == 0) return FALSE;
-    // For each node, get it's name
-    $result = array();
-    foreach($xPathSet as $xPath) {
-      $node = &$this->getNode($xPath);
-      if (!$node) {
-        // ### Fatal internal error?? 
-        continue;
-      }
-      $result[] = $node['name'];
-    }
-    // If just a single string, return string
-    if (count($xPathSet) == 1) $result = $result[0];
-    // Return result.
-    return $result;
-  }
-  
-  /**
-   * 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.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery  (string) xpath to the node (See note above).
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                               the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (bool)   TRUE on success, FALSE on error;
-   * @see    setModMatch(), reindexNodeTree()
-   */
-  function removeChild($xPathQuery, $autoReindex=TRUE) {
-    $NULL = NULL;
-    $bDebugThisFunction = FALSE;  // Get diagnostic output for this function
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction('removeChild');
-      echo "Node: $xPathQuery\n";
-      echo '<hr>';
-    }
-    $status = FALSE;
-    do { // try-block
-      // Check for a valid xPathQuery
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'removeChild');
-      if (sizeOf($xPathSet) === 0) {
-        $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-        break; // try-block
-      }
-      $mustReindex = FALSE;
-      // Make chages from 'bottom-up'. In this manner the modifications will not affect itself.
-      for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-        $absoluteXPath = $xPathSet[$i];
-        if (preg_match(';/attribute::;', $absoluteXPath)) { // Handle the case of an attribute node
-          $xPath = $this->_prestr($absoluteXPath, '/attribute::');       // Get the path to the attribute node's parent.
-          $attribute = $this->_afterstr($absoluteXPath, '/attribute::'); // Get the name of the attribute.
-          unSet($this->nodeIndex[$xPath]['attributes'][$attribute]);     // Unset the attribute
-          if ($bDebugThisFunction) echo "We removed the attribute '$attribute' of node '$xPath'.\n";
-          continue;
-        }
-        // Otherwise remove the node by setting it to NULL. It will be removed on the next reindexNodeTree() call.
-        $mustReindex = $autoReindex;
-        // Flag the index as dirty; it's not uptodate. A reindex will be forced (if dirty) when exporting the XML doc
-        $this->_indexIsDirty = TRUE;
-        
-        $theNode = $this->nodeIndex[$absoluteXPath];
-        $theNode['parentNode']['childNodes'][$theNode['pos']] =& $NULL;
-        if ($bDebugThisFunction) echo "We removed the node '$absoluteXPath'.\n";
-      }
-      // Reindex the node tree again
-      if ($mustReindex) $this->reindexNodeTree();
-      $status = TRUE;
-    } while(FALSE);
-    
-    if ($bDebugThisFunction) $this->_closeDebugFunction($aStartTime, $status);
-    return $status;
-  }
-  
-  /**
-   * Replace a node with any data string. The $data is taken 1:1.
-   *
-   * 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. 
-   *   You are in charge that the data you enter is valid XML if you intend
-   *   to export and import the content again.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery  (string) xpath to the node (See note above).
-   * @param  $data        (string) String containing the content to be set. *READONLY*
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                               the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (bool)   TRUE on success, FALSE on error;
-   * @see    setModMatch(), replaceChild(), reindexNodeTree()
-   */
-  function replaceChildByData($xPathQuery, $data, $autoReindex=TRUE) {
-    $NULL = NULL;
-    $bDebugThisFunction = FALSE;  // Get diagnostic output for this function
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction('replaceChildByData');
-      echo "Node: $xPathQuery\n";
-    }
-    $status = FALSE;
-    do { // try-block
-      // Check for a valid xPathQuery
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'replaceChildByData');
-      if (sizeOf($xPathSet) === 0) {
-        $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-        break; // try-block
-      }
-      $mustReindex = FALSE;
-      // Make chages from 'bottom-up'. In this manner the modifications will not affect itself.
-      for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-        $mustReindex = $autoReindex;
-        // Flag the index as dirty; it's not uptodate. A reindex will be forced (if dirty) when exporting the XML doc
-        $this->_indexIsDirty = TRUE;
-        
-        $absoluteXPath = $xPathSet[$i];
-        $theNode = $this->nodeIndex[$absoluteXPath];
-        $pos = $theNode['pos'];
-        $theNode['parentNode']['textParts'][$pos] .= $data;
-        $theNode['parentNode']['childNodes'][$pos] =& $NULL;
-        if ($bDebugThisFunction) echo "We replaced the node '$absoluteXPath' with data.\n";
-      }
-      // Reindex the node tree again
-      if ($mustReindex) $this->reindexNodeTree();
-      $status = TRUE;
-    } while(FALSE);
-    
-    if ($bDebugThisFunction) $this->_closeDebugFunction($aStartTime, ($status) ? 'Success' : '!!! FAILD !!!');
-    return $status;
-  }
-  
-  /**
-   * Replace the node(s) that matches the xQuery with the passed node (or passed node-tree)
-   * 
-   * If the passed node is a string it's assumed to be XML and replaceChildByXml() 
-   * will be called.
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery  (string) Xpath to the node being replaced.
-   * @param  $node        (mixed)  String or Array (Usually a String)
-   *                               If string: Vaild XML. E.g. "<A/>" or "<A> foo <B/> bar <A/>"
-   *                               If array:  A Node (can be a whole sub-tree) (See comment in header)
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                               the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (array)  The last replaced $node (can be a whole sub-tree)
-   * @see    reindexNodeTree()
-   */
-  function &replaceChild($xPathQuery, $node, $autoReindex=TRUE) {
-    $NULL = NULL;
-    if (is_string($node)) {
-      if (empty($node)) { //--sam. Not sure how to react on an empty string - think it's an error.
-        return array();
-      } else { 
-        if (!($node = $this->_xml2Document($node))) return FALSE;
-      }
-    }
-    
-    // Special case if it's 'super root'. We then have to take the child node == top node
-    if (empty($node['parentNode'])) $node = $node['childNodes'][0];
-    
-    $status = FALSE;
-    do { // try-block
-      // Check for a valid xPathQuery
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'replaceChild');
-      if (sizeOf($xPathSet) === 0) {
-        $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-        break; // try-block
-      }
-      $mustReindex = FALSE;
-      
-      // Make chages from 'bottom-up'. In this manner the modifications will not affect itself.
-      for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-        $mustReindex = $autoReindex;
-        // Flag the index as dirty; it's not uptodate. A reindex will be forced (if dirty) when exporting the XML doc
-        $this->_indexIsDirty = TRUE;
-        
-        $absoluteXPath = $xPathSet[$i];
-        $childNode =& $this->nodeIndex[$absoluteXPath];
-        $parentNode =& $childNode['parentNode'];
-        $childNode['parentNode'] =& $NULL;
-        $childPos = $childNode['pos'];
-        $parentNode['childNodes'][$childPos] =& $this->cloneNode($node);
-      }
-      if ($mustReindex) $this->reindexNodeTree();
-      $status = TRUE;
-    } while(FALSE);
-    
-    if (!$status) return FALSE;
-    return $childNode;
-  }
-  
-  /**
-   * Insert passed node (or passed node-tree) at the node(s) that matches the xQuery.
-   *
-   * With parameters you can define if the 'hit'-node is shifted to the right or left 
-   * and if it's placed before of after the text-part.
-   * Per derfault the 'hit'-node is shifted to the right and the node takes the place 
-   * the of the 'hit'-node. 
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   * 
-   * E.g. Following is given:           AAA[1]           
-   *                                  /       \          
-   *                              ..BBB[1]..BBB[2] ..    
-   *
-   * a) insertChild('/AAA[1]/BBB[2]', <node CCC>)
-   * b) insertChild('/AAA[1]/BBB[2]', <node CCC>, $shiftRight=FALSE)
-   * c) insertChild('/AAA[1]/BBB[2]', <node CCC>, $shiftRight=FALSE, $afterText=FALSE)
-   *
-   * a)                          b)                           c)                        
-   *          AAA[1]                       AAA[1]                       AAA[1]          
-   *        /    |   \                   /    |   \                   /    |   \        
-   *  ..BBB[1]..CCC[1]BBB[2]..     ..BBB[1]..BBB[2]..CCC[1]     ..BBB[1]..BBB[2]CCC[1]..
-   *
-   * #### Do a complete review of the "(optional)" tag after several arguments.
-   *
-   * @param  $xPathQuery  (string) Xpath to the node to append.
-   * @param  $node        (mixed)  String or Array (Usually a String)
-   *                               If string: Vaild XML. E.g. "<A/>" or "<A> foo <B/> bar <A/>"
-   *                               If array:  A Node (can be a whole sub-tree) (See comment in header)
-   * @param  $shiftRight  (bool)   (optional, default=TRUE) Shift the target node to the right.
-   * @param  $afterText   (bool)   (optional, default=TRUE) Insert after the text.
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                                the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (mixed)  FALSE on error (or no match). On success we return the path(s) to the newly
-   *                               appended nodes. That is: Array of paths if more then 1 node was added or
-   *                               a single path string if only one node was added.
-   *                               NOTE:  If autoReindex is FALSE, then we can't return the *complete* path
-   *                               as the exact doc-pos isn't available without reindexing. In that case we leave
-   *                               out the last [docpos] in the path(s). ie  we'd return /A[3]/B instead of /A[3]/B[2]
-   * @see    appendChildByXml(), reindexNodeTree()
-   */
-  function insertChild($xPathQuery, $node, $shiftRight=TRUE, $afterText=TRUE, $autoReindex=TRUE) {
-    if (is_string($node)) {
-      if (empty($node)) { //--sam. Not sure how to react on an empty string - think it's an error.
-        return FALSE;
-      } else { 
-        if (!($node = $this->_xml2Document($node))) return FALSE;
-      }
-    }
-
-    // Special case if it's 'super root'. We then have to take the child node == top node
-    if (empty($node['parentNode'])) $node = $node['childNodes'][0];
-    
-    // Check for a valid xPathQuery
-    $xPathSet = $this->_resolveXPathQuery($xPathQuery,'insertChild');
-    if (sizeOf($xPathSet) === 0) {
-      $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-      return FALSE;
-    }
-    $mustReindex = FALSE;
-    $newNodes = array();
-    $result = array();
-    // Make chages from 'bottom-up'. In this manner the modifications will not affect itself.
-    for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-      $absoluteXPath = $xPathSet[$i];
-      $childNode =& $this->nodeIndex[$absoluteXPath];
-      $parentNode =& $childNode['parentNode'];
-
-      // We can't insert at the super root or at the root.
-      if (empty($absoluteXPath) || (!$parentNode['parentNode'])) {
-        $this->_displayError(sprintf($this->errorStrings['RootNodeAlreadyExists']), __LINE__, __FILE__, FALSE);
-        return FALSE;
-      }
-
-      $mustReindex = $autoReindex;
-      // Flag the index as dirty; it's not uptodate. A reindex will be forced (if dirty) when exporting the XML doc
-      $this->_indexIsDirty = TRUE;
-      
-      //Special case: It not possible to add siblings to the top node.
-      if (empty($parentNode['name'])) continue;
-      $newNode =& $this->cloneNode($node);
-      $pos = $shiftRight ? $childNode['pos'] : $childNode['pos']+1;
-      $parentNode['childNodes'] = array_merge(
-                                    array_slice($parentNode['childNodes'], 0, $pos),
-                                    array(&$newNode),
-                                    array_slice($parentNode['childNodes'], $pos)
-                                  );
-      $pos += $afterText ? 1 : 0;
-      $parentNode['textParts'] = array_merge(
-                                   array_slice($parentNode['textParts'], 0, $pos),
-                                   '',
-                                   array_slice($parentNode['textParts'], $pos)
-                                 );
-      
-      // We are going from bottom to top, but the user will want results from top to bottom.
-      if ($mustReindex) {
-        // We'll have to wait till after the reindex to get the full path to this new node.
-        $newNodes[] = &$newNode;
-      } else {
-        // If we are reindexing the tree later, then we can't return the user any
-        // useful results, so we just return them the count.
-        $newNodePath = $parentNode['xpath'].'/'.$newNode['name'];
-        array_unshift($result, $newNodePath);
-      }
-    }
-    if ($mustReindex) {
-      $this->reindexNodeTree();
-      // Now we must fill in the result array.  Because until now we did not
-      // know what contextpos our newly added entries had, just their pos within
-      // the siblings.
-      foreach ($newNodes as $newNode) {
-        array_unshift($result, $newNode['xpath']);
-      }
-    }
-    if (count($result) == 1) $result = $result[0];
-    return $result;
-  }
-  
-  /**
-   * Appends a child to anothers children.
-   *
-   * If you intend to do a lot of appending, you should leave autoIndex as FALSE
-   * and then call reindexNodeTree() when you are finished all the appending.
-   *
-   * @param  $xPathQuery  (string) Xpath to the node to append to.
-   * @param  $node        (mixed)  String or Array (Usually a String)
-   *                               If string: Vaild XML. E.g. "<A/>" or "<A> foo <B/> bar <A/>"
-   *                               If array:  A Node (can be a whole sub-tree) (See comment in header)
-   * @param  $afterText   (bool)   (optional, default=FALSE) Insert after the text.
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                               the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (mixed)  FALSE on error (or no match). On success we return the path(s) to the newly
-   *                               appended nodes. That is: Array of paths if more then 1 node was added or
-   *                               a single path string if only one node was added.
-   *                               NOTE:  If autoReindex is FALSE, then we can't return the *complete* path
-   *                               as the exact doc-pos isn't available without reindexing. In that case we leave
-   *                               out the last [docpos] in the path(s). ie  we'd return /A[3]/B instead of /A[3]/B[2]
-   * @see    insertChild(), reindexNodeTree()
-   */
-  function appendChild($xPathQuery, $node, $afterText=FALSE, $autoReindex=TRUE) {
-    if (is_string($node)) {
-      if (empty($node)) { //--sam. Not sure how to react on an empty string - think it's an error.
-        return FALSE;
-      } else { 
-        if (!($node = $this->_xml2Document($node))) return FALSE;
-      }
-    }
-    
-    // Special case if it's 'super root'. We then have to take the child node == top node
-    if (empty($node['parentNode'])) $node = $node['childNodes'][0];
-
-    // Check for a valid xPathQuery
-    $xPathSet = $this->_resolveXPathQueryForNodeMod($xPathQuery, 'appendChild');
-    if (sizeOf($xPathSet) === 0) return FALSE;
-
-    $mustReindex = FALSE;
-    $newNodes = array();
-    $result = array();
-    // Make chages from 'bottom-up'. In this manner the modifications will not affect itself.
-    for ($i=sizeOf($xPathSet)-1; $i>=0; $i--) {
-      $mustReindex = $autoReindex;
-      // Flag the index as dirty; it's not uptodate. A reindex will be forced (if dirty) when exporting the XML doc
-      $this->_indexIsDirty = TRUE;
-      
-      $absoluteXPath = $xPathSet[$i];
-      $parentNode =& $this->nodeIndex[$absoluteXPath];
-      $newNode =& $this->cloneNode($node);
-      $parentNode['childNodes'][] =& $newNode;
-      $pos = count($parentNode['textParts']);
-      $pos -= $afterText ? 0 : 1;
-      $parentNode['textParts'] = array_merge(
-                                   array_slice($parentNode['textParts'], 0, $pos),
-                                   '',
-                                   array_slice($parentNode['textParts'], $pos)
-                                 );
-      // We are going from bottom to top, but the user will want results from top to bottom.
-      if ($mustReindex) {
-        // We'll have to wait till after the reindex to get the full path to this new node.
-        $newNodes[] = &$newNode;
-      } else {
-        // If we are reindexing the tree later, then we can't return the user any
-        // useful results, so we just return them the count.
-        array_unshift($result, "$absoluteXPath/{$newNode['name']}");
-      }
-    }
-    if ($mustReindex) {
-      $this->reindexNodeTree();
-      // Now we must fill in the result array.  Because until now we did not
-      // know what contextpos our newly added entries had, just their pos within
-      // the siblings.
-      foreach ($newNodes as $newNode) {
-        array_unshift($result, $newNode['xpath']);
-      }
-    } 
-    if (count($result) == 1) $result = $result[0];
-    return $result;
-  }
-  
-  /**
-   * Inserts a node before the reference node with the same parent.
-   *
-   * If you intend to do a lot of appending, you should leave autoIndex as FALSE
-   * and then call reindexNodeTree() when you are finished all the appending.
-   *
-   * @param  $xPathQuery  (string) Xpath to the node to insert new node before
-   * @param  $node        (mixed)  String or Array (Usually a String)
-   *                               If string: Vaild XML. E.g. "<A/>" or "<A> foo <B/> bar <A/>"
-   *                               If array:  A Node (can be a whole sub-tree) (See comment in header)
-   * @param  $afterText   (bool)   (optional, default=FLASE) Insert after the text.
-   * @param  $autoReindex (bool)   (optional, default=TRUE) Reindex the document to reflect 
-   *                               the changes.  A performance helper.  See reindexNodeTree()
-   * @return              (mixed)  FALSE on error (or no match). On success we return the path(s) to the newly
-   *                               appended nodes. That is: Array of paths if more then 1 node was added or
-   *                               a single path string if only one node was added.
-   *                               NOTE:  If autoReindex is FALSE, then we can't return the *complete* path
-   *                               as the exact doc-pos isn't available without reindexing. In that case we leave
-   *                               out the last [docpos] in the path(s). ie  we'd return /A[3]/B instead of /A[3]/B[2]
-   * @see    reindexNodeTree()
-   */
-  function insertBefore($xPathQuery, $node, $afterText=TRUE, $autoReindex=TRUE) {
-    return $this->insertChild($xPathQuery, $node, $shiftRight=TRUE, $afterText, $autoReindex);
-  }
-  
-
-  //-----------------------------------------------------------------------------------------
-  // XPath                     ------  Attribute  Set/Get  ------                            
-  //-----------------------------------------------------------------------------------------
-  
-  /** 
-   * Retrieves a dedecated attribute value or a hash-array of all attributes of a node.
-   * 
-   * The first param $absoluteXPath must be a valid xpath OR a xpath-query that results 
-   * to *one* xpath. If the second param $attrName is not set, a hash-array of all attributes 
-   * of that node is returned.
-   *
-   * Optionally you may pass an attrubute name in $attrName and the function will return the 
-   * string value of that attribute.
-   *
-   * @param  $absoluteXPath (string) Full xpath OR a xpath-query that results to *one* xpath.
-   * @param  $attrName      (string) (Optional) The name of the attribute. See above.
-   * @return                (mixed)  hash-array or a string of attributes depending if the 
-   *                                 parameter $attrName was set (see above).  FALSE if the 
-   *                                 node or attribute couldn't be found.
-   * @see    setAttribute(), removeAttribute()
-   */
-  function getAttributes($absoluteXPath, $attrName=NULL) {
-    // Numpty check
-    if (!isSet($this->nodeIndex[$absoluteXPath])) {
-      $xPathSet = $this->_resolveXPathQuery($absoluteXPath,'getAttributes');
-      if (empty($xPathSet)) return FALSE;
-      // only use the first entry
-      $absoluteXPath = $xPathSet[0];
-    }
-    
-    // Return the complete list or just the desired element
-    if (is_null($attrName)) {
-      return $this->nodeIndex[$absoluteXPath]['attributes'];
-    } elseif (isSet($this->nodeIndex[$absoluteXPath]['attributes'][$attrName])) {
-      return $this->nodeIndex[$absoluteXPath]['attributes'][$attrName];
-    }
-    return FALSE;
-  }
-  
-  /**
-   * Set attributes of a node(s).
-   *
-   * This method sets a number single attributes. An existing attribute is overwritten (default)
-   * with the new value, but setting the last param to FALSE will prevent overwritten.
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery (string) xpath to the node (See note above).
-   * @param  $name       (string) Attribute name.
-   * @param  $value      (string) Attribute value.   
-   * @param  $overwrite  (bool)   If the attribute is already set we overwrite it (see text above)
-   * @return             (bool)   TRUE on success, FALSE on failure.
-   * @see    getAttribute(), removeAttribute()
-   */
-  function setAttribute($xPathQuery, $name, $value, $overwrite=TRUE) {
-    return $this->setAttributes($xPathQuery, array($name => $value), $overwrite);
-  }
-  
-  /**
-   * Version of setAttribute() that sets multiple attributes to node(s).
-   *
-   * This method sets a number of attributes. Existing attributes are overwritten (default)
-   * with the new values, but setting the last param to FALSE will prevent overwritten.
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery (string) xpath to the node (See note above).
-   * @param  $attributes (array)  associative array of attributes to set.
-   * @param  $overwrite  (bool)   If the attributes are already set we overwrite them (see text above)
-   * @return             (bool)   TRUE on success, FALSE otherwise
-   * @see    setAttribute(), getAttribute(), removeAttribute()
-   */
-  function setAttributes($xPathQuery, $attributes, $overwrite=TRUE) {
-    $status = FALSE;
-    do { // try-block
-      // The attributes parameter should be an associative array.
-      if (!is_array($attributes)) break;  // try-block
-      
-      // Check for a valid xPathQuery
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'setAttributes');
-      foreach($xPathSet as $absoluteXPath) {
-        // Add the attributes to the node.
-        $theNode =& $this->nodeIndex[$absoluteXPath];
-        if (empty($theNode['attributes'])) {
-          $this->nodeIndex[$absoluteXPath]['attributes'] = $attributes;
-        } else {
-          $theNode['attributes'] = $overwrite ? array_merge($theNode['attributes'],$attributes) : array_merge($attributes, $theNode['attributes']);
-        }
-      }
-      $status = TRUE;
-    } while(FALSE); // END try-block
-    
-    return $status;
-  }
-  
-  /**
-   * Removes an attribute of a node(s).
-   *
-   * This method removes *ALL* attributres per default unless the second parameter $attrList is set.
-   * $attrList can be either a single attr-name as string OR a vector of attr-names as array.
-   * E.g. 
-   *  removeAttribute(<xPath>);                     # will remove *ALL* attributes.
-   *  removeAttribute(<xPath>, 'A');                # will only remove attributes called 'A'.
-   *  removeAttribute(<xPath>, array('A_1','A_2')); # will remove attribute 'A_1' and 'A_2'.
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param   $xPathQuery (string) xpath to the node (See note above).
-   * @param   $attrList   (mixed)  (optional) if not set will delete *all* (see text above)
-   * @return              (bool)   TRUE on success, FALSE if the node couldn't be found
-   * @see     getAttribute(), setAttribute()
-   */
-  function removeAttribute($xPathQuery, $attrList=NULL) {
-    // Check for a valid xPathQuery
-    $xPathSet = $this->_resolveXPathQuery($xPathQuery, 'removeAttribute');
-    
-    if (!empty($attrList) AND is_string($attrList)) $attrList = array($attrList);
-    if (!is_array($attrList)) return FALSE;
-    
-    foreach($xPathSet as $absoluteXPath) {
-      // If the attribute parameter wasn't set then remove all the attributes
-      if ($attrList[0] === NULL) {
-        $this->nodeIndex[$absoluteXPath]['attributes'] = array();
-        continue; 
-      }
-      // Remove all the elements in the array then.
-      foreach($attrList as $name) {
-        unset($this->nodeIndex[$absoluteXPath]['attributes'][$name]);
-      }
-    }
-    return TRUE;
-  }
-  
-  //-----------------------------------------------------------------------------------------
-  // XPath                        ------  Text  Set/Get  ------                              
-  //-----------------------------------------------------------------------------------------
-  
-  /**
-   * Retrieve all the text from a node as a single string.
-   *
-   * Sample  
-   * Given is: <AA> This <BB\>is <BB\>  some<BB\>text </AA>
-   * Return of getData('/AA[1]') would be:  " This is   sometext "
-   * The first param $xPathQuery must be a valid xpath OR a xpath-query that 
-   * results to *one* xpath. 
-   *
-   * @param  $xPathQuery (string) xpath to the node - resolves to *one* xpath.
-   * @return             (mixed)  The returned string (see above), FALSE if the node 
-   *                              couldn't be found or is not unique.
-   * @see getDataParts()
-   */
-  function getData($xPathQuery) {
-    $aDataParts = $this->getDataParts($xPathQuery);
-    if ($aDataParts === FALSE) return FALSE;
-    return implode('', $aDataParts);
-  }
-  
-  /**
-   * Retrieve all the text from a node as a vector of strings
-   * 
-   * Where each element of the array was interrupted by a non-text child element.
-   *
-   * Sample  
-   * Given is: <AA> This <BB\>is <BB\>  some<BB\>text </AA>
-   * Return of getDataParts('/AA[1]') would be:  array([0]=>' This ', [1]=>'is ', [2]=>'  some', [3]=>'text ');
-   * The first param $absoluteXPath must be a valid xpath OR a xpath-query that results 
-   * to *one* xpath. 
-   *
-   * @param  $xPathQuery (string) xpath to the node - resolves to *one* xpath.
-   * @return             (mixed)  The returned array (see above), or FALSE if node is not 
-   *                              found or is not unique.
-   * @see getData()
-   */
-  function getDataParts($xPathQuery) {
-    // Resolve xPath argument
-    $xPathSet = $this->_resolveXPathQuery($xPathQuery, 'getDataParts');
-    if (1 !== ($setSize=count($xPathSet))) {
-      $this->_displayError(sprintf($this->errorStrings['AbsoluteXPathRequired'], $xPathQuery) . "Not unique xpath-query, matched {$setSize}-times.", __LINE__, __FILE__, FALSE);
-      return FALSE;
-    }
-    $absoluteXPath = $xPathSet[0];
-    // Is it an attribute node?
-    if (preg_match(";(.*)/attribute::([^/]*)$;U", $xPathSet[0], $matches)) {
-      $absoluteXPath = $matches[1];
-      $attribute = $matches[2];
-      if (!isSet($this->nodeIndex[$absoluteXPath]['attributes'][$attribute])) {
-        $this->_displayError("The $absoluteXPath/attribute::$attribute value isn't a node in this document.", __LINE__, __FILE__, FALSE);
-        continue;
-      }
-      return array($this->nodeIndex[$absoluteXPath]['attributes'][$attribute]);
-    } else if (preg_match(":(.*)/text\(\)(\[(.*)\])?$:U", $xPathQuery, $matches)) {
-      $absoluteXPath = $matches[1];
-      $textPartNr = $matches[2];      
-      return array($this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr]);
-    } else {
-      return $this->nodeIndex[$absoluteXPath]['textParts'];
-    }
-  }
-  
-  /**
-   * Retrieves a sub string of a text-part OR attribute-value.
-   *
-   * This method retrieves the sub string of a specific text-part OR (if the 
-   * $absoluteXPath references an attribute) the the sub string  of the attribute value.
-   * If no 'direct referencing' is used (Xpath ends with text()[<part-number>]), then 
-   * the first text-part of the node ist returned (if exsiting).
-   *
-   * @param  $absoluteXPath (string) Xpath to the node (See note above).   
-   * @param  $offset        (int)    (optional, default is 0) Starting offset. (Just like PHP's substr())
-   * @param  $count         (number) (optional, default is ALL) Character count  (Just like PHP's substr())
-   * @return                (mixed)  The sub string, FALSE if not found or on error
-   * @see    XPathEngine::wholeText(), PHP's substr()
-   */
-  function substringData($absoluteXPath, $offset = 0, $count = NULL) {
-    if (!($text = $this->wholeText($absoluteXPath))) return FALSE;
-    if (is_null($count)) {
-      return substr($text, $offset);
-    } else {
-      return substr($text, $offset, $count);
-    } 
-  }
-  
-  /**
-   * Replace a sub string of a text-part OR attribute-value.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery    (string) xpath to the node (See note above).
-   * @param  $replacement   (string) The string to replace with.
-   * @param  $offset        (int)    (optional, default is 0) Starting offset. (Just like PHP's substr_replace ())
-   * @param  $count         (number) (optional, default is 0=ALL) Character count  (Just like PHP's substr_replace())
-   * @param  $textPartNr    (int)    (optional) (see _getTextSet() )
-   * @return                (bool)   The new string value on success, FALSE if not found or on error
-   * @see    substringData()
-   */
-  function replaceData($xPathQuery, $replacement, $offset = 0, $count = 0, $textPartNr=1) {
-    if (!($textSet = $this->_getTextSet($xPathQuery, $textPartNr))) return FALSE;
-    $tSize=sizeOf($textSet);
-    for ($i=0; $i<$tSize; $i++) {
-      if ($count) {
-        $textSet[$i] = substr_replace($textSet[$i], $replacement, $offset, $count);
-      } else {
-        $textSet[$i] = substr_replace($textSet[$i], $replacement, $offset);
-      } 
-    }
-    return TRUE;
-  }
-  
-  /**
-   * Insert a sub string in a text-part OR attribute-value.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery (string) xpath to the node (See note above).
-   * @param  $data       (string) The string to replace with.
-   * @param  $offset     (int)    (optional, default is 0) Offset at which to insert the data.
-   * @return             (bool)   The new string on success, FALSE if not found or on error
-   * @see    replaceData()
-   */
-  function insertData($xPathQuery, $data, $offset=0) {
-    return $this->replaceData($xPathQuery, $data, $offset, 0);
-  }
-  
-  /**
-   * Append text data to the end of the text for an attribute OR node text-part.
-   *
-   * This method adds content to a node. If it's an attribute node, then
-   * the value of the attribute will be set, otherwise the passed data will append to 
-   * character data of the node text-part. Per default the first text-part is taken.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param   $xPathQuery (string) to the node(s) (See note above).
-   * @param   $data       (string) String containing the content to be added.
-   * @param   $textPartNr (int)    (optional, default is 1) (see _getTextSet())
-   * @return              (bool)   TRUE on success, otherwise FALSE
-   * @see     _getTextSet()
-   */
-  function appendData($xPathQuery, $data, $textPartNr=1) {
-    if (!($textSet = $this->_getTextSet($xPathQuery, $textPartNr))) return FALSE;
-    $tSize=sizeOf($textSet);
-    for ($i=0; $i<$tSize; $i++) {
-      $textSet[$i] .= $data;
-    }
-    return TRUE;
-  }
-  
-  /**
-   * Delete the data of a node.
-   *
-   * This method deletes content of a node. If it's an attribute node, then
-   * the value of the attribute will be removed, otherwise the node text-part. 
-   * will be deleted.  Per default the first text-part is deleted.
-   *
-   * NOTE: When passing a xpath-query instead of an abs. Xpath.
-   *       Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param  $xPathQuery (string) to the node(s) (See note above).
-   * @param  $offset     (int)    (optional, default is 0) Starting offset. (Just like PHP's substr_replace())
-   * @param  $count      (number) (optional, default is 0=ALL) Character count.  (Just like PHP's substr_replace())
-   * @param  $textPartNr (int)    (optional, default is 0) the text part to delete (see _getTextSet())
-   * @return             (bool)   TRUE on success, otherwise FALSE
-   * @see     _getTextSet()
-   */
-  function deleteData($xPathQuery, $offset=0, $count=0, $textPartNr=1) {
-    if (!($textSet = $this->_getTextSet($xPathQuery, $textPartNr))) return FALSE;
-    $tSize=sizeOf($textSet);
-    for ($i=0; $i<$tSize; $i++) {
-      if (!$count)
-        $textSet[$i] = "";
-      else
-        $textSet[$i] = substr_replace($textSet[$i],'', $offset, $count);
-    } 
-    return TRUE;
-  }
- 
-  //-----------------------------------------------------------------------------------------
-  // XPath                      ------  Help Stuff  ------                                   
-  //-----------------------------------------------------------------------------------------
-   
-  /**
-   * Parse the XML to a node-tree. A so called 'document'
-   *
-   * @param  $xmlString (string) The string to turn into a document node.
-   * @return            (&array)  a node-tree
-   */
-  function &_xml2Document($xmlString) {
-    $xmlOptions = array(
-                    XML_OPTION_CASE_FOLDING => $this->getProperties('caseFolding'), 
-                    XML_OPTION_SKIP_WHITE   => $this->getProperties('skipWhiteSpaces')
-                  );
-    $xmlParser =& new XPathEngine($xmlOptions);
-    $xmlParser->setVerbose($this->properties['verboseLevel']);
-    // Parse the XML string
-    if (!$xmlParser->importFromString($xmlString)) {
-      $this->_displayError($xmlParser->getLastError(), __LINE__, __FILE__, FALSE);
-      return FALSE;
-    }
-    return $xmlParser->getNode('/');
-  }
-  
-  /**
-   * Get a reference-list to node text part(s) or node attribute(s).
-   * 
-   * If the Xquery references an attribute(s) (Xquery ends with attribute::), 
-   * then the text value of the node-attribute(s) is/are returned.
-   * Otherwise the Xquery is referencing to text part(s) of node(s). This can be either a 
-   * direct reference to text part(s) (Xquery ends with text()[<nr>]) or indirect reference 
-   * (a simple Xquery to node(s)).
-   * 1) Direct Reference (Xquery ends with text()[<part-number>]):
-   *   If the 'part-number' is omitted, the first text-part is assumed; starting by 1.
-   *   Negative numbers are allowed, where -1 is the last text-part a.s.o.
-   * 2) Indirect Reference (a simple  Xquery to node(s)):
-   *   Default is to return the first text part(s). Optionally you may pass a parameter 
-   *   $textPartNr to define the text-part you want;  starting by 1.
-   *   Negative numbers are allowed, where -1 is the last text-part a.s.o.
-   *
-   * NOTE I : The returned vector is a set of references to the text parts / attributes.
-   *          This is handy, if you wish to modify the contents.
-   * NOTE II: text-part numbers out of range will not be in the list
-   * NOTE III:Instead of an absolute xpath you may also pass a xpath-query.
-   *          Depending on setModMatch() one, none or multiple nodes are affected.
-   *
-   * @param   $xPathQuery (string) xpath to the node (See note above).
-   * @param   $textPartNr (int)    String containing the content to be set.
-   * @return              (mixed)  A vector of *references* to the text that match, or 
-   *                               FALSE on error
-   * @see XPathEngine::wholeText()
-   */
-  function _getTextSet($xPathQuery, $textPartNr=1) {
-    $status = FALSE;
-
-    $bDebugThisFunction = FALSE;  // Get diagnostic output for this function
-    if ($bDebugThisFunction) {
-      $aStartTime = $this->_beginDebugFunction('_getTextSet');
-      echo "Node: $xPathQuery\n";
-      echo "Text Part Number: $textPartNr\n";
-      echo "<hr>";
-    }
-    
-    $funcName = '_getTextSet';
-    $textSet = array();
-    
-    do { // try-block
-      // Check if it's a Xpath reference to an attribut(s). Xpath ends with attribute::)
-      if (preg_match(";(.*)/(attribute::|@)([^/]*)$;U", $xPathQuery, $matches)) {
-        $xPathQuery = $matches[1];
-        $attribute = $matches[3];
-        // Quick out
-        if (isSet($this->nodeIndex[$xPathQuery])) {
-          $xPathSet[] = $xPathQuery;
-        } else {
-          // Try to evaluate the absoluteXPath (since it seems to be an Xquery and not an abs. Xpath)
-          $xPathSet = $this->_resolveXPathQuery("$xPathQuery/attribute::$attribute", $funcName);
-        }
-        foreach($xPathSet as $absoluteXPath) {
-          preg_match(";(.*)/attribute::([^/]*)$;U", $xPathSet[0], $matches);
-          $absoluteXPath = $matches[1];
-          $attribute = $matches[2];
-          if (!isSet($this->nodeIndex[$absoluteXPath]['attributes'][$attribute])) {
-            $this->_displayError("The $absoluteXPath/attribute::$attribute value isn't a node in this document.", __LINE__, __FILE__, FALSE);
-            continue;
-          }
-          $textSet[] =& $this->nodes[$absoluteXPath]['attributes'][$attribute];
-        }
-        $status = TRUE;
-        break; // try-block
-      }
-      
-      // Check if it's a Xpath reference direct to a text-part(s). (xpath ends with text()[<part-number>])
-      if (preg_match(":(.*)/text\(\)(\[(.*)\])?$:U", $xPathQuery, $matches)) {
-        $xPathQuery = $matches[1];
-        // default to the first text node if a text node was not specified
-        $textPartNr = isSet($matches[2]) ? substr($matches[2],1,-1) : 1;
-        // Quick check
-        if (isSet($this->nodeIndex[$xPathQuery])) {
-          $xPathSet[] = $xPathQuery;
-        } else {
-          // Try to evaluate the absoluteXPath (since it seams to be an Xquery and not an abs. Xpath)
-          $xPathSet = $this->_resolveXPathQuery("$xPathQuery/text()[$textPartNr]", $funcName);
-        }
-      }
-      else {
-        // At this point we have been given an xpath with neither a 'text()' or 'attribute::' axis at the end
-        // So this means to get the text-part of the node. If parameter $textPartNr was not set, use the last
-        // text-part.
-        if (isSet($this->nodeIndex[$xPathQuery])) {
-          $xPathSet[] = $xPathQuery;
-        } else {
-          // Try to evaluate the absoluteXPath (since it seams to be an Xquery and not an abs. Xpath)
-          $xPathSet = $this->_resolveXPathQuery($xPathQuery, $funcName);
-        }
-      }
-
-      if ($bDebugThisFunction) {
-        echo "Looking up paths for:\n";
-        print_r($xPathSet);
-      }
-
-      // Now fetch all text-parts that match. (May be 0,1 or many)
-      foreach($xPathSet as $absoluteXPath) {
-        unset($text);
-        if ($text =& $this->wholeText($absoluteXPath, $textPartNr)) {
-          $textSet[] =& $text;
-        } else {
-          // The node does not yet have any text, so we have to add a '' string so that
-          // if we insert or replace to it, then we'll actually have something to op on.
-          $this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr-1] = '';
-          $textSet[] =& $this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr-1];
-        }
-      }
-
-      $status = TRUE;
-    } while (FALSE); // END try-block
-    
-    if (!$status) $result = FALSE;
-    else          $result = $textSet;
-
-    if ($bDebugThisFunction) $this->_closeDebugFunction($aStartTime, $result);
-
-    return $result;
-  }
-  
-
-  /**
-   * Resolves an xPathQuery vector for a node op for modification
-   *
-   * It is possible to create a brand new object, and try to append and insert nodes
-   * into it, so this is a version of _resolveXPathQuery() that will autocreate the
-   * super root if it detects that it is not present and the $xPathQuery is empty.
-   *
-   * Also it demands that there be at least one node returned, and displays a suitable
-   * error message if the returned xPathSet does not contain any nodes.
-   * 
-   * @param  $xPathQuery (string) An xpath query targeting a single node.  If empty() 
-   *                              returns the root node and auto creates the root node
-   *                              if it doesn't exist.
-   * @param  $function   (string) The function in which this check was called
-   * @return             (array)  Vector of $absoluteXPath's (May be empty)
-   * @see    _resolveXPathQuery()
-   */
-  function _resolveXPathQueryForNodeMod($xPathQuery, $functionName) {
-    $xPathSet = array();
-    if (empty($xPathQuery)) {
-      // You can append even if the root node doesn't exist.
-      if (!isset($this->nodeIndex[$xPathQuery])) $this->_createSuperRoot();
-      $xPathSet[] = '';
-      // However, you can only append to the super root, if there isn't already a root entry.
-      $rootNodes = $this->_resolveXPathQuery('/*','appendChild');
-      if (count($rootNodes) !== 0) {
-        $this->_displayError(sprintf($this->errorStrings['RootNodeAlreadyExists']), __LINE__, __FILE__, FALSE);
-        return array();
-      }
-    } else {
-      $xPathSet = $this->_resolveXPathQuery($xPathQuery,'appendChild');
-      if (sizeOf($xPathSet) === 0) {
-        $this->_displayError(sprintf($this->errorStrings['NoNodeMatch'], $xPathQuery), __LINE__, __FILE__, FALSE);
-        return array();
-      }
-    }
-    return $xPathSet;
-  }
-
-  /**
-   * Resolves an xPathQuery vector depending on the property['modMatch']
-   * 
-   * To:
-   *   - all matches, 
-   *   - the first
-   *   - none (If the query matches more then one node.)
-   * see  setModMatch() for details
-   * 
-   * @param  $xPathQuery (string) An xpath query targeting a single node.  If empty() 
-   *                              returns the root node (if it exists).
-   * @param  $function   (string) The function in which this check was called
-   * @return             (array)  Vector of $absoluteXPath's (May be empty)
-   * @see    setModMatch()
-   */
-  function _resolveXPathQuery($xPathQuery, $function) {
-    $xPathSet = array();
-    do { // try-block
-      if (isSet($this->nodeIndex[$xPathQuery])) {
-        $xPathSet[] = $xPathQuery;
-        break; // try-block
-      }
-      if (empty($xPathQuery)) break; // try-block
-      if (substr($xPathQuery, -1) === '/') break; // If the xPathQuery ends with '/' then it cannot be a good query.
-      // If this xPathQuery is not absolute then attempt to evaluate it
-      $xPathSet = $this->match($xPathQuery);
-      
-      $resultSize = sizeOf($xPathSet);
-      switch($this->properties['modMatch']) {
-        case XPATH_QUERYHIT_UNIQUE : 
-          if ($resultSize >1) {
-            $xPathSet = array();
-            if ($this->properties['verboseLevel']) $this->_displayError("Canceled function '{$function}'. The query '{$xPathQuery}' mached {$resultSize} nodes and 'modMatch' is set to XPATH_QUERYHIT_UNIQUE.", __LINE__, __FILE__, FALSE);
-          }
-          break;
-        case XPATH_QUERYHIT_FIRST : 
-          if ($resultSize >1) {
-            $xPathSet = array($xPathSet[0]);
-            if ($this->properties['verboseLevel']) $this->_displayError("Only modified first node in function '{$function}' because the query '{$xPathQuery}' mached {$resultSize} nodes and 'modMatch' is set to XPATH_QUERYHIT_FIRST.", __LINE__, __FILE__, FALSE);
-          }
-          break;
-        default: ; // DO NOTHING
-      }
-    } while (FALSE);
-    
-    if ($this->properties['verboseLevel'] >= 2) $this->_displayMessage("'{$xPathQuery}' parameter from '{$function}' returned the following nodes: ".(count($xPathSet)?implode('<br>', $xPathSet):'[none]'), __LINE__, __FILE__);
-    return $xPathSet;
-  }
-} // END OF CLASS XPath
-
-// -----------------------------------------------------------------------------------------
-// -----------------------------------------------------------------------------------------
-// -----------------------------------------------------------------------------------------
-// -----------------------------------------------------------------------------------------
-
-/**************************************************************************************************
-// Usage Sample:
-// -------------
-// Following code will give you an idea how to work with PHP.XPath. It's a working sample
-// to help you get started. :o)
-// Take the comment tags away and run this file.
-**************************************************************************************************/
-
-/**
- * Produces a short title line.
- */
-function _title($title) { 
-  echo "<br><hr><b>" . htmlspecialchars($title) . "</b><hr>\n";
-}
-
-$self = isSet($_SERVER) ? $_SERVER['PHP_SELF'] : $PHP_SELF;
-if (basename($self) == 'XPath.class.php') {
-  // The sampe source:
-  $q = '?';
-  $xmlSource = <<< EOD
-  <{$q}Process_Instruction test="&copy;&nbsp;All right reserved" {$q}>
-    <AAA foo="bar"> ,,1,,
-      ..1.. <![CDATA[ bla  bla 
-      newLine blo blo ]]>
-      <BBB foo="bar">
-        ..2..
-      </BBB>..3..<CC/>   ..4..</AAA> 
-EOD;
-  
-  // The sample code:
-  $xmlOptions = array(XML_OPTION_CASE_FOLDING => TRUE, XML_OPTION_SKIP_WHITE => TRUE);
-  $xPath =& new XPath(FALSE, $xmlOptions);
-  //$xPath->bDebugXmlParse = TRUE;
-  if (!$xPath->importFromString($xmlSource)) { echo $xPath->getLastError(); exit; }
-  
-  _title("Following was imported:");
-  echo $xPath->exportAsHtml();
-  
-  _title("Get some content");
-  echo "Last text part in &lt;AAA&gt;: '" . $xPath->wholeText('/AAA[1]', -1) ."'<br>\n";
-  echo "All the text in  &lt;AAA&gt;: '" . $xPath->wholeText('/AAA[1]') ."'<br>\n";
-  echo "The attibute value  in  &lt;BBB&gt; using getAttributes('/AAA[1]/BBB[1]', 'FOO'): '" . $xPath->getAttributes('/AAA[1]', 'FOO') ."'<br>\n";
-  echo "The attibute value  in  &lt;BBB&gt; using getData('/AAA[1]/@FOO'): '" . $xPath->getData('/AAA[1]/@FOO') ."'<br>\n";
-  
-  _title("Append some additional XML below /AAA/BBB:");
-  $xPath->appendChild('/AAA[1]/BBB[1]', '<CCC> Step 1. Append new node </CCC>', $afterText=FALSE);
-  $xPath->appendChild('/AAA[1]/BBB[1]', '<CCC> Step 2. Append new node </CCC>', $afterText=TRUE);
-  $xPath->appendChild('/AAA[1]/BBB[1]', '<CCC> Step 3. Append new node </CCC>', $afterText=TRUE);
-  echo $xPath->exportAsHtml();
-  
-  _title("Insert some additional XML below <AAA>:");
-  $xPath->reindexNodeTree();
-  $xPath->insertChild('/AAA[1]/BBB[1]', '<BB> Step 1. Insert new node </BB>', $shiftRight=TRUE, $afterText=TRUE);
-  $xPath->insertChild('/AAA[1]/BBB[1]', '<BB> Step 2. Insert new node </BB>', $shiftRight=FALSE, $afterText=TRUE);
-  $xPath->insertChild('/AAA[1]/BBB[1]', '<BB> Step 3. Insert new node </BB>', $shiftRight=FALSE, $afterText=FALSE);
-  echo $xPath->exportAsHtml();
-
-  _title("Replace the last <BB> node with new XML data '&lt;DDD&gt; Replaced last BB &lt;/DDD&gt;':");
-  $xPath->reindexNodeTree();
-  $xPath->replaceChild('/AAA[1]/BB[last()]', '<DDD> Replaced last BB </DDD>', $afterText=FALSE);
-  echo $xPath->exportAsHtml();
-  
-  _title("Replace second <BB> node with normal text");
-  $xPath->reindexNodeTree();
-  $xPath->replaceChildByData('/AAA[1]/BB[2]', '"Some new text"');
-  echo $xPath->exportAsHtml();
-}
-
-?>
\ No newline at end of file



More information about the pLog-svn mailing list