<?php

/** @file

 * The Query Path package provides tools for manipulating a Document Object Model.

 * The two major DOMs are the XML DOM and the HTML DOM. Using Query Path, you can 

 * build, parse, search, and modify DOM documents.

 *

 * To use Query Path, this is the only file you should need to import.

 *

 * Standard usage:

 * @code

 * <?php

 * require 'QueryPath/QueryPath.php';

 * $qp = qp('#myID', '<?xml version="1.0"?><test><foo id="myID"/></test>');

 * $qp->append('<new><elements/></new>')->writeHTML();

 * ?>

 * @endcode

 *

 * The above would print (formatted for readability):

 * @code

 * <?xml version="1.0"?>

 * <test>

 *  <foo id="myID">

 *    <new>

 *      <element/>

 *    </new>

 *  </foo>

 * </test>

 * @endcode

 *

 * To learn about the functions available to a Query Path object, 

 * see {@link QueryPath}. The {@link qp()} function is used to build

 * new QueryPath objects. The documentation for that function explains the

 * wealth of arguments that the function can take.

 *

 * Included with the source code for QueryPath is a complete set of unit tests

 * as well as some example files. Those are good resources for learning about

 * how to apply QueryPath's tools. The full API documentation can be generated

 * from these files using PHPDocumentor.

 *

 * If you are interested in building extensions for QueryParser, see the 

 * {@link QueryPathExtender} class. There, you will find information on adding

 * your own tools to QueryPath.

 *

 * QueryPath also comes with a full CSS 3 selector parser implementation. If

 * you are interested in reusing that in other code, you will want to start

 * with {@link CssEventHandler.php}, which is the event interface for the parser.

 *

 * All of the code in QueryPath is licensed under either the LGPL or an MIT-like

 * license (you may choose which you prefer). All of the code is Copyright, 2009

 * by Matt Butcher.

 *

 * @author M Butcher <matt @aleph-null.tv>

 * @license http://opensource.org/licenses/lgpl-2.1.php The GNU Lesser GPL (LGPL) or an MIT-like license.

 * @see QueryPath

 * @see qp()

 * @see http://querypath.org The QueryPath home page.

 * @see http://api.querypath.org An online version of the API docs.

 * @see http://technosophos.com For how-tos and examples.

 * @copyright Copyright (c) 2009, Matt Butcher.

 * @version -UNSTABLE%

 *

 */



/** @addtogroup querypath_core Core API

 * Core classes and functions for QueryPath.

 *

 * These are the classes, objects, and functions that developers who use QueryPath

 * are likely to use. The qp() and htmlqp() functions are the best place to start,

 * while most of the frequently used methods are part of the QueryPath object.

 */



/** @addtogroup querypath_util Utilities

 * Utility classes for QueryPath.

 *

 * These classes add important, but less-often used features to QueryPath. Some of

 * these are used transparently (QueryPathIterator). Others you can use directly in your 

 * code (QueryPathEntities).

 */



/*   * @namespace QueryPath

 * The core classes that compose QueryPath.

 *

 * The QueryPath classes contain the brunt of the QueryPath code. If you are 

 * interested in working with just the CSS engine, you may want to look at CssEventHandler,

 * which can be used without the rest of QueryPath. If you are interested in looking 

 * carefully at QueryPath's implementation details, then the QueryPath class is where you 

 * should begin. If you are interested in writing extensions, than you may want to look at

 * QueryPathExtension, and also at some of the simple extensions, such as QPXML.

 */

 

/**

 * Regular expression for checking whether a string looks like XML.

 * @deprecated This is no longer used in QueryPath.

 */

define('ML_EXP','/^[^<]*(<(.|\s)+>)[^>]*$/');



/**

 * The CssEventHandler interfaces with the CSS parser.

 */

require_once 'CssEventHandler.php';

/**

 * The extender is used to provide support for extensions.

 */

require_once 'QueryPathExtension.php';



/**

 * Build a new Query Path.

 * This builds a new Query Path object. The new object can be used for 

 * reading, search, and modifying a document.

 *

 * While it is permissible to directly create new instances of a QueryPath

 * implementation, it is not advised. Instead, you should use this function

 * as a factory.

 *

 * Example:

 * @code

 * <?php

 * qp(); // New empty QueryPath

 * qp('path/to/file.xml'); // From a file

 * qp('<html><head></head><body></body></html>'); // From HTML or XML

 * qp(QueryPath::XHTML_STUB); // From a basic HTML document.

 * qp(QueryPath::XHTML_STUB, 'title'); // Create one from a basic HTML doc and position it at the title element.

 *

 * // Most of the time, methods are chained directly off of this call.

 * qp(QueryPath::XHTML_STUB, 'body')->append('<h1>Title</h1>')->addClass('body-class');

 * ?>

 * @endcode

 *

 * This function is used internally by QueryPath. Anything that modifies the

 * behavior of this function may also modify the behavior of common QueryPath

 * methods.

 *

 * <b>Types of documents that QueryPath can support</b>

 *

 *  qp() can take any of these as its first argument:

 *

 *  - A string of XML or HTML (See {@link XHTML_STUB})

 *  - A path on the file system or a URL

 *  - A {@link DOMDocument} object

 *  - A {@link SimpleXMLElement} object.

 *  - A {@link DOMNode} object.

 *  - An array of {@link DOMNode} objects (generally {@link DOMElement} nodes).

 *  - Another {@link QueryPath} object.

 *

 * Keep in mind that most features of QueryPath operate on elements. Other 

 * sorts of DOMNodes might not work with all features.

 *

 * <b>Supported Options</b>

 *  - context: A stream context object. This is used to pass context info

 *    to the underlying file IO subsystem.

 *  - encoding: A valid character encoding, such as 'utf-8' or 'ISO-8859-1'.

 *    The default is system-dependant, typically UTF-8. Note that this is 

 *    only used when creating new documents, not when reading existing content.

 *    (See convert_to_encoding below.)

 *  - parser_flags: An OR-combined set of parser flags. The flags supported

 *    by the DOMDocument PHP class are all supported here.

 *  - omit_xml_declaration: Boolean. If this is TRUE, then certain output

 *    methods (like {@link QueryPath::xml()}) will omit the XML declaration

 *    from the beginning of a document.

 *  - replace_entities: Boolean. If this is TRUE, then any of the insertion

 *    functions (before(), append(), etc.) will replace named entities with

 *    their decimal equivalent, and will replace un-escaped ampersands with 

 *    a numeric entity equivalent.

 *  - ignore_parser_warnings: Boolean. If this is TRUE, then E_WARNING messages

 *    generated by the XML parser will not cause QueryPath to throw an exception.

 *    This is useful when parsing

 *    badly mangled HTML, or when failure to find files should not result in 

 *    an exception. By default, this is FALSE -- that is, parsing warnings and 

 *    IO warnings throw exceptions.

 *  - convert_to_encoding: Use the MB library to convert the document to the 

 *    named encoding before parsing. This is useful for old HTML (set it to

 *    iso-8859-1 for best results). If this is not supplied, no character set 

 *    conversion will be performed. See {@link mb_convert_encoding()}.

 *    (QueryPath 1.3 and later)

 *  - convert_from_encoding: If 'convert_to_encoding' is set, this option can be

 *    used to explicitly define what character set the source document is using.

 *    By default, QueryPath will allow the MB library to guess the encoding.

 *    (QueryPath 1.3 and later)

 *  - strip_low_ascii: If this is set to TRUE then markup will have all low ASCII

 *    characters (<32) stripped out before parsing. This is good in cases where 

 *    icky HTML has (illegal) low characters in the document.

 *  - use_parser: If 'xml', Parse the document as XML. If 'html', parse the 

 *    document as HTML. Note that the XML parser is very strict, while the 

 *    HTML parser is more lenient, but does enforce some of the DTD/Schema.

 *    <i>By default, QueryPath autodetects the type.</i>

 *  - escape_xhtml_js_css_sections: XHTML needs script and css sections to be

 *    escaped. Yet older readers do not handle CDATA sections, and comments do not

 *    work properly (for numerous reasons). By default, QueryPath's *XHTML methods

 *    will wrap a script body with a CDATA declaration inside of C-style comments.

 *    If you want to change this, you can set this option with one of the 

 *    JS_CSS_ESCAPE_* constants, or you can write your own.

 *  - QueryPath_class: (ADVANCED) Use this to set the actual classname that

 *    {@link qp()} loads as a QueryPath instance. It is assumed that the 

 *    class is either {@link QueryPath} or a subclass thereof. See the test 

 *    cases for an example.

 *

 * @ingroup querypath_core

 * @param mixed $document

 *  A document in one of the forms listed above.

 * @param string $string 

 *  A CSS 3 selector.

 * @param array $options

 *  An associative array of options. Currently supported options are listed above.

 * @return QueryPath

 */

function qp($document = NULL, $string = NULL, $options = array()) {

  

  $qpClass = isset($options['QueryPath_class']) ? $options['QueryPath_class'] : 'QueryPath';

  

  $qp = new $qpClass($document, $string, $options);

  return $qp;

}



/**

 * A special-purpose version of {@link qp()} designed specifically for HTML.

 *

 * XHTML (if valid) can be easily parsed by {@link qp()} with no problems. However,

 * because of the way that libxml handles HTML, there are several common steps that

 * need to be taken to reliably parse non-XML HTML documents. This function is

 * a convenience tool for configuring QueryPath to parse HTML.

 *

 * The following options are automatically set unless overridden:

 *  - ignore_parser_warnings: TRUE

 *  - convert_to_encoding: ISO-8859-1 (the best for the HTML parser).

 *  - convert_from_encoding: auto (autodetect encoding)

 *  - use_parser: html

 *

 * Parser warning messages are also suppressed, so if the parser emits a warning,

 * the application will not be notified. This is equivalent to 

 * calling @code@qp()@endcode.

 *

 * Warning: Character set conversions will only work if the Multi-Byte (mb) library

 * is installed and enabled. This is usually enabled, but not always.

 *

 * @ingroup querypath_core

 * @see qp()

 */

function htmlqp($document = NULL, $selector = NULL, $options = array()) {



  // Need a way to force an HTML parse instead of an XML parse when the 

  // doctype is XHTML, since many XHTML documents are not valid XML

  // (because of coding errors, not by design).

  

  $options += array(

    'ignore_parser_warnings' => TRUE,

    'convert_to_encoding' => 'ISO-8859-1',

    'convert_from_encoding' => 'auto',

    //'replace_entities' => TRUE,

    'use_parser' => 'html',

    // This is stripping actually necessary low ASCII.

    //'strip_low_ascii' => TRUE,

  );

  return @qp($document, $selector, $options);

}



/**

 * The Query Path object is the primary tool in this library.

 *

 * To create a new Query Path, use the {@link qp()} function.

 *

 * If you are new to these documents, start at the {@link QueryPath.php} page.

 * There you will find a quick guide to the tools contained in this project.

 *

 * A note on serialization: QueryPath uses DOM classes internally, and those

 * do not serialize well at all. In addition, QueryPath may contain many

 * extensions, and there is no guarantee that extensions can serialize. The

 * moral of the story: Don't serialize QueryPath.

 *

 * @see qp()

 * @see QueryPath.php

 * @ingroup querypath_core

 */

class QueryPath implements IteratorAggregate, Countable {

  

  /**

   * The version string for this version of QueryPath.

   *

   * Standard releases will be of the following form: <MAJOR>.<MINOR>[.<PATCH>][-STABILITY].

   *

   * Examples:

   * - 2.0

   * - 2.1.1

   * - 2.0-alpha1

   *

   * Developer releases will always be of the form dev-<DATE>.

   *

   * @since 2.0

   */

  const VERSION = '-UNSTABLE%';

  

  /**

   * This is a stub HTML 4.01 document.

   *

   * <b>Using {@link QueryPath::XHTML_STUB} is preferred.</b>

   *

   * This is primarily for generating legacy HTML content. Modern web applications

   * should use {@link QueryPath::XHTML_STUB}.

   *

   * Use this stub with the HTML familiy of methods ({@link html()}, 

   * {@link writeHTML()}, {@link innerHTML()}).

   */

  const HTML_STUB = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

  <html lang="en">

  <head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <title>Untitled</title>

  </head>

  <body></body>

  </html>';

  

  /**

   * This is a stub XHTML document.

   * 

   * Since XHTML is an XML format, you should use XML functions with this document

   * fragment. For example, you should use {@link xml()}, {@link innerXML()}, and 

   * {@link writeXML()}.

   * 

   * This can be passed into {@link qp()} to begin a new basic HTML document.

   *

   * Example:

   * @code

   * $qp = qp(QueryPath::XHTML_STUB); // Creates a new XHTML document

   * $qp->writeXML(); // Writes the document as well-formed XHTML.

   * @endcode

   * @since 2.0

   */

  const XHTML_STUB = '<?xml version="1.0"?>

  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

  <html xmlns="http://www.w3.org/1999/xhtml">

  <head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

  <title>Untitled</title>

  </head>

  <body></body>

  </html>';

  

  /**

   * Default parser flags.

   *

   * These are flags that will be used if no global or local flags override them.

   * @since 2.0

   */

  const DEFAULT_PARSER_FLAGS = NULL;

  

  const JS_CSS_ESCAPE_CDATA = '\\1';

  const JS_CSS_ESCAPE_CDATA_CCOMMENT = '/* \\1 */';

  const JS_CSS_ESCAPE_CDATA_DOUBLESLASH = '// \\1';

  const JS_CSS_ESCAPE_NONE = '';

  

  //const IGNORE_ERRORS = 1544; //E_NOTICE | E_USER_WARNING | E_USER_NOTICE;

  private $errTypes = 771; //E_ERROR; | E_USER_ERROR;

  

  /**

   * The base DOMDocument.

   */

  protected $document = NULL;

  private $options = array(

    'parser_flags' => NULL,

    'omit_xml_declaration' => FALSE,

    'replace_entities' => FALSE,

    'exception_level' => 771, // E_ERROR | E_USER_ERROR | E_USER_WARNING | E_WARNING

    'ignore_parser_warnings' => FALSE,

    'escape_xhtml_js_css_sections' => self::JS_CSS_ESCAPE_CDATA_CCOMMENT,

  );

  /**

   * The array of matches.

   */

  protected $matches = array();

  /**

   * The last array of matches.

   */

  protected $last = array(); // Last set of matches.

  private $ext = array(); // Extensions array.

  

  /**

   * The number of current matches.

   *

   * @see count()

   */

  public $length = 0;

  

  /**

   * Constructor.

   *

   * This should not be called directly. Use the {@link qp()} factory function

   * instead.

   *

   * @param mixed $document 

   *   A document-like object.

   * @param string $string

   *   A CSS 3 Selector

   * @param array $options

   *   An associative array of options.

   * @see qp()

   */

  public function __construct($document = NULL, $string = NULL, $options = array()) {

    $string = trim($string);

    $this->options = $options + QueryPathOptions::get() + $this->options;

    

    $parser_flags = isset($options['parser_flags']) ? $options['parser_flags'] : self::DEFAULT_PARSER_FLAGS;

    if (!empty($this->options['ignore_parser_warnings'])) {

      // Don't convert parser warnings into exceptions.

      $this->errTypes = 257; //E_ERROR | E_USER_ERROR;

    }

    elseif (isset($this->options['exception_level'])) {

      // Set the error level at which exceptions will be thrown. By default, 

      // QueryPath will throw exceptions for 

      // E_ERROR | E_USER_ERROR | E_WARNING | E_USER_WARNING.

      $this->errTypes = $this->options['exception_level'];

    }

    

    // Empty: Just create an empty QP.

    if (empty($document)) {

      $this->document = isset($this->options['encoding']) ? new DOMDocument('1.0', $this->options['encoding']) : new DOMDocument();

      $this->setMatches(new SplObjectStorage());

    }

    // Figure out if document is DOM, HTML/XML, or a filename

    elseif (is_object($document)) {

      

      if ($document instanceof QueryPath) {

        $this->matches = $document->get(NULL, TRUE);

        if ($this->matches->count() > 0)

          $this->document = $this->getFirstMatch()->ownerDocument;

      }

      elseif ($document instanceof DOMDocument) {

        $this->document = $document;

        //$this->matches = $this->matches($document->documentElement);

        $this->setMatches($document->documentElement);

      }

      elseif ($document instanceof DOMNode) {

        $this->document = $document->ownerDocument;

        //$this->matches = array($document);

        $this->setMatches($document);

      }

      elseif ($document instanceof SimpleXMLElement) {

        $import = dom_import_simplexml($document);

        $this->document = $import->ownerDocument;

        //$this->matches = array($import);

        $this->setMatches($import);

      }

      elseif ($document instanceof SplObjectStorage) {

        if ($document->count() == 0) {

          throw new QueryPathException('Cannot initialize QueryPath from an empty SplObjectStore');

        }

        $this->matches = $document;

        $this->document = $this->getFirstMatch()->ownerDocument;

      }

      else {

        throw new QueryPathException('Unsupported class type: ' . get_class($document));

      }

    }

    elseif (is_array($document)) {

      //trigger_error('Detected deprecated array support', E_USER_NOTICE);

      if (!empty($document) && $document[0] instanceof DOMNode) {

        $found = new SplObjectStorage();

        foreach ($document as $item) $found->attach($item);

        //$this->matches = $found;

        $this->setMatches($found);

        $this->document = $this->getFirstMatch()->ownerDocument;

      }

    }

    elseif ($this->isXMLish($document)) {

      // $document is a string with XML

      $this->document = $this->parseXMLString($document);

      $this->setMatches($this->document->documentElement);

    }

    else {

      

      // $document is a filename

      $context = empty($options['context']) ? NULL : $options['context'];

      $this->document = $this->parseXMLFile($document, $parser_flags, $context);

      $this->setMatches($this->document->documentElement);

    }

    

    // Do a find if the second param was set.

    if (isset($string) && strlen($string) > 0) {

      $this->find($string);

    }

  }

  

  /**

   * A static function for transforming data into a Data URL.

   *

   * This can be used to create Data URLs for injection into CSS, JavaScript, or other 

   * non-XML/HTML content. If you are working with QP objects, you may want to use

   * {@link dataURL()} instead.

   *

   * @param mixed $data

   *  The contents to inject as the data. The value can be any one of the following:

   *  - A URL: If this is given, then the subsystem will read the content from that URL. THIS 

   *    MUST BE A FULL URL, not a relative path.

   *  - A string of data: If this is given, then the subsystem will encode the string.

   *  - A stream or file handle: If this is given, the stream's contents will be encoded

   *    and inserted as data.

   *  (Note that we make the assumption here that you would never want to set data to be

   *  a URL. If this is an incorrect assumption, file a bug.)

   * @param string $mime

   *  The MIME type of the document.

   * @param resource $context

   *  A valid context. Use this only if you need to pass a stream context. This is only necessary

   *  if $data is a URL. (See {@link stream_context_create()}).

   * @return 

   *  An encoded data URL.

   */

  public static function encodeDataURL($data, $mime = 'application/octet-stream', $context = NULL) {

    if (is_resource($data)) {

      $data = stream_get_contents($data);

    }

    elseif (filter_var($data, FILTER_VALIDATE_URL)) {

      $data = file_get_contents($data, FALSE, $context);

    }

    

    $encoded = base64_encode($data);

    

    return 'data:' . $mime . ';base64,' . $encoded;

  }

  

  /**

   * Get the effective options for the current QueryPath object.

   *

   * This returns an associative array of all of the options as set

   * for the current QueryPath object. This includes default options,

   * options directly passed in via {@link qp()} or the constructor,

   * an options set in the {@link QueryPathOptions} object.

   *

   * The order of merging options is this:

   *  - Options passed in using {@link qp()} are highest priority, and will

   *    override other options.

   *  - Options set with {@link QueryPathOptions} will override default options,

   *    but can be overridden by options passed into {@link qp()}.

   *  - Default options will be used when no overrides are present.

   *

   * This function will return the options currently used, with the above option

   * overriding having been calculated already.

   *

   * @return array

   *  An associative array of options, calculated from defaults and overridden 

   *  options. 

   * @see qp()

   * @see QueryPathOptions::set()

   * @see QueryPathOptions::merge()

   * @since 2.0

   */

  public function getOptions() {

    return $this->options;

  }

  

  /**

   * Select the root element of the document.

   *

   * This sets the current match to the document's root element. For 

   * practical purposes, this is the same as:

   * @code

   * qp($someDoc)->find(':root');

   * @endcode

   * However, since it doesn't invoke a parser, it has less overhead. It also 

   * works in cases where the QueryPath has been reduced to zero elements (a

   * case that is not handled by find(':root') because there is no element

   * whose root can be found).

   *

   * @param string $selector

   *  A selector. If this is supplied, QueryPath will navigate to the 

   *  document root and then run the query. (Added in QueryPath 2.0 Beta 2) 

   * @return QueryPath

   *  The QueryPath object, wrapping the root element (document element)

   *  for the current document.

   */

  public function top($selector = NULL) {

    $this->setMatches($this->document->documentElement);

    // print '=====================' . PHP_EOL;

    // var_dump($this->document);

    // print '=====================' . PHP_EOL;

    return !empty($selector) ? $this->find($selector) : $this;

  }

  

  /**

   * Given a CSS Selector, find matching items.

   *

   * @param string $selector

   *   CSS 3 Selector

   * @return QueryPath

   * @see filter()

   * @see is()

   * @todo If a find() returns zero matches, then a subsequent find() will

   *  also return zero matches, even if that find has a selector like :root.

   *  The reason for this is that the {@link QueryPathCssEventHandler} does

   *  not set the root of the document tree if it cannot find any elements

   *  from which to determine what the root is. The workaround is to use 

   *  {@link top()} to select the root element again.

   */

  public function find($selector) {

    

    // Optimize for ID/Class searches. These two take a long time

    // when a rdp is used. Using an XPath pushes work to C code.

    $ids = array();

    

    $regex = '/^#([\w-]+)$|^\.([\w-]+)$/'; // $1 is ID, $2 is class.

    //$regex = '/^#([\w-]+)$/';

    if (preg_match($regex, $selector, $ids) === 1) {

      // If $1 is a match, we have an ID.

      if (!empty($ids[1])) {

        $xpath = new DOMXPath($this->document);

        foreach ($this->matches as $item) {

          

          // For whatever reasons, the .// does not work correctly

          // if the selected element is the root element. So we have

          // an awful hack.

          if ($item->isSameNode($this->document->documentElement) ) {

            $xpathQuery = "//*[@id='{$ids[1]}']";

          }

          // This is the correct XPath query.

          else {

            $xpathQuery = ".//*[@id='{$ids[1]}']";

          }

          //$nl = $xpath->query("//*[@id='{$ids[1]}']", $item);

          //$nl = $xpath->query(".//*[@id='{$ids[1]}']", $item);

          $nl = $xpath->query($xpathQuery, $item);

          if ($nl->length > 0) {

            $this->setMatches($nl->item(0));

            break;

          }

          else {

            // If no match is found, we set an empty.

            $this->noMatches();

          }

        }

      }

      // Quick search for class values. While the XPath can't do it

      // all, it is faster than doing a recusive node search.

      else {

        $xpath = new DOMXPath($this->document);

        $found = new SplObjectStorage();

        foreach ($this->matches as $item) {

          // See comments on this in the #id code above.

          if ($item->isSameNode($this->document->documentElement) ) {

            $xpathQuery = "//*[@class]";

          }

          // This is the correct XPath query.

          else {

            $xpathQuery = ".//*[@class]";

          }

          $nl = $xpath->query($xpathQuery, $item);

          for ($i = 0; $i < $nl->length; ++$i) {

            $vals = explode(' ', $nl->item($i)->getAttribute('class'));

            if (in_array($ids[2], $vals)) $found->attach($nl->item($i));

          }

        }

        $this->setMatches($found);

      }

      

      return $this;

    }

    

    

    $query = new QueryPathCssEventHandler($this->matches);

    $query->find($selector);

    //$this->matches = $query->getMatches();

    $this->setMatches($query->getMatches());

    return $this;

  }

  

  /**

   * Execute an XPath query and store the results in the QueryPath.

   *

   * Most methods in this class support CSS 3 Selectors. Sometimes, though,

   * XPath provides a finer-grained query language. Use this to execute

   * XPath queries.

   *

   * Beware, though. QueryPath works best on DOM Elements, but an XPath 

   * query can return other nodes, strings, and values. These may not work with

   * other QueryPath functions (though you will be able to access the

   * values with {@link get()}).

   *

   * @param string $query

   *  An XPath query.

   * @param array $options

   *  Currently supported options are:

   *   - 'namespace_prefix': And XML namespace prefix to be used as the default. Used 

   *      in conjunction with 'namespace_uri'

   *   - 'namespace_uri': The URI to be used as the default namespace URI. Used 

   *      with 'namespace_prefix'

   * @return QueryPath 

   *  A QueryPath object wrapping the results of the query.

   * @see find()

   * @author M Butcher

   * @author Xavier Prud'homme

   */

  public function xpath($query, $options = array()) {

    $xpath = new DOMXPath($this->document);

    

    // Register a default namespace.

    if (!empty($options['namespace_prefix']) && !empty($options['namespace_uri'])) {

      $xpath->registerNamespace($options['namespace_prefix'], $options['namespace_uri']);

    }

    

    $found = new SplObjectStorage();

    foreach ($this->matches as $item) {

      $nl = $xpath->query($query, $item);

      if ($nl->length > 0) {

        for ($i = 0; $i < $nl->length; ++$i) $found->attach($nl->item($i));

      }

    }

    $this->setMatches($found);

    return $this;

  }

  

  /**

   * Get the number of elements currently wrapped by this object.

   *

   * Note that there is no length property on this object.

   *

   * @return int

   *  Number of items in the object.

   * @deprecated QueryPath now implements Countable, so use count().

   */

  public function size() {

    return $this->matches->count();

  }

  

  /**

   * Get the number of elements currently wrapped by this object.

   *

   * Since QueryPath is Countable, the PHP count() function can also

   * be used on a QueryPath.

   *

   * @code

   * <?php

   *  count(qp($xml, 'div'));

   * ?>

   * @endcode

   *

   * @return int

   *  The number of matches in the QueryPath.

   */

  public function count() {

    return $this->matches->count();

  }

  

  /**

   * Get one or all elements from this object.

   *

   * When called with no paramaters, this returns all objects wrapped by 

   * the QueryPath. Typically, these are DOMElement objects (unless you have

   * used {@link map()}, {@link xpath()}, or other methods that can select

   * non-elements).

   *

   * When called with an index, it will return the item in the QueryPath with 

   * that index number.

   *

   * Calling this method does not change the QueryPath (e.g. it is 

   * non-destructive).

   *

   * You can use qp()->get() to iterate over all elements matched. You can

   * also iterate over qp() itself (QueryPath implementations must be Traversable). 

   * In the later case, though, each item 

   * will be wrapped in a QueryPath object. To learn more about iterating

   * in QueryPath, see {@link examples/techniques.php}.

   *

   * @param int $index

   *   If specified, then only this index value will be returned. If this 

   *   index is out of bounds, a NULL will be returned.

   * @param boolean $asObject

   *   If this is TRUE, an {@link SplObjectStorage} object will be returned 

   *   instead of an array. This is the preferred method for extensions to use.

   * @return mixed

   *   If an index is passed, one element will be returned. If no index is

   *   present, an array of all matches will be returned.

   * @see eq()

   * @see SplObjectStorage

   */

  public function get($index = NULL, $asObject = FALSE) {

    if (isset($index)) {

      return ($this->size() > $index) ? $this->getNthMatch($index) : NULL;

    }

    // Retain support for legacy.

    if (!$asObject) {

      $matches = array();

      foreach ($this->matches as $m) $matches[] = $m;

      return $matches;

    }

    return $this->matches;

  }

  

  /**

   * Get the DOMDocument that we currently work with.

   *

   * This returns the current DOMDocument. Any changes made to this document will be

   * accessible to QueryPath, as both will share access to the same object.

   * 

   * @return DOMDocument

   */

  public function document() {

    return $this->document;

  }

  

  /**

   * On an XML document, load all XIncludes.

   *

   * @return QueryPath

   */

  public function xinclude() {

    $this->document->xinclude();

    return $this;

  }

  

  /**

   * Get all current elements wrapped in an array.

   * Compatibility function for jQuery 1.4, but identical to calling {@link get()}

   * with no parameters.

   *

   * @return array

   *  An array of DOMNodes (typically DOMElements).

   */

  public function toArray() {

    return $this->get();

  }

  /**

   * Get/set an attribute.

   * - If no parameters are specified, this returns an associative array of all 

   *   name/value pairs.

   * - If both $name and $value are set, then this will set the attribute name/value

   *   pair for all items in this object. 

   * - If $name is set, and is an array, then

   *   all attributes in the array will be set for all items in this object.

   * - If $name is a string and is set, then the attribute value will be returned.

   *

   * When an attribute value is retrieved, only the attribute value of the FIRST

   * match is returned.

   *

   * @param mixed $name

   *   The name of the attribute or an associative array of name/value pairs.

   * @param string $value

   *   A value (used only when setting an individual property).

   * @return mixed

   *   If this was a setter request, return the QueryPath object. If this was

   *   an access request (getter), return the string value.

   * @see removeAttr()

   * @see tag()

   * @see hasAttr()

   * @see hasClass()

   */

  public function attr($name = NULL, $value = NULL) {

    

    // Default case: Return all attributes as an assoc array.

    if (is_null($name)) {

      if ($this->matches->count() == 0) return NULL;

      $ele = $this->getFirstMatch();

      $buffer = array();

      

      // This does not appear to be part of the DOM

      // spec. Nor is it documented. But it works.

      foreach ($ele->attributes as $name => $attrNode) {

        $buffer[$name] = $attrNode->value;

      }

      return $buffer;

    }

    

    // multi-setter

    if (is_array($name)) {

      foreach ($name as $k => $v) {

        foreach ($this->matches as $m) $m->setAttribute($k, $v);

      }

      return $this;

    }

    // setter

    if (isset($value)) {

      foreach ($this->matches as $m) $m->setAttribute($name, $value);

      return $this;

    }

    

    //getter

    if ($this->matches->count() == 0) return NULL;

    

    // Special node type handler:

    if ($name == 'nodeType') {

      return $this->getFirstMatch()->nodeType;

    }

    

    // Always return first match's attr.

    return $this->getFirstMatch()->getAttribute($name);

  }

  /**

   * Check to see if the given attribute is present.

   *

   * This returns TRUE if <em>all</em> selected items have the attribute, or 

   * FALSE if at least one item does not have the attribute.

   *

   * @param string $attrName

   *  The attribute name.

   * @return boolean

   *  TRUE if all matches have the attribute, FALSE otherwise.

   * @since 2.0

   * @see attr()

   * @see hasClass()

   */

  public function hasAttr($attrName) {

    foreach ($this->matches as $match) {

      if (!$match->hasAttribute($attrName)) return FALSE;

    }

    return TRUE;

  }

  

  /**

   * Set/get a CSS value for the current element(s).

   * This sets the CSS value for each element in the QueryPath object.

   * It does this by setting (or getting) the style attribute (without a namespace).

   *

   * For example, consider this code:

   * @code

   * <?php

   * qp(HTML_STUB, 'body')->css('background-color','red')->html();

   * ?>

   * @endcode

   * This will return the following HTML:

   * @code

   * <body style="background-color: red"/>

   * @endcode

   *

   * If no parameters are passed into this function, then the current style

   * element will be returned unparsed. Example:

   * @code

   * <?php

   * qp(HTML_STUB, 'body')->css('background-color','red')->css();

   * ?>

   * @endcode

   * This will return the following:

   * @code

   * background-color: red

   * @endcode

   *

   * As of QueryPath 2.1, existing style attributes will be merged with new attributes.

   * (In previous versions of QueryPath, a call to css() overwrite the existing style

   * values).

   *

   * @param mixed $name

   *  If this is a string, it will be used as a CSS name. If it is an array,

   *  this will assume it is an array of name/value pairs of CSS rules. It will

   *  apply all rules to all elements in the set.

   * @param string $value

   *  The value to set. This is only set if $name is a string.

   * @return QueryPath

   */

  public function css($name = NULL, $value = '') {

    if (empty($name)) {

      return $this->attr('style');

    }

    

    // Get any existing CSS.

    $css = array();

    foreach ($this->matches as $match) {

      $style = $match->getAttribute('style');

      if (!empty($style)) {

        // XXX: Is this sufficient?

        $style_array = explode(';', $style);

        foreach ($style_array as $item) {

          $item = trim($item);

          

          // Skip empty attributes.

          if (strlen($item) == 0) continue;

          

          list($css_att, $css_val) = explode(':',$item, 2);

          $css[$css_att] = trim($css_val);

        }

      }

    }

    

    if (is_array($name)) {

      // Use array_merge instead of + to preserve order.

      $css = array_merge($css, $name);

    }

    else {

      $css[$name] = $value;

    }

    

    // Collapse CSS into a string.

    $format = '%s: %s;';

    $css_string = '';

    foreach ($css as $n => $v) {

      $css_string .= sprintf($format, $n, trim($v));

    }

    

    $this->attr('style', $css_string);

    return $this;

  }

  

  /**

   * Insert or retrieve a Data URL.

   *

   * When called with just $attr, it will fetch the result, attempt to decode it, and

   * return an array with the MIME type and the application data.

   *

   * When called with both $attr and $data, it will inject the data into all selected elements

   * So @code$qp->dataURL('src', file_get_contents('my.png'), 'image/png')@endcode will inject 

   * the given PNG image into the selected elements.

   *

   * The current implementation only knows how to encode and decode Base 64 data.

   *

   * Note that this is known *not* to work on IE 6, but should render fine in other browsers.

   *

   * @param string $attr

   *  The name of the attribute.

   * @param mixed $data

   *  The contents to inject as the data. The value can be any one of the following:

   *  - A URL: If this is given, then the subsystem will read the content from that URL. THIS 

   *    MUST BE A FULL URL, not a relative path.

   *  - A string of data: If this is given, then the subsystem will encode the string.

   *  - A stream or file handle: If this is given, the stream's contents will be encoded

   *    and inserted as data.

   *  (Note that we make the assumption here that you would never want to set data to be

   *  a URL. If this is an incorrect assumption, file a bug.)

   * @param string $mime

   *  The MIME type of the document.

   * @param resource $context

   *  A valid context. Use this only if you need to pass a stream context. This is only necessary

   *  if $data is a URL. (See {@link stream_context_create()}).

   * @return

   *  If this is called as a setter, this will return a QueryPath object. Otherwise, it

   *  will attempt to fetch data out of the attribute and return that.

   * @see http://en.wikipedia.org/wiki/Data:_URL

   * @see attr()

   * @since 2.1

   */

  public function dataURL($attr, $data = NULL, $mime = 'application/octet-stream', $context = NULL) {

    if (is_null($data)) {

      // Attempt to fetch the data

      $data = $this->attr($attr);

      if (empty($data) || is_array($data) || strpos($data, 'data:') !== 0) {

        return;

      }

      

      // So 1 and 2 should be MIME types, and 3 should be the base64-encoded data.

      $regex = '/^data:([a-zA-Z0-9]+)\/([a-zA-Z0-9]+);base64,(.*)$/';

      $matches = array();

      preg_match($regex, $data, $matches);

      

      if (!empty($matches)) {

        $result = array(

          'mime' => $matches[1] . '/' . $matches[2],

          'data' => base64_decode($matches[3]),

        );

        return $result;

      }

    }

    else {

      

      $attVal = self::encodeDataURL($data, $mime, $context);

      

      return $this->attr($attr, $attVal);

      

    }

  }

  



  

  /**

   * Remove the named attribute from all elements in the current QueryPath.

   *

   * This will remove any attribute with the given name. It will do this on each

   * item currently wrapped by QueryPath.

   *

   * As is the case in jQuery, this operation is not considered destructive.

   *

   * @param string $name

   *  Name of the parameter to remove.

   * @return QueryPath

   *  The QueryPath object with the same elements.

   * @see attr()

   */

  public function removeAttr($name) {

    foreach ($this->matches as $m) {

      //if ($m->hasAttribute($name))

        $m->removeAttribute($name);

    }

    return $this;

  }

  /**

   * Reduce the matched set to just one.

   *

   * This will take a matched set and reduce it to just one item -- the item 

   * at the index specified. This is a destructive operation, and can be undone

   * with {@link end()}.

   *

   * @param $index

   *  The index of the element to keep. The rest will be 

   *  discarded.

   * @return QueryPath

   * @see get()

   * @see is()

   * @see end()

   */

  public function eq($index) {

    // XXX: Might there be a more efficient way of doing this?

    $this->setMatches($this->getNthMatch($index));

    return $this;

  }

  /**

   * Given a selector, this checks to see if the current set has one or more matches.

   *

   * Unlike jQuery's version, this supports full selectors (not just simple ones).

   *

   * @param string $selector

   *   The selector to search for. As of QueryPath 2.1.1, this also supports passing a

   *   DOMNode object.

   * @return boolean

   *   TRUE if one or more elements match. FALSE if no match is found.

   * @see get()

   * @see eq()

   */

  public function is($selector) {

    

    if (is_object($selector)) {

      if ($selector instanceof DOMNode) {

        return count($this->matches) == 1 && $selector->isSameNode($this->get(0));

      }

      elseif ($selector instanceof Traversable) {

        if (count($selector) != count($this->matches)) {

          return FALSE;

        }

        // Without $seen, there is an edge case here if $selector contains the same object

        // more than once, but the counts are equal. For example, [a, a, a, a] will

        // pass an is() on [a, b, c, d]. We use the $seen SPLOS to prevent this.

        $seen = new SplObjectStorage();

        foreach ($selector as $item) {

          if (!$this->matches->contains($item) || $seen->contains($item)) {

            return FALSE;

          }

          $seen->attach($item);

        }

        return TRUE;

      }

      throw new Exception('Cannot compare an object to a QueryPath.');

      return FALSE;

    }

    

    foreach ($this->matches as $m) {

      $q = new QueryPathCssEventHandler($m);

      if ($q->find($selector)->getMatches()->count()) {

        return TRUE;

      }

    }

    return FALSE;

  }

  /**

   * Filter a list down to only elements that match the selector.

   * Use this, for example, to find all elements with a class, or with 

   * certain children.

   *

   * @param string $selector

   *   The selector to use as a filter.

   * @return QueryPath

   *   The QueryPath with non-matching items filtered out.

   * @see filterLambda()

   * @see filterCallback()

   * @see map()

   * @see find()

   * @see is()

   */

  public function filter($selector) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) if (qp($m, NULL, $this->options)->is($selector)) $found->attach($m);

    $this->setMatches($found);

    return $this;

  }

  /**

   * Filter based on a lambda function.

   *

   * The function string will be executed as if it were the body of a 

   * function. It is passed two arguments:

   * - $index: The index of the item.

   * - $item: The current Element.

   * If the function returns boolean FALSE, the item will be removed from

   * the list of elements. Otherwise it will be kept.

   *

   * Example:

   * @code

   * qp('li')->filterLambda('qp($item)->attr("id") == "test"');

   * @endcode

   *

   * The above would filter down the list to only an item whose ID is

   * 'text'.

   *

   * @param string $fn

   *  Inline lambda function in a string.

   * @return QueryPath

   * @see filter()

   * @see map()

   * @see mapLambda()

   * @see filterCallback()

   */

  public function filterLambda($fn) {

    $function = create_function('$index, $item', $fn);

    $found = new SplObjectStorage();

    $i = 0;

    foreach ($this->matches as $item)

      if ($function($i++, $item) !== FALSE) $found->attach($item);

    

    $this->setMatches($found);

    return $this;

  }

  

  /**

   * Use regular expressions to filter based on the text content of matched elements.

   *

   * Only items that match the given regular expression will be kept. All others will

   * be removed.

   *

   * The regular expression is run against the <i>text content</i> (the PCDATA) of the 

   * elements. This is a way of filtering elements based on their content. 

   *

   * Example:

   * @code

   *  <?xml version="1.0"?>

   *  <div>Hello <i>World</i></div>

   * @endcode

   *

   * @code

   *  <?php

   *    // This will be 1.

   *    qp($xml, 'div')->filterPreg('/World/')->size();

   *  ?>

   * @endcode

   *

   * The return value above will be 1 because the text content of @codeqp($xml, 'div')@endcode is

   * @codeHello World@endcode.

   *

   * Compare this to the behavior of the <em>:contains()</em> CSS3 pseudo-class.

   * 

   * @param string $regex

   *  A regular expression.

   * @return QueryPath

   * @see filter()

   * @see filterCallback()

   * @see preg_match()

   */

  public function filterPreg($regex) {

    

    $found = new SplObjectStorage();

    

    foreach ($this->matches as $item) {

      if (preg_match($regex, $item->textContent) > 0) {

        $found->attach($item);

      }

    }

    $this->setMatches($found);

    

    return $this;

  }

  /**

   * Filter based on a callback function.

   *

   * A callback may be any of the following:

   *  - a function: 'my_func'.

   *  - an object/method combo: $obj, 'myMethod'

   *  - a class/method combo: 'MyClass', 'myMethod'

   * Note that classes are passed in strings. Objects are not.

   *

   * Each callback is passed to arguments:

   *  - $index: The index position of the object in the array.

   *  - $item: The item to be operated upon.

   *

   * If the callback function returns FALSE, the item will be removed from the 

   * set of matches. Otherwise the item will be considered a match and left alone.

   *

   * @param callback $callback.

   *   A callback either as a string (function) or an array (object, method OR 

   *   classname, method).

   * @return QueryPath

   *   Query path object augmented according to the function.

   * @see filter()

   * @see filterLambda()

   * @see map()

   * @see is()

   * @see find()

   */

  public function filterCallback($callback) {

    $found = new SplObjectStorage();

    $i = 0;

    if (is_callable($callback)) {

      foreach($this->matches as $item) 

        if (call_user_func($callback, $i++, $item) !== FALSE) $found->attach($item);

    }

    else {

      throw new QueryPathException('The specified callback is not callable.');

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Filter a list to contain only items that do NOT match.

   *

   * @param string $selector

   *  A selector to use as a negation filter. If the filter is matched, the 

   *  element will be removed from the list.

   * @return QueryPath

   *  The QueryPath object with matching items filtered out.

   * @see find()

   */

  public function not($selector) {

    $found = new SplObjectStorage();

    if ($selector instanceof DOMElement) {

      foreach ($this->matches as $m) if ($m !== $selector) $found->attach($m); 

    }

    elseif (is_array($selector)) {

      foreach ($this->matches as $m) {

        if (!in_array($m, $selector, TRUE)) $found->attach($m);

      }

    }

    elseif ($selector instanceof SplObjectStorage) {

      foreach ($this->matches as $m) if ($selector->contains($m)) $found->attach($m); 

    }

    else {

      foreach ($this->matches as $m) if (!qp($m, NULL, $this->options)->is($selector)) $found->attach($m);

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get an item's index.

   *

   * Given a DOMElement, get the index from the matches. This is the 

   * converse of {@link get()}.

   *

   * @param DOMElement $subject

   *  The item to match.

   * 

   * @return mixed

   *  The index as an integer (if found), or boolean FALSE. Since 0 is a 

   *  valid index, you should use strong equality (===) to test..

   * @see get()

   * @see is()

   */

  public function index($subject) {

    

    $i = 0;

    foreach ($this->matches as $m) {

      if ($m === $subject) {

        return $i;

      }

      ++$i;

    }

    return FALSE;

  }

  /**

   * Run a function on each item in a set.

   *

   * The mapping callback can return anything. Whatever it returns will be

   * stored as a match in the set, though. This means that afer a map call, 

   * there is no guarantee that the elements in the set will behave correctly

   * with other QueryPath functions.

   *

   * Callback rules:

   * - If the callback returns NULL, the item will be removed from the array.

   * - If the callback returns an array, the entire array will be stored in 

   *   the results.

   * - If the callback returns anything else, it will be appended to the array 

   *   of matches.

   *

   * @param callback $callback

   *  The function or callback to use. The callback will be passed two params:

   *  - $index: The index position in the list of items wrapped by this object.

   *  - $item: The current item.

   *

   * @return QueryPath

   *  The QueryPath object wrapping a list of whatever values were returned

   *  by each run of the callback.

   *

   * @see QueryPath::get()

   * @see filter()

   * @see find()

   */

  public function map($callback) {

    $found = new SplObjectStorage();

    

    if (is_callable($callback)) {

      $i = 0;

      foreach ($this->matches as $item) {

        $c = call_user_func($callback, $i, $item);

        if (isset($c)) {

          if (is_array($c) || $c instanceof Iterable) {

            foreach ($c as $retval) {

              if (!is_object($retval)) {

                $tmp = new stdClass();

                $tmp->textContent = $retval;

                $retval = $tmp;

              }

              $found->attach($retval);

            }

          }

          else {

            if (!is_object($c)) {

              $tmp = new stdClass();

              $tmp->textContent = $c;

              $c = $tmp;

            }

            $found->attach($c);

          }

        }

        ++$i;

      }

    }

    else {

      throw new QueryPathException('Callback is not callable.');

    }

    $this->setMatches($found, FALSE);

    return $this;

  }

  /**

   * Narrow the items in this object down to only a slice of the starting items.

   *

   * @param integer $start

   *  Where in the list of matches to begin the slice.

   * @param integer $length

   *  The number of items to include in the slice. If nothing is specified, the 

   *  all remaining matches (from $start onward) will be included in the sliced

   *  list.

   * @return QueryPath

   * @see array_slice()

   */

  public function slice($start, $length = 0) {

    $end = $length;

    $found = new SplObjectStorage();

    if ($start >= $this->size()) {

      $this->setMatches($found);

      return $this;

    }

    

    $i = $j = 0;

    foreach ($this->matches as $m) {

      if ($i >= $start) {

        if ($end > 0 && $j >= $end) {

          break;

        }

        $found->attach($m);

        ++$j;

      }

      ++$i;

    }

    

    $this->setMatches($found);

    return $this;

  }

  /**

   * Run a callback on each item in the list of items.

   *

   * Rules of the callback:

   * - A callback is passed two variables: $index and $item. (There is no 

   *   special treatment of $this, as there is in jQuery.)

   *   - You will want to pass $item by reference if it is not an

   *     object (DOMNodes are all objects).

   * - A callback that returns FALSE will stop execution of the each() loop. This

   *   works like break in a standard loop.

   * - A TRUE return value from the callback is analogous to a continue statement.

   * - All other return values are ignored.

   *

   * @param callback $callback

   *  The callback to run.

   * @return QueryPath

   *  The QueryPath.

   * @see eachLambda()

   * @see filter()

   * @see map()

   */

  public function each($callback) {

    if (is_callable($callback)) {

      $i = 0;

      foreach ($this->matches as $item) {

        if (call_user_func($callback, $i, $item) === FALSE) return $this;

        ++$i;

      }

    }

    else {

      throw new QueryPathException('Callback is not callable.');

    }

    return $this;

  }

  /**

   * An each() iterator that takes a lambda function.

   * 

   * @param string $lambda

   *  The lambda function. This will be passed ($index, &$item).

   * @return QueryPath

   *  The QueryPath object.

   * @see each()

   * @see filterLambda()

   * @see filterCallback()

   * @see map()

   */

  public function eachLambda($lambda) {

    $index = 0;

    foreach ($this->matches as $item) {

      $fn = create_function('$index, &$item', $lambda);

      if ($fn($index, $item) === FALSE) return $this;

      ++$index;

    }

    return $this;

  }

  /**

   * Insert the given markup as the last child.

   *

   * The markup will be inserted into each match in the set.

   *

   * The same element cannot be inserted multiple times into a document. DOM

   * documents do not allow a single object to be inserted multiple times 

   * into the DOM. To insert the same XML repeatedly, we must first clone

   * the object. This has one practical implication: Once you have inserted

   * an element into the object, you cannot further manipulate the original 

   * element and expect the changes to be replciated in the appended object.

   * (They are not the same -- there is no shared reference.) Instead, you

   * will need to retrieve the appended object and operate on that.

   *

   * @param mixed $data

   *  This can be either a string (the usual case), or a DOM Element.

   * @return QueryPath

   *  The QueryPath object.

   * @see appendTo()

   * @see prepend()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function append($data) {

    $data = $this->prepareInsert($data);

    if (isset($data)) {

      if (empty($this->document->documentElement) && $this->matches->count() == 0) {

        // Then we assume we are writing to the doc root

        $this->document->appendChild($data);

        $found = new SplObjectStorage();

        $found->attach($this->document->documentElement);

        $this->setMatches($found);

      }

      else {

        // You can only append in item once. So in cases where we

        // need to append multiple times, we have to clone the node.

        foreach ($this->matches as $m) { 

          // DOMDocumentFragments are even more troublesome, as they don't

          // always clone correctly. So we have to clone their children.

          if ($data instanceof DOMDocumentFragment) {

            foreach ($data->childNodes as $n)

              $m->appendChild($n->cloneNode(TRUE));

          }

          else {

            // Otherwise a standard clone will do.

            $m->appendChild($data->cloneNode(TRUE));

          }

          

        }

      }

        

    }

    return $this;

  }

  /**

   * Append the current elements to the destination passed into the function.

   *

   * This cycles through all of the current matches and appends them to 

   * the context given in $destination. If a selector is provided then the 

   * $destination is queried (using that selector) prior to the data being

   * appended. The data is then appended to the found items.

   *

   * @param QueryPath $dest

   *  A QueryPath object that will be appended to.

   * @return QueryPath

   *  The original QueryPath, unaltered. Only the destination QueryPath will

   *  be modified.

   * @see append()

   * @see prependTo()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function appendTo(QueryPath $dest) {

    foreach ($this->matches as $m) $dest->append($m);

    return $this;

  }

  /**

   * Insert the given markup as the first child.

   *

   * The markup will be inserted into each match in the set.

   *

   * @param mixed $data

   *  This can be either a string (the usual case), or a DOM Element.

   * @return QueryPath

   * @see append()

   * @see before()

   * @see after()

   * @see prependTo()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function prepend($data) {

    $data = $this->prepareInsert($data);

    if (isset($data)) {

      foreach ($this->matches as $m) {

        $ins = $data->cloneNode(TRUE);

        if ($m->hasChildNodes())

          $m->insertBefore($ins, $m->childNodes->item(0));

        else

          $m->appendChild($ins);

      }

    }

    return $this;

  }

  /**

   * Take all nodes in the current object and prepend them to the children nodes of

   * each matched node in the passed-in QueryPath object.

   *

   * This will iterate through each item in the current QueryPath object and 

   * add each item to the beginning of the children of each element in the 

   * passed-in QueryPath object.

   *

   * @see insertBefore()

   * @see insertAfter()

   * @see prepend()

   * @see appendTo()

   * @param QueryPath $dest

   *  The destination QueryPath object.

   * @return QueryPath

   *  The original QueryPath, unmodified. NOT the destination QueryPath.

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function prependTo(QueryPath $dest) {

    foreach ($this->matches as $m) $dest->prepend($m);

    return $this;

  }



  /**

   * Insert the given data before each element in the current set of matches.

   *

   * This will take the give data (XML or HTML) and put it before each of the items that 

   * the QueryPath object currently contains. Contrast this with after().

   * 

   * @param mixed $data

   *  The data to be inserted. This can be XML in a string, a DomFragment, a DOMElement,

   *  or the other usual suspects. (See {@link qp()}).

   * @return QueryPath

   *  Returns the QueryPath with the new modifications. The list of elements currently

   *  selected will remain the same.

   * @see insertBefore()

   * @see after()

   * @see append()

   * @see prepend()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function before($data) {

    $data = $this->prepareInsert($data);

    foreach ($this->matches as $m) {

      $ins = $data->cloneNode(TRUE);

      $m->parentNode->insertBefore($ins, $m);

    }

    

    return $this;

  }

  /**

   * Insert the current elements into the destination document.

   * The items are inserted before each element in the given QueryPath document.

   * That is, they will be siblings with the current elements.

   *

   * @param QueryPath $dest

   *  Destination QueryPath document.

   * @return QueryPath

   *  The current QueryPath object, unaltered. Only the destination QueryPath

   *  object is altered.

   * @see before()

   * @see insertAfter()

   * @see appendTo()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function insertBefore(QueryPath $dest) {

    foreach ($this->matches as $m) $dest->before($m);

    return $this;

  }

  /**

   * Insert the contents of the current QueryPath after the nodes in the 

   * destination QueryPath object.

   *

   * @param QueryPath $dest

   *  Destination object where the current elements will be deposited.

   * @return QueryPath

   *  The present QueryPath, unaltered. Only the destination object is altered.

   * @see after()

   * @see insertBefore()

   * @see append()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function insertAfter(QueryPath $dest) {

    foreach ($this->matches as $m) $dest->after($m);

    return $this;

  }

  /**

   * Insert the given data after each element in the current QueryPath object.

   *

   * This inserts the element as a peer to the currently matched elements.

   * Contrast this with {@link append()}, which inserts the data as children

   * of matched elements.

   *

   * @param mixed $data

   *  The data to be appended.

   * @return QueryPath

   *  The QueryPath object (with the items inserted).

   * @see before()

   * @see append()

   * @throws QueryPathException

   *  Thrown if $data is an unsupported object type.

   */

  public function after($data) {

    $data = $this->prepareInsert($data);

    foreach ($this->matches as $m) {

      $ins = $data->cloneNode(TRUE);

      if (isset($m->nextSibling)) 

        $m->parentNode->insertBefore($ins, $m->nextSibling);

      else

        $m->parentNode->appendChild($ins);

    }

    return $this;

  }

  /**

   * Replace the existing element(s) in the list with a new one.

   *

   * @param mixed $new

   *  A DOMElement or XML in a string. This will replace all elements

   *  currently wrapped in the QueryPath object.

   * @return QueryPath

   *  The QueryPath object wrapping <b>the items that were removed</b>.

   *  This remains consistent with the jQuery API.

   * @see append()

   * @see prepend()

   * @see before()

   * @see after()

   * @see remove()

   * @see replaceAll()

   */

  public function replaceWith($new) {

    $data = $this->prepareInsert($new);

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      $parent = $m->parentNode;

      $parent->insertBefore($data->cloneNode(TRUE), $m);

      $found->attach($parent->removeChild($m));

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Remove the parent element from the selected node or nodes.

   *

   * This takes the given list of nodes and "unwraps" them, moving them out of their parent

   * node, and then deleting the parent node.

   *

   * For example, consider this:

   *

   * @code

   *   <root><wrapper><content/></wrapper></root>

   * @endcode

   * 

   * Now we can run this code:

   * @code

   *   qp($xml, 'content')->unwrap();

   * @endcode

   *

   * This will result in:

   *

   * @code

   *   <root><content/></root>

   * @endcode

   * This is the opposite of {@link wrap()}.

   *

   * <b>The root element cannot be unwrapped.</b> It has no parents.

   * If you attempt to use unwrap on a root element, this will throw a QueryPathException.

   * (You can, however, "Unwrap" a child that is a direct descendant of the root element. This

   * will remove the root element, and replace the child as the root element. Be careful, though.

   * You cannot set more than one child as a root element.)

   *

   * @return QueryPath

   *  The QueryPath object, with the same element(s) selected.

   * @throws QueryPathException

   *  An exception is thrown if one attempts to unwrap a root element.

   * @see wrap()

   * @since 2.1

   * @author mbutcher

   */

  public function unwrap() {

    

    // We do this in two loops in order to

    // capture the case where two matches are

    // under the same parent. Othwerwise we might

    // remove a match before we can move it.

    $parents = new SplObjectStorage();

    foreach ($this->matches as $m) {

      

      // Cannot unwrap the root element.

      if ($m->isSameNode($m->ownerDocument->documentElement)) {

        throw new QueryPathException('Cannot unwrap the root element.');

      }

      

      // Move children to peer of parent.

      $parent = $m->parentNode;

      $old = $parent->removeChild($m);

      $parent->parentNode->insertBefore($old, $parent);

      $parents->attach($parent);

    }

    

    // Now that all the children are moved, we 

    // remove all of the parents.

    foreach ($parents as $ele) {

      $ele->parentNode->removeChild($ele);

    }

    

    return $this;

  }

  /**

   * Wrap each element inside of the given markup.

   *

   * Markup is usually a string, but it can also be a DOMNode, a document

   * fragment, a SimpleXMLElement, or another QueryPath object (in which case

   * the first item in the list will be used.)

   *

   * @param mixed $markup 

   *  Markup that will wrap each element in the current list.

   * @return QueryPath

   *  The QueryPath object with the wrapping changes made.

   * @see wrapAll()

   * @see wrapInner()

   */

  public function wrap($markup) {

    $data = $this->prepareInsert($markup);

    

    // If the markup passed in is empty, we don't do any wrapping.

    if (empty($data)) {

      return $this;

    }

    

    foreach ($this->matches as $m) {

      $copy = $data->firstChild->cloneNode(TRUE);

      

      // XXX: Should be able to avoid doing this over and over.

      if ($copy->hasChildNodes()) {

        $deepest = $this->deepestNode($copy); 

        // FIXME: Does this need a different data structure?

        $bottom = $deepest[0];

      }

      else

        $bottom = $copy;



      $parent = $m->parentNode;

      $parent->insertBefore($copy, $m);

      $m = $parent->removeChild($m);

      $bottom->appendChild($m);

      //$parent->appendChild($copy);

    }

    return $this;  

  }

  /**

   * Wrap all elements inside of the given markup.

   *

   * So all elements will be grouped together under this single marked up 

   * item. This works by first determining the parent element of the first item

   * in the list. It then moves all of the matching elements under the wrapper

   * and inserts the wrapper where that first element was found. (This is in 

   * accordance with the way jQuery works.)

   *

   * Markup is usually XML in a string, but it can also be a DOMNode, a document

   * fragment, a SimpleXMLElement, or another QueryPath object (in which case

   * the first item in the list will be used.)

   * 

   * @param string $markup 

   *  Markup that will wrap all elements in the current list.

   * @return QueryPath

   *  The QueryPath object with the wrapping changes made.

   * @see wrap()

   * @see wrapInner()

   */

  public function wrapAll($markup) {

    if ($this->matches->count() == 0) return;

    

    $data = $this->prepareInsert($markup);

    

    if (empty($data)) {

      return $this;

    }

    

    if ($data->hasChildNodes()) {

      $deepest = $this->deepestNode($data); 

      // FIXME: Does this need fixing?

      $bottom = $deepest[0];

    }

    else

      $bottom = $data;



    $first = $this->getFirstMatch();

    $parent = $first->parentNode;

    $parent->insertBefore($data, $first);

    foreach ($this->matches as $m) {

      $bottom->appendChild($m->parentNode->removeChild($m));

    }

    return $this;

  }

  /**

   * Wrap the child elements of each item in the list with the given markup.

   *

   * Markup is usually a string, but it can also be a DOMNode, a document

   * fragment, a SimpleXMLElement, or another QueryPath object (in which case

   * the first item in the list will be used.)

   *

   * @param string $markup 

   *  Markup that will wrap children of each element in the current list.

   * @return QueryPath

   *  The QueryPath object with the wrapping changes made.

   * @see wrap()

   * @see wrapAll()

   */

  public function wrapInner($markup) {

    $data = $this->prepareInsert($markup);

    

    // No data? Short circuit.

    if (empty($data)) return $this;

    

    if ($data->hasChildNodes()) {

      $deepest = $this->deepestNode($data); 

      // FIXME: ???

      $bottom = $deepest[0];

    }

    else

      $bottom = $data;

      

    foreach ($this->matches as $m) {

      if ($m->hasChildNodes()) {

        while($m->firstChild) {

          $kid = $m->removeChild($m->firstChild);

          $bottom->appendChild($kid);

        }

      }

      $m->appendChild($data);

    }

    return $this; 

  }

  /**

   * Reduce the set of matches to the deepest child node in the tree.

   *

   * This loops through the matches and looks for the deepest child node of all of 

   * the matches. "Deepest", here, is relative to the nodes in the list. It is 

   * calculated as the distance from the starting node to the most distant child

   * node. In other words, it is not necessarily the farthest node from the root

   * element, but the farthest note from the matched element.

   *

   * In the case where there are multiple nodes at the same depth, all of the 

   * nodes at that depth will be included.

   *

   * @return QueryPath

   *  The QueryPath wrapping the single deepest node.

   */

  public function deepest() {

    $deepest = 0;

    $winner = new SplObjectStorage();

    foreach ($this->matches as $m) {

      $local_deepest = 0;

      $local_ele = $this->deepestNode($m, 0, NULL, $local_deepest);

      

      // Replace with the new deepest.

      if ($local_deepest > $deepest) {

        $winner = new SplObjectStorage();

        foreach ($local_ele as $lele) $winner->attach($lele);

        $deepest = $local_deepest;

      }

      // Augument with other equally deep elements.

      elseif ($local_deepest == $deepest) {

        foreach ($local_ele as $lele)

          $winner->attach($lele);

      }

    }

    $this->setMatches($winner);

    return $this;

  }

  

  /**

   * A depth-checking function. Typically, it only needs to be

   * invoked with the first parameter. The rest are used for recursion.

   * @see deepest();

   * @param DOMNode $ele

   *  The element.

   * @param int $depth

   *  The depth guage

   * @param mixed $current

   *  The current set.

   * @param DOMNode $deepest

   *  A reference to the current deepest node.

   * @return array

   *  Returns an array of DOM nodes.

   */

  protected function deepestNode(DOMNode $ele, $depth = 0, $current = NULL, &$deepest = NULL) {

    // FIXME: Should this use SplObjectStorage?

    if (!isset($current)) $current = array($ele);

    if (!isset($deepest)) $deepest = $depth;

    if ($ele->hasChildNodes()) {

      foreach ($ele->childNodes as $child) {

        if ($child->nodeType === XML_ELEMENT_NODE) {

          $current = $this->deepestNode($child, $depth + 1, $current, $deepest);

        }

      }

    }

    elseif ($depth > $deepest) {

      $current = array($ele);

      $deepest = $depth;

    }

    elseif ($depth === $deepest) {

      $current[] = $ele;

    }

    return $current;

  }

  

  /**

   * Prepare an item for insertion into a DOM.

   *

   * This handles a variety of boilerplate tasks that need doing before an 

   * indeterminate object can be inserted into a DOM tree.

   * - If item is a string, this is converted into a document fragment and returned.

   * - If item is a QueryPath, then the first item is retrieved and this call function

   *   is called recursivel.

   * - If the item is a DOMNode, it is imported into the current DOM if necessary.

   * - If the item is a SimpleXMLElement, it is converted into a DOM node and then

   *   imported.

   *

   * @param mixed $item

   *  Item to prepare for insert.

   * @return mixed

   *  Returns the prepared item.

   * @throws QueryPathException

   *  Thrown if the object passed in is not of a supprted object type.

   */

  protected function prepareInsert($item) {

    if(empty($item)) {

      return;

    }

    elseif (is_string($item)) {

      // If configured to do so, replace all entities.

      if ($this->options['replace_entities']) {

        $item = QueryPathEntities::replaceAllEntities($item);

      }

      

      $frag = $this->document->createDocumentFragment();

      try {

        set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);

        $frag->appendXML($item);

      }

      // Simulate a finally block.

      catch (Exception $e) {

        restore_error_handler();

        throw $e;

      }

      restore_error_handler();

      return $frag;

    }

    elseif ($item instanceof QueryPath) {

      if ($item->size() == 0) 

        return;

        

      return $this->prepareInsert($item->get(0));

    }

    elseif ($item instanceof DOMNode) {

      if ($item->ownerDocument !== $this->document) {

        // Deep clone this and attach it to this document

        $item = $this->document->importNode($item, TRUE);

      }

      return $item;

    }

    elseif ($item instanceof SimpleXMLElement) {

      $element = dom_import_simplexml($item);

      return $this->document->importNode($element, TRUE);

    }

    // What should we do here?

    //var_dump($item);

    throw new QueryPathException("Cannot prepare item of unsupported type: " . gettype($item));

  }

  /**

   * The tag name of the first element in the list.

   *

   * This returns the tag name of the first element in the list of matches. If

   * the list is empty, an empty string will be used.

   *

   * @see replaceAll()

   * @see replaceWith()

   * @return string

   *  The tag name of the first element in the list.

   */

  public function tag() {

    return ($this->size() > 0) ? $this->getFirstMatch()->tagName : '';

  }

  /**

   * Remove any items from the list if they match the selector.

   *

   * In other words, each item that matches the selector will be remove 

   * from the DOM document. The returned QueryPath wraps the list of 

   * removed elements.

   *

   * If no selector is specified, this will remove all current matches from

   * the document.

   *

   * @param string $selector

   *  A CSS Selector.

   * @return QueryPath

   *  The Query path wrapping a list of removed items.

   * @see replaceAll()

   * @see replaceWith()

   * @see removeChildren()

   */

  public function remove($selector = NULL) {

    if(!empty($selector)) {

      // Do a non-destructive find.

      $query = new QueryPathCssEventHandler($this->matches);

      $query->find($selector);

      $matches = $query->getMatches();

    }

    else {

      $matches = $this->matches;

    }



    $found = new SplObjectStorage();

    foreach ($matches as $item) { 

      // The item returned is (according to docs) different from 

      // the one passed in, so we have to re-store it.

      $found->attach($item->parentNode->removeChild($item));

    }

    

    // Return a clone QueryPath with just the removed items. If

    // no items are found, this will return an empty QueryPath.

    return count($found) == 0 ? new QueryPath() : new QueryPath($found);

  }

  /**

   * This replaces everything that matches the selector with the first value

   * in the current list.

   *

   * This is the reverse of replaceWith.

   *

   * Unlike jQuery, QueryPath cannot assume a default document. Consequently,

   * you must specify the intended destination document. If it is omitted, the

   * present document is assumed to be tthe document. However, that can result

   * in undefined behavior if the selector and the replacement are not sufficiently

   * distinct.

   *

   * @param string $selector

   *  The selector.

   * @param DOMDocument $document

   *  The destination document.

   * @return QueryPath

   *  The QueryPath wrapping the modified document.

   * @deprecated Due to the fact that this is not a particularly friendly method,

   *  and that it can be easily replicated using {@see replaceWith()}, it is to be 

   *  considered deprecated.

   * @see remove()

   * @see replaceWith()

   */

  public function replaceAll($selector, DOMDocument $document) {

    $replacement = $this->size() > 0 ? $this->getFirstMatch() : $this->document->createTextNode('');

    

    $c = new QueryPathCssEventHandler($document);

    $c->find($selector);

    $temp = $c->getMatches();

    foreach ($temp as $item) {

      $node = $replacement->cloneNode();

      $node = $document->importNode($node);

      $item->parentNode->replaceChild($node, $item);

    }

    return qp($document, NULL, $this->options);

  }

  /**

   * Add more elements to the current set of matches.

   *

   * This begins the new query at the top of the DOM again. The results found

   * when running this selector are then merged into the existing results. In

   * this way, you can add additional elements to the existing set.

   *

   * @param string $selector

   *  A valid selector.

   * @return QueryPath

   *  The QueryPath object with the newly added elements.

   * @see append()

   * @see after()

   * @see andSelf()

   * @see end()

   */

  public function add($selector) {

    

    // This is destructive, so we need to set $last:

    $this->last = $this->matches;

    

    foreach (qp($this->document, $selector, $this->options)->get() as $item)

      $this->matches->attach($item);

    return $this;

  }

  /**

   * Revert to the previous set of matches.

   *

   * This will revert back to the last set of matches (before the last 

   * "destructive" set of operations). This undoes any change made to the set of

   * matched objects. Functions like find() and filter() change the 

   * list of matched objects. The end() function will revert back to the last set of

   * matched items.

   *

   * Note that functions that modify the document, but do not change the list of 

   * matched objects, are not "destructive". Thus, calling append('something')->end()

   * will not undo the append() call.

   *

   * Only one level of changes is stored. Reverting beyond that will result in 

   * an empty set of matches. Example:

   *

   * @code

   * // The line below returns the same thing as qp(document, 'p');

   * qp(document, 'p')->find('div')->end();

   * // This returns an empty array:

   * qp(document, 'p')->end();

   * // This returns an empty array:

   * qp(document, 'p')->find('div')->find('span')->end()->end();

   * @endcode

   *

   * The last one returns an empty array because only one level of changes is stored.

   *

   * @return QueryPath

   *  A QueryPath object reflecting the list of matches prior to the last destructive

   *  operation.

   * @see andSelf()

   * @see add()

   */

  public function end() {

    // Note that this does not use setMatches because it must set the previous

    // set of matches to empty array.

    $this->matches = $this->last;

    $this->last = new SplObjectStorage();

    return $this;

  }

  /**

   * Combine the current and previous set of matched objects.

   *

   * Example:

   *

   * @code

   * qp(document, 'p')->find('div')->andSelf();

   * @endcode

   *

   * The code above will contain a list of all p elements and all div elements that 

   * are beneath p elements.

   *

   * @see end();

   * @return QueryPath

   *  A QueryPath object with the results of the last two "destructive" operations.

   * @see add()

   * @see end()

   */

  public function andSelf() {

    // This is destructive, so we need to set $last:

    $last = $this->matches;

    

    foreach ($this->last as $item) $this->matches->attach($item);

    

    $this->last = $last;

    return $this;

  }

  /**

   * Remove all child nodes.

   *

   * This is equivalent to jQuery's empty() function. (However, empty() is a 

   * PHP built-in, and cannot be used as a method name.)

   *

   * @return QueryPath

   *  The QueryPath object with the child nodes removed.

   * @see replaceWith()

   * @see replaceAll()

   * @see remove()

   */

  public function removeChildren() {

    foreach ($this->matches as $m) {

      while($kid = $m->firstChild) {

        $m->removeChild($kid);

      }

    }

    return $this;

  }

  /**

   * Get the children of the elements in the QueryPath object.

   *

   * If a selector is provided, the list of children will be filtered through

   * the selector.

   *

   * @param string $selector

   *  A valid selector.

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see removeChildren()

   * @see parent()

   * @see parents()

   * @see next()

   * @see prev()

   */

  public function children($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      foreach($m->childNodes as $c) {

        if ($c->nodeType == XML_ELEMENT_NODE) $found->attach($c);

      }

    }

    if (empty($selector)) {

      $this->setMatches($found);

    }

    else {

      $this->matches = $found; // Don't buffer this. It is temporary.

      $this->filter($selector);

    }

    return $this;

  }

  /**

   * Get all child nodes (not just elements) of all items in the matched set.

   *

   * It gets only the immediate children, not all nodes in the subtree.

   *

   * This does not process iframes. Xinclude processing is dependent on the 

   * DOM implementation and configuration.

   *

   * @return QueryPath

   *  A QueryPath object wrapping all child nodes for all elements in the 

   *  QueryPath object.

   * @see find()

   * @see text()

   * @see html()

   * @see innerHTML()

   * @see xml()

   * @see innerXML()

   */

  public function contents() {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      if (empty($m->childNodes)) continue; // Issue #51

      foreach ($m->childNodes as $c) {

        $found->attach($c);

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get a list of siblings for elements currently wrapped by this object.

   *

   * This will compile a list of every sibling of every element in the 

   * current list of elements. 

   *

   * Note that if two siblings are present in the QueryPath object to begin with,

   * then both will be returned in the matched set, since they are siblings of each 

   * other. In other words,if the matches contain a and b, and a and b are siblings of 

   * each other, than running siblings will return a set that contains 

   * both a and b.

   *

   * @param string $selector

   *  If the optional selector is provided, siblings will be filtered through

   *  this expression.

   * @return QueryPath

   *  The QueryPath containing the matched siblings.

   * @see contents()

   * @see children()

   * @see parent()

   * @see parents()

   */

  public function siblings($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      $parent = $m->parentNode;

      foreach ($parent->childNodes as $n) {

        if ($n->nodeType == XML_ELEMENT_NODE && $n !== $m) {

          $found->attach($n);

        }

      }

    }

    if (empty($selector)) {

      $this->setMatches($found);

    }

    else {

      $this->matches = $found; // Don't buffer this. It is temporary.

      $this->filter($selector);

    }

    return $this;

  }

  /**

   * Find the closest element matching the selector.

   *

   * This finds the closest match in the ancestry chain. It first checks the 

   * present element. If the present element does not match, this traverses up 

   * the ancestry chain (e.g. checks each parent) looking for an item that matches.

   *

   * It is provided for jQuery 1.3 compatibility.

   * @param string $selector

   *  A CSS Selector to match.

   * @return QueryPath

   *  The set of matches.

   * @since 2.0

   */

  public function closest($selector) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      

      if (qp($m, NULL, $this->options)->is($selector) > 0) {

        $found->attach($m);

      }

      else {

        while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {

          $m = $m->parentNode;

          // Is there any case where parent node is not an element?

          if ($m->nodeType === XML_ELEMENT_NODE && qp($m, NULL, $this->options)->is($selector) > 0) {

            $found->attach($m);

            break;

          }

        }

      }

      

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get the immediate parent of each element in the QueryPath.

   *

   * If a selector is passed, this will return the nearest matching parent for

   * each element in the QueryPath.

   *

   * @param string $selector

   *  A valid CSS3 selector.

   * @return QueryPath

   *  A QueryPath object wrapping the matching parents.

   * @see children()

   * @see siblings()

   * @see parents()

   */

  public function parent($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {

        $m = $m->parentNode;

        // Is there any case where parent node is not an element?

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0) {

              $found->attach($m);

              break;

            }

          }

          else {

            $found->attach($m);

            break;

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get all ancestors of each element in the QueryPath.

   * 

   * If a selector is present, only matching ancestors will be retrieved.

   *

   * @see parent()

   * @param string $selector

   *  A valid CSS 3 Selector.

   * @return QueryPath

   *  A QueryPath object containing the matching ancestors.

   * @see siblings()

   * @see children()

   */

  public function parents($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {

        $m = $m->parentNode;

        // Is there any case where parent node is not an element?

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0)

              $found->attach($m);

          }

          else 

            $found->attach($m);

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Set or get the markup for an element.

   * 

   * If $markup is set, then the giving markup will be injected into each

   * item in the set. All other children of that node will be deleted, and this

   * new code will be the only child or children. The markup MUST BE WELL FORMED.

   *

   * If no markup is given, this will return a string representing the child 

   * markup of the first node.

   *

   * <b>Important:</b> This differs from jQuery's html() function. This function

   * returns <i>the current node</i> and all of its children. jQuery returns only

   * the children. This means you do not need to do things like this: 

   * @code$qp->parent()->html()@endcode.

   *

   * By default, this is HTML 4.01, not XHTML. Use {@link xml()} for XHTML.

   *

   * @param string $markup

   *  The text to insert.

   * @return mixed

   *  A string if no markup was passed, or a QueryPath if markup was passed.

   * @see xml()

   * @see text()

   * @see contents()

   */

  public function html($markup = NULL) {

    if (isset($markup)) {

      

      if ($this->options['replace_entities']) {

        $markup = QueryPathEntities::replaceAllEntities($markup);

      }

      

      // Parse the HTML and insert it into the DOM

      //$doc = DOMDocument::loadHTML($markup);

      $doc = $this->document->createDocumentFragment();

      $doc->appendXML($markup);

      $this->removeChildren();

      $this->append($doc);

      return $this;

    }

    $length = $this->size();

    if ($length == 0) {

      return NULL;

    }

    // Only return the first item -- that's what JQ does.

    $first = $this->getFirstMatch();



    // Catch cases where first item is not a legit DOM object.

    if (!($first instanceof DOMNode)) {

      return NULL;

    }

    

    // Added by eabrand.

    if(!$first->ownerDocument->documentElement) {

      return NULL;

    }

    

    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {

      return $this->document->saveHTML();

    }

    // saveHTML cannot take a node and serialize it.

    return $this->document->saveXML($first);

  }

  

  /**

   * Fetch the HTML contents INSIDE of the first QueryPath item.

   *

   * <b>This behaves the way jQuery's @codehtml()@endcode function behaves.</b>

   *

   * This gets all children of the first match in QueryPath. 

   *

   * Consider this fragment:

   * @code

   * <div>

   * test <p>foo</p> test

   * </div>

   * @endcode

   *

   * We can retrieve just the contents of this code by doing something like

   * this:

   * @code

   * qp($xml, 'div')->innerHTML();

   * @endcode

   *

   * This would return the following:

   * @codetest <p>foo</p> test@endcode

   *

   * @return string

   *  Returns a string representation of the child nodes of the first

   *  matched element.

   * @see html()

   * @see innerXML()

   * @see innerXHTML()

   * @since 2.0

   */

  public function innerHTML() {

    return $this->innerXML();

  } 

  

  /**

   * Fetch child (inner) nodes of the first match.

   *

   * This will return the children of the present match. For an example, 

   * see {@link innerHTML()}.

   *

   * @see innerHTML()

   * @see innerXML()

   * @return string

   *  Returns a string of XHTML that represents the children of the present

   *  node.

   * @since 2.0

   */

  public function innerXHTML() {

    $length = $this->size();

    if ($length == 0) {

      return NULL;

    }

    // Only return the first item -- that's what JQ does.

    $first = $this->getFirstMatch();



    // Catch cases where first item is not a legit DOM object.

    if (!($first instanceof DOMNode)) {

      return NULL;

    }

    elseif (!$first->hasChildNodes()) {

      return '';

    }

    

    $buffer = '';

    foreach ($first->childNodes as $child) {

      $buffer .= $this->document->saveXML($child, LIBXML_NOEMPTYTAG);

    }

    

    return $buffer;

  }

  

  /**

   * Fetch child (inner) nodes of the first match.

   *

   * This will return the children of the present match. For an example, 

   * see {@link innerHTML()}.

   *

   * @see innerHTML()

   * @see innerXHTML()

   * @return string

   *  Returns a string of XHTML that represents the children of the present

   *  node.

   * @since 2.0

   */

  public function innerXML() {

    $length = $this->size();

    if ($length == 0) {

      return NULL;

    }

    // Only return the first item -- that's what JQ does.

    $first = $this->getFirstMatch();



    // Catch cases where first item is not a legit DOM object.

    if (!($first instanceof DOMNode)) {

      return NULL;

    }

    elseif (!$first->hasChildNodes()) {

      return '';

    }

    

    $buffer = '';

    foreach ($first->childNodes as $child) {

      $buffer .= $this->document->saveXML($child);

    }

    

    return $buffer;

  }

  

  /**

   * Retrieve the text of each match and concatenate them with the given separator.

   *

   * This has the effect of looping through all children, retrieving their text

   * content, and then concatenating the text with a separator.

   *

   * @param string $sep

   *  The string used to separate text items. The default is a comma followed by a

   *  space.

   * @param boolean $filterEmpties

   *  If this is true, empty items will be ignored.

   * @return string

   *  The text contents, concatenated together with the given separator between

   *  every pair of items.

   * @see implode()

   * @see text()

   * @since 2.0

   */

  public function textImplode($sep = ', ', $filterEmpties = TRUE) {

    $tmp = array(); 

    foreach ($this->matches as $m) {

      $txt = $m->textContent;

      $trimmed = trim($txt);

      // If filter empties out, then we only add items that have content.

      if ($filterEmpties) {

        if (strlen($trimmed) > 0) $tmp[] = $txt;

      }

      // Else add all content, even if it's empty.

      else {

        $tmp[] = $txt;

      }

    }

    return implode($sep, $tmp);

  }

  /**

   * Get the text contents from just child elements.

   *

   * This is a specialized variant of textImplode() that implodes text for just the

   * child elements of the current element.

   *

   * @param string $separator

   *  The separator that will be inserted between found text content.

   * @return string

   *  The concatenated values of all children.

   */

  function childrenText($separator = ' ') {

    // Branch makes it non-destructive.

    return $this->branch()->xpath('descendant::text()')->textImplode($separator);

  }

  /**

   * Get or set the text contents of a node.

   * @param string $text

   *  If this is not NULL, this value will be set as the text of the node. It

   *  will replace any existing content.

   * @return mixed 

   *  A QueryPath if $text is set, or the text content if no text

   *  is passed in as a pram.

   * @see html()

   * @see xml()

   * @see contents()

   */

  public function text($text = NULL) {

    if (isset($text)) {

      $this->removeChildren();

      $textNode = $this->document->createTextNode($text);

      foreach ($this->matches as $m) $m->appendChild($textNode);

      return $this;

    }

    // Returns all text as one string:

    $buf = '';

    foreach ($this->matches as $m) $buf .= $m->textContent;

    return $buf;

  }

  /**

   * Get or set the text before each selected item.

   *

   * If $text is passed in, the text is inserted before each currently selected item.

   *

   * If no text is given, this will return the concatenated text after each selected element.

   *

   * @code

   * <?php

   * $xml = '<?xml version="1.0"?><root>Foo<a>Bar</a><b/></root>';

   * 

   * // This will return 'Foo'

   * qp($xml, 'a')->textBefore();

   *

   * // This will insert 'Baz' right before <b/>.

   * qp($xml, 'b')->textBefore('Baz');

   * ?>

   * @endcode

   *

   * @param string $text

   *  If this is set, it will be inserted before each node in the current set of

   *  selected items.

   * @return mixed

   *  Returns the QueryPath object if $text was set, and returns a string (possibly empty)

   *  if no param is passed.

   */

  public function textBefore($text = NULL) {

    if (isset($text)) {

      $textNode = $this->document->createTextNode($text);

      return $this->before($textNode);

    }

    $buffer = '';

    foreach ($this->matches as $m) {

      $p = $m;

      while (isset($p->previousSibling) && $p->previousSibling->nodeType == XML_TEXT_NODE) {

        $p = $p->previousSibling;

        $buffer .= $p->textContent;

      }

    }

    return $buffer;

  }

  

  public function textAfter($text = NULL) {

    if (isset($text)) {

      $textNode = $this->document->createTextNode($text);

      return $this->after($textNode);

    }

    $buffer = '';

    foreach ($this->matches as $m) {

      $n = $m;

      while (isset($n->nextSibling) && $n->nextSibling->nodeType == XML_TEXT_NODE) {

        $n = $n->nextSibling;

        $buffer .= $n->textContent;

      }

    }

    return $buffer;

  }

  

  /**

   * Set or get the value of an element's 'value' attribute.

   *

   * The 'value' attribute is common in HTML form elements. This is a 

   * convenience function for accessing the values. Since this is not  common

   * task on the server side, this method may be removed in future releases. (It 

   * is currently provided for jQuery compatibility.)

   *

   * If a value is provided in the params, then the value will be set for all 

   * matches. If no params are given, then the value of the first matched element

   * will be returned. This may be NULL.

   *

   * @deprecated Just use attr(). There's no reason to use this on the server.

   * @see attr()

   * @param string $value

   * @return mixed

   *  Returns a QueryPath if a string was passed in, and a string if no string

   *  was passed in. In the later case, an error will produce NULL.

   */

  public function val($value = NULL) {

    if (isset($value)) {

      $this->attr('value', $value);

      return $this;

    }

    return $this->attr('value');

  }

  /**

   * Set or get XHTML markup for an element or elements.

   * 

   * This differs from {@link html()} in that it processes (and produces)

   * strictly XML 1.0 compliant markup.

   *

   * Like {@link xml()} and {@link html()}, this functions as both a 

   * setter and a getter.

   *

   * This is a convenience function for fetching HTML in XML format.

   * It does no processing of the markup (such as schema validation).

   * @param string $markup

   *  A string containing XML data.

   * @return mixed

   *  If markup is passed in, a QueryPath is returned. If no markup is passed

   *  in, XML representing the first matched element is returned.

   * @see html()

   * @see innerXHTML()

   */

  public function xhtml($markup = NULL) {

    

    // XXX: This is a minor reworking of the original xml() method.

    // This should be refactored, probably.

    // See http://github.com/technosophos/querypath/issues#issue/10

   

    $omit_xml_decl = $this->options['omit_xml_declaration'];

    if ($markup === TRUE) {

      // Basically, we handle the special case where we don't

      // want the XML declaration to be displayed.

      $omit_xml_decl = TRUE;

    }

    elseif (isset($markup)) {

      return $this->xml($markup);

    }

    

    $length = $this->size();

    if ($length == 0) {

      return NULL;

    }

    

    // Only return the first item -- that's what JQ does.

    $first = $this->getFirstMatch();

    // Catch cases where first item is not a legit DOM object.

    if (!($first instanceof DOMNode)) {

      return NULL;

    }

    

    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {

      

      // Has the unfortunate side-effect of stripping doctype.

      //$text = ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement, LIBXML_NOEMPTYTAG) : $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG));

      $text = $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG);

    }

    else {

      $text = $this->document->saveXML($first, LIBXML_NOEMPTYTAG);

    }

    

    // Issue #47: Using the old trick for removing the XML tag also removed the 

    // doctype. So we remove it with a regex:

    if ($omit_xml_decl) {

      $text = preg_replace('/<\?xml\s[^>]*\?>/', '', $text);

    }

    

    // This is slightly lenient: It allows for cases where code incorrectly places content

    // inside of these supposedly unary elements.

    $unary = '/<(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)(?(?=\s)([^>\/]+))><\/[^>]*>/i';

    $text = preg_replace($unary, '<\\1\\2 />', $text);

    

    // Experimental: Support for enclosing CDATA sections with comments to be both XML compat

    // and HTML 4/5 compat

    $cdata = '/(<!\[CDATA\[|\]\]>)/i';

    $replace = $this->options['escape_xhtml_js_css_sections'];

    $text = preg_replace($cdata, $replace, $text);

    

    return $text;

  }

  /**

   * Set or get the XML markup for an element or elements.

   *

   * Like {@link html()}, this functions in both a setter and a getter mode.

   * 

   * In setter mode, the string passed in will be parsed and then appended to the 

   * elements wrapped by this QueryPath object.When in setter mode, this parses 

   * the XML using the DOMFragment parser. For that reason, an XML declaration 

   * is not necessary.

   *

   * In getter mode, the first element wrapped by this QueryPath object will be 

   * converted to an XML string and returned.

   *

   * @param string $markup

   *  A string containing XML data.

   * @return mixed

   *  If markup is passed in, a QueryPath is returned. If no markup is passed

   *  in, XML representing the first matched element is returned.

   * @see xhtml()

   * @see html()

   * @see text()

   * @see content()

   * @see innerXML()

   */

  public function xml($markup = NULL) {

    $omit_xml_decl = $this->options['omit_xml_declaration'];

    if ($markup === TRUE) {

      // Basically, we handle the special case where we don't

      // want the XML declaration to be displayed.

      $omit_xml_decl = TRUE;

    }

    elseif (isset($markup)) {

      if ($this->options['replace_entities']) {

        $markup = QueryPathEntities::replaceAllEntities($markup);

      }

      $doc = $this->document->createDocumentFragment();

      $doc->appendXML($markup);

      $this->removeChildren();

      $this->append($doc);

      return $this;

    }

    $length = $this->size();

    if ($length == 0) {

      return NULL;

    }

    // Only return the first item -- that's what JQ does.

    $first = $this->getFirstMatch();

    

    // Catch cases where first item is not a legit DOM object.

    if (!($first instanceof DOMNode)) {

      return NULL;

    }

    

    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {

      

      return  ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement) : $this->document->saveXML());

    }

    return $this->document->saveXML($first);

  }

  /**

   * Send the XML document to the client.

   * 

   * Write the document to a file path, if given, or 

   * to stdout (usually the client).

   *

   * This prints the entire document.

   *

   * @param string $path

   *  The path to the file into which the XML should be written. if 

   *  this is NULL, data will be written to STDOUT, which is usually

   *  sent to the remote browser.

   * @param int $options

   *  (As of QueryPath 2.1) Pass libxml options to the saving mechanism.

   * @return QueryPath

   *  The QueryPath object, unmodified.

   * @see xml()

   * @see innerXML()

   * @see writeXHTML()

   * @throws Exception 

   *  In the event that a file cannot be written, an Exception will be thrown.

   */

  public function writeXML($path = NULL, $options = NULL) {

    if ($path == NULL) {

      print $this->document->saveXML(NULL, $options);

    }

    else {

      try {

        set_error_handler(array('QueryPathIOException', 'initializeFromError'));

        $this->document->save($path, $options);

      }

      catch (Exception $e) {

        restore_error_handler();

        throw $e;

      }

      restore_error_handler();

    }

    return $this;

  }

  /**

   * Writes HTML to output.

   *

   * HTML is formatted as HTML 4.01, without strict XML unary tags. This is for

   * legacy HTML content. Modern XHTML should be written using {@link toXHTML()}.

   * 

   * Write the document to stdout (usually the client) or to a file.

   *

   * @param string $path

   *  The path to the file into which the XML should be written. if 

   *  this is NULL, data will be written to STDOUT, which is usually

   *  sent to the remote browser.

   * @return QueryPath

   *  The QueryPath object, unmodified.

   * @see html()

   * @see innerHTML()

   * @throws Exception 

   *  In the event that a file cannot be written, an Exception will be thrown.

   */

  public function writeHTML($path = NULL) {

    if ($path == NULL) {

      print $this->document->saveHTML();

    }

    else {

      try {

        set_error_handler(array('QueryPathParseException', 'initializeFromError'));

        $this->document->saveHTMLFile($path);

      }

      catch (Exception $e) {

        restore_error_handler();

        throw $e;

      }

      restore_error_handler();

    }

    return $this;

  }

  

  /**

   * Write an XHTML file to output.

   *

   * Typically, you should use this instead of {@link writeHTML()}.

   *

   * Currently, this functions identically to {@link toXML()} <i>except that</i>

   * it always uses closing tags (e.g. always @code<script></script>@endcode, 

   * never @code<script/>@endcode). It will

   * write the file as well-formed XML. No XHTML schema validation is done.

   *

   * @see writeXML()

   * @see xml()

   * @see writeHTML()

   * @see innerXHTML()

   * @see xhtml()

   * @param string $path

   *  The filename of the file to write to.

   * @return QueryPath

   *  Returns the QueryPath, unmodified.

   * @throws Exception

   *  In the event that the output file cannot be written, an exception is

   *  thrown.

   * @since 2.0

   */

  public function writeXHTML($path = NULL) {

    return $this->writeXML($path, LIBXML_NOEMPTYTAG);

    /*

    if ($path == NULL) {

      print $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG);

    }

    else {

      try {

        set_error_handler(array('QueryPathIOException', 'initializeFromError'));

        $this->document->save($path, LIBXML_NOEMPTYTAG);

      }

      catch (Exception $e) {

        restore_error_handler();

        throw $e;

      }

      restore_error_handler();

    }

    return $this;

    */

  }

  /**

   * Get the next sibling of each element in the QueryPath.

   *

   * If a selector is provided, the next matching sibling will be returned.

   *

   * @param string $selector

   *  A CSS3 selector.

   * @return QueryPath

   *  The QueryPath object.

   * @see nextAll()

   * @see prev()

   * @see children()

   * @see contents()

   * @see parent()

   * @see parents()

   */

  public function next($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->nextSibling)) {

        $m = $m->nextSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0) {

              $found->attach($m);

              break;

            }

          }

          else {

            $found->attach($m);

            break;

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get all siblings after an element.

   *

   * For each element in the QueryPath, get all siblings that appear after

   * it. If a selector is passed in, then only siblings that match the 

   * selector will be included.

   *

   * @param string $selector 

   *  A valid CSS 3 selector.

   * @return QueryPath

   *  The QueryPath object, now containing the matching siblings.

   * @see next()

   * @see prevAll()

   * @see children()

   * @see siblings()

   */

  public function nextAll($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->nextSibling)) {

        $m = $m->nextSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0) {

              $found->attach($m);

            }

          }

          else {

            $found->attach($m);

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get the next sibling before each element in the QueryPath.

   *

   * For each element in the QueryPath, this retrieves the previous sibling

   * (if any). If a selector is supplied, it retrieves the first matching 

   * sibling (if any is found).

   *

   * @param string $selector

   *  A valid CSS 3 selector.

   * @return QueryPath

   *  A QueryPath object, now containing any previous siblings that have been 

   *  found.

   * @see prevAll()

   * @see next()

   * @see siblings()

   * @see children()

   */

  public function prev($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->previousSibling)) {

        $m = $m->previousSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector)) {

              $found->attach($m);

              break;

            }

          }

          else {

            $found->attach($m);

            break;

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Get the previous siblings for each element in the QueryPath.

   *

   * For each element in the QueryPath, get all previous siblings. If a 

   * selector is provided, only matching siblings will be retrieved.

   *

   * @param string $selector

   *  A valid CSS 3 selector.

   * @return QueryPath

   *  The QueryPath object, now wrapping previous sibling elements.

   * @see prev()

   * @see nextAll()

   * @see siblings()

   * @see contents()

   * @see children()

   */

  public function prevAll($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->previousSibling)) {

        $m = $m->previousSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector)) {

              $found->attach($m);

            }

          }

          else {

            $found->attach($m);

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * @deprecated Use {@link siblings()}.

   */

  public function peers($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      foreach ($m->parentNode->childNodes as $kid) {

        if ($kid->nodeType == XML_ELEMENT_NODE && $m !== $kid) {

          if (!empty($selector)) {

            if (qp($kid, NULL, $this->options)->is($selector)) {

              $found->attach($kid);

            }

          }

          else {

            $found->attach($kid);

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  /**

   * Add a class to all elements in the current QueryPath.

   *

   * This searchers for a class attribute on each item wrapped by the current 

   * QueryPath object. If no attribute is found, a new one is added and its value

   * is set to $class. If a class attribute is found, then the value is appended

   * on to the end.

   *

   * @param string $class 

   *  The name of the class.

   * @return QueryPath

   *  Returns the QueryPath object.

   * @see css()

   * @see attr()

   * @see removeClass()

   * @see hasClass()

   */

  public function addClass($class) {

    foreach ($this->matches as $m) {

      if ($m->hasAttribute('class')) {

        $val = $m->getAttribute('class');

        $m->setAttribute('class', $val . ' ' . $class);

      }

      else {

        $m->setAttribute('class', $class);

      }

    }

    return $this;

  }

  /**

   * Remove the named class from any element in the QueryPath that has it.

   *

   * This may result in the entire class attribute being removed. If there

   * are other items in the class attribute, though, they will not be removed.

   * 

   * Example:

   * Consider this XML:

   * @code

   * <element class="first second"/>

   * @endcode

   *

   * Executing this fragment of code will remove only the 'first' class:

   * @code

   * qp(document, 'element')->removeClass('first');

   * @endcode

   *

   * The resulting XML will be:

   * @code

   * <element class="second"/>

   * @endcode

   *

   * To remove the entire 'class' attribute, you should use {@see removeAttr()}.

   *

   * @param string $class

   *  The class name to remove.

   * @return QueryPath

   *  The modified QueryPath object.

   * @see attr()

   * @see addClass()

   * @see hasClass()

   */

  public function removeClass($class) {

    foreach ($this->matches as $m) {

      if ($m->hasAttribute('class')) {

        $vals = explode(' ', $m->getAttribute('class'));

        if (in_array($class, $vals)) {

          $buf = array();

          foreach ($vals as $v) {

            if ($v != $class) $buf[] = $v;

          }

          if (count($buf) == 0)

            $m->removeAttribute('class');

          else

            $m->setAttribute('class', implode(' ', $buf));

        }

      }

    }

    return $this;

  }

  /**

   * Returns TRUE if any of the elements in the QueryPath have the specified class.

   *

   * @param string $class

   *  The name of the class.

   * @return boolean 

   *  TRUE if the class exists in one or more of the elements, FALSE otherwise.

   * @see addClass()

   * @see removeClass()

   */

  public function hasClass($class) {

    foreach ($this->matches as $m) {

      if ($m->hasAttribute('class')) {

        $vals = explode(' ', $m->getAttribute('class'));

        if (in_array($class, $vals)) return TRUE;

      }

    }

    return FALSE;

  }



  /**

   * Branch the base QueryPath into another one with the same matches.

   *

   * This function makes a copy of the QueryPath object, but keeps the new copy 

   * (initially) pointed at the same matches. This object can then be queried without

   * changing the original QueryPath. However, changes to the elements inside of this

   * QueryPath will show up in the QueryPath from which it is branched.

   * 

   * Compare this operation with {@link cloneAll()}. The cloneAll() call takes

   * the current QueryPath object and makes a copy of all of its matches. You continue

   * to operate on the same QueryPath object, but the elements inside of the QueryPath

   * are copies of those before the call to cloneAll().

   *

   * This, on the other hand, copies <i>the QueryPath</i>, but keeps valid 

   * references to the document and the wrapped elements. A new query branch is 

   * created, but any changes will be written back to the same document.

   *

   * In practice, this comes in handy when you want to do multiple queries on a part

   * of the document, but then return to a previous set of matches. (see {@link QPTPL}

   * for examples of this in practice).

   *

   * Example:

   *

   * @code

   * <?php

   * $qp = qp(QueryPath::HTML_STUB);

   * $branch = $qp->branch();

   * $branch->find('title')->text('Title');

   * $qp->find('body')->text('This is the body')->writeHTML;

   * ?>

   * @endcode

   *

   * Notice that in the code, each of the QueryPath objects is doing its own 

   * query. However, both are modifying the same document. The result of the above 

   * would look something like this:

   *

   * @code

   * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

   * <html xmlns="http://www.w3.org/1999/xhtml">

   * <head>

   *    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>

   *    <title>Title</title>

   * </head>

   * <body>This is the body</body>

   * </html>

   * @endcode

   *

   * Notice that while $qp and $banch were performing separate queries, they 

   * both modified the same document.

   *

   * In jQuery or a browser-based solution, you generally do not need a branching

   * function because there is (implicitly) only one document. In QueryPath, there

   * is no implicit document. Every document must be explicitly specified (and,

   * in most cases, parsed -- which is costly). Branching makes it possible to 

   * work on one document with multiple QueryPath objects.

   *

   * @param string $selector

   *  If a selector is passed in, an additional {@link find()} will be executed

   *  on the branch before it is returned. (Added in QueryPath 2.0.)

   * @return QueryPath

   *  A copy of the QueryPath object that points to the same set of elements that

   *  the original QueryPath was pointing to.

   * @since 1.1

   * @see cloneAll()

   * @see find()

   */

  public function branch($selector = NULL) {

    $temp = qp($this->matches, NULL, $this->options);

    if (isset($selector)) $temp->find($selector);

    return $temp;

  }

  /**

   * Perform a deep clone of each node in the QueryPath.

   *

   * This does not clone the QueryPath object, but instead clones the 

   * list of nodes wrapped by the QueryPath. Every element is deeply 

   * cloned.

   *

   * This method is analogous to jQuery's clone() method.

   *

   * This is a destructive operation, which means that end() will revert

   * the list back to the clone's original.

   * @see qp()

   * @return QueryPath

   */

  public function cloneAll() {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) $found->attach($m->cloneNode(TRUE));

    $this->setMatches($found, FALSE);

    return $this;

  }

  

  /**

   * Clone the QueryPath.

   *

   * This makes a deep clone of the elements inside of the QueryPath.

   *

   * This clones only the QueryPathImpl, not all of the decorators. The

   * clone operator in PHP should handle the cloning of the decorators.

   */

  public function __clone() {

    //XXX: Should we clone the document?

    

    // Make sure we clone the kids.

    $this->cloneAll();

  }  



  /**

   * Detach any items from the list if they match the selector.

   *

   * In other words, each item that matches the selector will be remove

   * from the DOM document. The returned QueryPath wraps the list of

   * removed elements.

   *

   * If no selector is specified, this will remove all current matches from

   * the document.

   *

   * @param string $selector

   *  A CSS Selector.

   * @return QueryPath

   *  The Query path wrapping a list of removed items.

   * @see replaceAll()

   * @see replaceWith()

   * @see removeChildren()

   * @since 2.1

   * @author eabrand

   */

  public function detach($selector = NULL) {



    if(!empty($selector))

    $this->find($selector);



    $found = new SplObjectStorage();

    $this->last = $this->matches;

    foreach ($this->matches as $item) {

      // The item returned is (according to docs) different from

      // the one passed in, so we have to re-store it.

      $found->attach($item->parentNode->removeChild($item));

    }

    $this->setMatches($found);

    return $this;

  }



  /**

   * Attach any items from the list if they match the selector.

   *

   * If no selector is specified, this will remove all current matches from

   * the document.

   *

   * @param QueryPath $dest

   *  A QueryPath Selector.

   * @return QueryPath

   *  The Query path wrapping a list of removed items.

   * @see replaceAll()

   * @see replaceWith()

   * @see removeChildren()

   * @since 2.1

   * @author eabrand

   */

  public function attach(QueryPath $dest) {

    foreach ($this->last as $m) $dest->append($m);

    return $this;

  }

  

  /**

   * Reduce the elements matched by QueryPath to only those which contain the given item.

   *

   * There are two ways in which this is different from jQuery's implementation:

   * - We allow ANY DOMNode, not just DOMElements. That means this will work on 

   *   processor instructions, text nodes, comments, etc.

   * - Unlike jQuery, this implementation of has() follows QueryPath standard behavior

   *   and modifies the existing object. It does not create a brand new object.

   * 

   * @param mixed $contained

   *   - If $contained is a CSS selector (e.g. '#foo'), this will test to see

   *     if the current QueryPath has any elements that contain items that match

   *     the selector.

   *   - If $contained is a DOMNode, then this will test to see if THE EXACT DOMNode

   *     exists in the currently matched elements. (Note that you cannot match across DOM trees, even if it is the same document.)

   * @since 2.1

   * @author eabrand

   * @todo It would be trivially easy to add support for iterating over an array or Iterable of DOMNodes.

   */

  public function has($contained) {

    $found = new SplObjectStorage();

    

    // If it's a selector, we just get all of the DOMNodes that match the selector.

    $nodes = array();

    if (is_string($contained)) {

      // Get the list of nodes.

      $nodes = $this->branch($contained)->get();

    }

    elseif ($contained instanceof DOMNode) {

      // Make a list with one node.

      $nodes = array($contained);

    }

    

    // Now we go through each of the nodes that we are testing. We want to find

    // ALL PARENTS that are in our existing QueryPath matches. Those are the

    // ones we add to our new matches.

    foreach ($nodes as $original_node) {

      $node = $original_node;

      while (!empty($node)/* && $node != $node->ownerDocument*/) {

        if ($this->matches->contains($node)) {

          $found->attach($node);

        }

        $node = $node->parentNode;

      }

    }

    

    $this->setMatches($found);

    return $this;

  }



  /**

   * Empty everything within the specified element.

   *

   * A convenience function for removeChildren(). This is equivalent to jQuery's 

   * empty() function. However, `empty` is a built-in in PHP, and cannot be used as a 

   * function name.

   *

   * @return QueryPath

   *  The QueryPath object with the newly emptied elements.

   * @see removeChildren()

   * @since 2.1

   * @author eabrand

   * @deprecated The removeChildren() function is the preferred method.

   */

  public function emptyElement() {

    $this->removeChildren();

    return $this;

  }

  

  /**

   * Get the even elements, so counter-intuitively 1, 3, 5, etc.

   *

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see removeChildren()

   * @see parent()

   * @see parents()

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function even() {

    $found = new SplObjectStorage();

    $even = false;

    foreach ($this->matches as $m) {

      if ($even && $m->nodeType == XML_ELEMENT_NODE) $found->attach($m);

      $even = ($even) ? false : true;

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }



  /**

   * Get the odd elements, so counter-intuitively 0, 2, 4, etc.

   *

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see removeChildren()

   * @see parent()

   * @see parents()

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function odd() {

    $found = new SplObjectStorage();

    $odd = true;

    foreach ($this->matches as $m) {

      if ($odd && $m->nodeType == XML_ELEMENT_NODE) $found->attach($m);

      $odd = ($odd) ? false : true;

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }

  

  /**

   * Get the first matching element.

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function first() {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      if ($m->nodeType == XML_ELEMENT_NODE) {

        $found->attach($m);

        break;

      }

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }



  /**

   * Get the first child of the matching element.

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function firstChild() {

    // Could possibly use $m->firstChild http://theserverpages.com/php/manual/en/ref.dom.php

    $found = new SplObjectStorage();

    $flag = false;

    foreach ($this->matches as $m) {

      foreach($m->childNodes as $c) {

        if ($c->nodeType == XML_ELEMENT_NODE) {

          $found->attach($c);

          $flag = true;

          break;

        }

      }

      if($flag) break;

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }



  /**

   * Get the last matching element.

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function last() {

    $found = new SplObjectStorage();

    $item = null;

    foreach ($this->matches as $m) {

      if ($m->nodeType == XML_ELEMENT_NODE) {

        $item = $m;

      }

    }

    if ($item) {

      $found->attach($item);

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }



  /**

   * Get the last child of the matching element.

   *

   *

   * @return QueryPath

   *  A QueryPath wrapping all of the children.

   * @see next()

   * @see prev()

   * @since 2.1

   * @author eabrand

   */

  public function lastChild() {

    $found = new SplObjectStorage();

    $item = null;

    foreach ($this->matches as $m) {

      foreach($m->childNodes as $c) {

        if ($c->nodeType == XML_ELEMENT_NODE) {

          $item = $c;

        }

      }

      if ($item) {

        $found->attach($item);

        $item = null;

      }

    }

    $this->setMatches($found);

    $this->matches = $found; // Don't buffer this. It is temporary.

    return $this;

  }



  /**

   * Get all siblings after an element until the selector is reached.

   *

   * For each element in the QueryPath, get all siblings that appear after

   * it. If a selector is passed in, then only siblings that match the

   * selector will be included.

   *

   * @param string $selector

   *  A valid CSS 3 selector.

   * @return QueryPath

   *  The QueryPath object, now containing the matching siblings.

   * @see next()

   * @see prevAll()

   * @see children()

   * @see siblings()

   * @since 2.1

   * @author eabrand

   */

  public function nextUntil($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->nextSibling)) {

        $m = $m->nextSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0) {

              break;

            }

            else {

              $found->attach($m);

            }

          }

          else {

            $found->attach($m);

          }

        }

      }

    }

    $this->setMatches($found);

    return $this;

  } 



  /**

   * Get the previous siblings for each element in the QueryPath

   * until the selector is reached.

   *

   * For each element in the QueryPath, get all previous siblings. If a

   * selector is provided, only matching siblings will be retrieved.

   *

   * @param string $selector

   *  A valid CSS 3 selector.

   * @return QueryPath

   *  The QueryPath object, now wrapping previous sibling elements.

   * @see prev()

   * @see nextAll()

   * @see siblings()

   * @see contents()

   * @see children()

   * @since 2.1

   * @author eabrand

   */

  public function prevUntil($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while (isset($m->previousSibling)) {

        $m = $m->previousSibling;

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector) && qp($m, NULL, $this->options)->is($selector))

          break;

          else

          $found->attach($m);

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  

  /**

   * Get all ancestors of each element in the QueryPath until the selector is reached.

   *

   * If a selector is present, only matching ancestors will be retrieved.

   *

   * @see parent()

   * @param string $selector

   *  A valid CSS 3 Selector.

   * @return QueryPath

   *  A QueryPath object containing the matching ancestors.

   * @see siblings()

   * @see children()

   * @since 2.1

   * @author eabrand

   */

  public function parentsUntil($selector = NULL) {

    $found = new SplObjectStorage();

    foreach ($this->matches as $m) {

      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {

        $m = $m->parentNode;

        // Is there any case where parent node is not an element?

        if ($m->nodeType === XML_ELEMENT_NODE) {

          if (!empty($selector)) {

            if (qp($m, NULL, $this->options)->is($selector) > 0)

            break;

            else

            $found->attach($m);

          }

          else

          $found->attach($m);

        }

      }

    }

    $this->setMatches($found);

    return $this;

  }

  

  /////// INTERNAL FUNCTIONS ////////

  

  

  /**

   * Determine whether a given string looks like XML or not.

   *

   * Basically, this scans a portion of the supplied string, checking to see

   * if it has a tag-like structure. It is possible to "confuse" this, which

   * may subsequently result in parse errors, but in the vast majority of 

   * cases, this method serves as a valid inicator of whether or not the 

   * content looks like XML.

   *

   * Things that are intentional excluded:

   * - plain text with no markup.

   * - strings that look like filesystem paths.

   * 

   * Subclasses SHOULD NOT OVERRIDE THIS. Altering it may be altering

   * core assumptions about how things work. Instead, classes should 

   * override the constructor and pass in only one of the parsed types

   * that this class expects.

   */

  protected function isXMLish($string) {

    // Long strings will exhaust the regex engine, so we

    // grab a representative string.

    // $test = substr($string, 0, 255);

    return (strpos($string, '<') !== FALSE && strpos($string, '>') !== FALSE);

    //return preg_match(ML_EXP, $test) > 0;

  }

  

  private function parseXMLString($string, $flags = NULL) {

    

    $document = new DOMDocument('1.0');

    $lead = strtolower(substr($string, 0, 5)); // <?xml

    try {

      set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);

      

      if (isset($this->options['convert_to_encoding'])) {

        // Is there another way to do this?

        

        $from_enc = isset($this->options['convert_from_encoding']) ? $this->options['convert_from_encoding'] : 'auto';

        $to_enc = $this->options['convert_to_encoding'];

        

        if (function_exists('mb_convert_encoding')) {

          $string = mb_convert_encoding($string, $to_enc, $from_enc);

        }

        

      }

      

      // This is to avoid cases where low ascii digits have slipped into HTML.

      // AFAIK, it should not adversly effect UTF-8 documents.

      if (!empty($this->options['strip_low_ascii'])) {

        $string = filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW);

      }

      

      // Allow users to override parser settings.

      if (empty($this->options['use_parser'])) {

        $useParser = '';

      }

      else {

        $useParser = strtolower($this->options['use_parser']);

      }

      

      // If HTML parser is requested, we use it.

      if ($useParser == 'html') {

        $document->loadHTML($string);

      }

      // Parse as XML if it looks like XML, or if XML parser is requested.

      elseif ($lead == '<?xml' || $useParser == 'xml') {

        if ($this->options['replace_entities']) {

          $string = QueryPathEntities::replaceAllEntities($string);

        }

        $document->loadXML($string, $flags);

      }

      // In all other cases, we try the HTML parser.

      else {

        $document->loadHTML($string);

      }

    }

    // Emulate 'finally' behavior.

    catch (Exception $e) {

      restore_error_handler();

      throw $e;

    }

    restore_error_handler();

    

    if (empty($document)) {

      throw new QueryPathParseException('Unknown parser exception.');

    }

    return $document;

  }

  

  /**

   * EXPERT: Be very, very careful using this.

   * A utility function for setting the current set of matches.

   * It makes sure the last matches buffer is set (for end() and andSelf()).

   * @since 2.0

   */

  public function setMatches($matches, $unique = TRUE) {

    // This causes a lot of overhead....

    //if ($unique) $matches = self::unique($matches);

    $this->last = $this->matches;

    

    // Just set current matches.

    if ($matches instanceof SplObjectStorage) {

      $this->matches = $matches;

    }

    // This is likely legacy code that needs conversion.

    elseif (is_array($matches)) {

      trigger_error('Legacy array detected.');

      $tmp = new SplObjectStorage();

      foreach ($matches as $m) $tmp->attach($m);

      $this->matches = $tmp;

    }

    // For non-arrays, try to create a new match set and 

    // add this object.

    else {

      $found = new SplObjectStorage();

      if (isset($matches)) $found->attach($matches);

      $this->matches = $found;

    }

    

    // EXPERIMENTAL: Support for qp()->length.

    $this->length = $this->matches->count();

  }

  

  /**

   * Set the match monitor to empty.

   *

   * This preserves history.

   *

   * @since 2.0

   */

  private function noMatches() {

    $this->setMatches(NULL);

  }

    

  /**

   * A utility function for retriving a match by index.

   *

   * The internal data structure used in QueryPath does not have

   * strong random access support, so we suppliment it with this method.

   */

  private function getNthMatch($index) {

    if ($index > $this->matches->count() || $index < 0) return;

    

    $i = 0;

    foreach ($this->matches as $m) {

      if ($i++ == $index) return $m;

    }

  }

  

  /**

   * Convenience function for getNthMatch(0).

   */

  private function getFirstMatch() {

    $this->matches->rewind();

    return $this->matches->current();

  }

  

  /**

   * Parse just a fragment of XML.

   * This will automatically prepend an <?xml ?> declaration before parsing.

   * @param string $string 

   *   Fragment to parse.

   * @return DOMDocumentFragment 

   *   The parsed document fragment.

   */

   /*

  private function parseXMLFragment($string) {

    $frag = $this->document->createDocumentFragment();

    $frag->appendXML($string);

    return $frag;

  }

  */

  

  /**

   * Parse an XML or HTML file.

   *

   * This attempts to autodetect the type of file, and then parse it.

   *

   * @param string $filename

   *  The file name to parse.

   * @param int $flags

   *  The OR-combined flags accepted by the DOM parser. See the PHP documentation

   *  for DOM or for libxml.

   * @param resource $context

   *  The stream context for the file IO. If this is set, then an alternate 

   *  parsing path is followed: The file is loaded by PHP's stream-aware IO

   *  facilities, read entirely into memory, and then handed off to 

   *  {@link parseXMLString()}. On large files, this can have a performance impact.

   * @throws QueryPathParseException 

   *  Thrown when a file cannot be loaded or parsed.

   */

  private function parseXMLFile($filename, $flags = NULL, $context = NULL) {

    

    // If a context is specified, we basically have to do the reading in 

    // two steps:

    if (!empty($context)) {

      try {

        set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);

        $contents = file_get_contents($filename, FALSE, $context);

        

      }

      // Apparently there is no 'finally' in PHP, so we have to restore the error

      // handler this way:

      catch(Exception $e) {

        restore_error_handler();

        throw $e;

      }

      restore_error_handler();

      

      if ($contents == FALSE) {

        throw new QueryPathParseException(sprintf('Contents of the file %s could not be retrieved.', $filename));

      }

      

      

      /* This is basically unneccessary overhead, as it is not more

       * accurate than the existing method.

      if (isset($md['wrapper_type']) &&  $md['wrapper_type'] == 'http') {

        for ($i = 0; $i < count($md['wrapper_data']); ++$i) {

          if (stripos($md['wrapper_data'][$i], 'content-type:') !== FALSE) {

            $ct = trim(substr($md['wrapper_data'][$i], 12));

            if (stripos('text/html') === 0) {

              $this->parseXMLString($contents, $flags, 'text/html');

            }

            else {

              // We can't account for all of the mime types that have

              // an XML payload, so we set it to XML.

              $this->parseXMLString($contents, $flags, 'text/xml');

            }

            break;

          }

        }

      }

      */

      

      return $this->parseXMLString($contents, $flags);

    }

    

    $document = new DOMDocument();

    $lastDot = strrpos($filename, '.');

    

    $htmlExtensions = array(

      '.html' => 1,

      '.htm' => 1,

    );

    

    // Allow users to override parser settings.

    if (empty($this->options['use_parser'])) {

      $useParser = '';

    }

    else {

      $useParser = strtolower($this->options['use_parser']);

    }

    

    $ext = $lastDot !== FALSE ? strtolower(substr($filename, $lastDot)) : '';

    

    try {

      set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);

      

      // If the parser is explicitly set to XML, use that parser.

      if ($useParser == 'xml') {

        $r = $document->load($filename, $flags);

      }

      // Otherwise, see if it looks like HTML.

      elseif (isset($htmlExtensions[$ext]) || $useParser == 'html') {

        // Try parsing it as HTML.

        $r = $document->loadHTMLFile($filename);

      }

      // Default to XML.

      else {

        $r = $document->load($filename, $flags);

      }

      

    }

    // Emulate 'finally' behavior.

    catch (Exception $e) {

      restore_error_handler();

      throw $e;

    }

    restore_error_handler();

    

    

    

    /*

    if ($r == FALSE) {

      $fmt = 'Failed to load file %s: %s (%s, %s)';

      $err = error_get_last();

      if ($err['type'] & self::IGNORE_ERRORS) {

        // Need to report these somehow...

        trigger_error($err['message'], E_USER_WARNING);

      }

      else {

        throw new QueryPathParseException(sprintf($fmt, $filename, $err['message'], $err['file'], $err['line']));

      }

      

      //throw new QueryPathParseException(sprintf($fmt, $filename, $err['message'], $err['file'], $err['line']));

    }

    */

    return $document;

  }

  

  /**

   * Call extension methods.

   *

   * This function is used to invoke extension methods. It searches the

   * registered extenstensions for a matching function name. If one is found,

   * it is executed with the arguments in the $arguments array.

   * 

   * @throws QueryPathException

   *  An exception is thrown if a non-existent method is called.

   */

  public function __call($name, $arguments) {

    

    if (!QueryPathExtensionRegistry::$useRegistry) {

      throw new QueryPathException("No method named $name found (Extensions disabled).");      

    }

    

    // Loading of extensions is deferred until the first time a

    // non-core method is called. This makes constructing faster, but it

    // may make the first invocation of __call() slower (if there are 

    // enough extensions.)

    //

    // The main reason for moving this out of the constructor is that most

    // new QueryPath instances do not use extensions. Charging qp() calls

    // with the additional hit is not a good idea.

    //

    // Also, this will at least limit the number of circular references.

    if (empty($this->ext)) {

      // Load the registry

      $this->ext = QueryPathExtensionRegistry::getExtensions($this);

    }

    

    // Note that an empty ext registry indicates that extensions are disabled.

    if (!empty($this->ext) && QueryPathExtensionRegistry::hasMethod($name)) {

      $owner = QueryPathExtensionRegistry::getMethodClass($name);

      $method = new ReflectionMethod($owner, $name);

      return $method->invokeArgs($this->ext[$owner], $arguments);

    }

    throw new QueryPathException("No method named $name found. Possibly missing an extension.");

  }

  

  /**

   * Dynamically generate certain properties.

   *

   * This is used primarily to increase jQuery compatibility by providing property-like

   * behaviors.

   *

   * Currently defined properties:

   *   - length: Alias of {@link size()}.

   */

   /*

  public function __get($name) {

    switch ($name) {

      case 'length':

        return $this->size();

      default:

        throw new QueryPathException('Unknown or inaccessible property "' . $name . '" (via __get())');

    }

  }

  */

  

  /**

   * Get an iterator for the matches in this object.

   * @return Iterable

   *  Returns an iterator.

   */

  public function getIterator() {

    $i = new QueryPathIterator($this->matches);

    $i->options = $this->options;

    return $i;

  }

}



/**

 * Perform various tasks on HTML/XML entities.

 *

 * @ingroup querypath_util

 */

class QueryPathEntities {

  

  /**

   * This is three regexes wrapped into 1. The | divides them.

   * 1: Match any char-based entity. This will go in $matches[1]

   * 2: Match any num-based entity. This will go in $matches[2]

   * 3: Match any hex-based entry. This will go in $matches[3]

   * 4: Match any ampersand that is not an entity. This goes in $matches[4]

   *    This last rule will only match if one of the previous two has not already

   *    matched.

   * XXX: Are octal encodings for entities acceptable?

   */

  //protected static $regex = '/&([\w]+);|&#([\d]+);|&([\w]*[\s$]+)/m';

  protected static $regex = '/&([\w]+);|&#([\d]+);|&#(x[0-9a-fA-F]+);|(&)/m';

  

  /**

   * Replace all entities.

   * This will scan a string and will attempt to replace all

   * entities with their numeric equivalent. This will not work

   * with specialized entities.

   *

   * @param string $string

   *  The string to perform replacements on.

   * @return string

   *  Returns a string that is similar to the original one, but with 

   *  all entity replacements made.

   */

  public static function replaceAllEntities($string) {

    return preg_replace_callback(self::$regex, 'QueryPathEntities::doReplacement', $string);

  }

  

  /**

   * Callback for processing replacements.

   *

   * @param array $matches

   *  The regular expression replacement array.

   */

  protected static function doReplacement($matches) {

    // See how the regex above works out.

    //print_r($matches);



    // From count, we can tell whether we got a 

    // char, num, or bare ampersand.

    $count = count($matches);

    switch ($count) {

      case 2:

        // We have a character entity

        return '&#' . self::replaceEntity($matches[1]) . ';';

      case 3:

      case 4:

        // we have a numeric entity

        return '&#' . $matches[$count-1] . ';'; 

      case 5:

        // We have an unescaped ampersand.

        return '&#38;';

    }

  }

  

  /**

   * Lookup an entity string's numeric equivalent.

   *

   * @param string $entity

   *  The entity whose numeric value is needed.

   * @return int

   *  The integer value corresponding to the entity.

   * @author Matt Butcher

   * @author Ryan Mahoney

   */

  public static function replaceEntity($entity) {

    return self::$entity_array[$entity];

  }

  

  /**

   * Conversion mapper for entities in HTML.

   * Large entity conversion table. This is 

   * significantly broader in range than 

   * get_html_translation_table(HTML_ENTITIES).

   *

   * This code comes from Rhizome ({@link http://code.google.com/p/sinciput})

   *

   * @see get_html_translation_table()

   */

  private static $entity_array = array(

	  'nbsp' => 160, 'iexcl' => 161, 'cent' => 162, 'pound' => 163, 

	  'curren' => 164, 'yen' => 165, 'brvbar' => 166, 'sect' => 167, 

	  'uml' => 168, 'copy' => 169, 'ordf' => 170, 'laquo' => 171, 

	  'not' => 172, 'shy' => 173, 'reg' => 174, 'macr' => 175, 'deg' => 176, 

	  'plusmn' => 177, 'sup2' => 178, 'sup3' => 179, 'acute' => 180, 

	  'micro' => 181, 'para' => 182, 'middot' => 183, 'cedil' => 184, 

	  'sup1' => 185, 'ordm' => 186, 'raquo' => 187, 'frac14' => 188, 

	  'frac12' => 189, 'frac34' => 190, 'iquest' => 191, 'Agrave' => 192, 

	  'Aacute' => 193, 'Acirc' => 194, 'Atilde' => 195, 'Auml' => 196, 

	  'Aring' => 197, 'AElig' => 198, 'Ccedil' => 199, 'Egrave' => 200, 

	  'Eacute' => 201, 'Ecirc' => 202, 'Euml' => 203, 'Igrave' => 204, 

	  'Iacute' => 205, 'Icirc' => 206, 'Iuml' => 207, 'ETH' => 208, 

	  'Ntilde' => 209, 'Ograve' => 210, 'Oacute' => 211, 'Ocirc' => 212, 

	  'Otilde' => 213, 'Ouml' => 214, 'times' => 215, 'Oslash' => 216, 

	  'Ugrave' => 217, 'Uacute' => 218, 'Ucirc' => 219, 'Uuml' => 220, 

	  'Yacute' => 221, 'THORN' => 222, 'szlig' => 223, 'agrave' => 224, 

	  'aacute' => 225, 'acirc' => 226, 'atilde' => 227, 'auml' => 228, 

	  'aring' => 229, 'aelig' => 230, 'ccedil' => 231, 'egrave' => 232, 

	  'eacute' => 233, 'ecirc' => 234, 'euml' => 235, 'igrave' => 236, 

	  'iacute' => 237, 'icirc' => 238, 'iuml' => 239, 'eth' => 240, 

	  'ntilde' => 241, 'ograve' => 242, 'oacute' => 243, 'ocirc' => 244, 

	  'otilde' => 245, 'ouml' => 246, 'divide' => 247, 'oslash' => 248, 

	  'ugrave' => 249, 'uacute' => 250, 'ucirc' => 251, 'uuml' => 252, 

	  'yacute' => 253, 'thorn' => 254, 'yuml' => 255, 'quot' => 34, 

	  'amp' => 38, 'lt' => 60, 'gt' => 62, 'apos' => 39, 'OElig' => 338, 

	  'oelig' => 339, 'Scaron' => 352, 'scaron' => 353, 'Yuml' => 376, 

	  'circ' => 710, 'tilde' => 732, 'ensp' => 8194, 'emsp' => 8195, 

	  'thinsp' => 8201, 'zwnj' => 8204, 'zwj' => 8205, 'lrm' => 8206, 

	  'rlm' => 8207, 'ndash' => 8211, 'mdash' => 8212, 'lsquo' => 8216, 

	  'rsquo' => 8217, 'sbquo' => 8218, 'ldquo' => 8220, 'rdquo' => 8221, 

	  'bdquo' => 8222, 'dagger' => 8224, 'Dagger' => 8225, 'permil' => 8240, 

	  'lsaquo' => 8249, 'rsaquo' => 8250, 'euro' => 8364, 'fnof' => 402, 

	  'Alpha' => 913, 'Beta' => 914, 'Gamma' => 915, 'Delta' => 916, 

	  'Epsilon' => 917, 'Zeta' => 918, 'Eta' => 919, 'Theta' => 920, 

	  'Iota' => 921, 'Kappa' => 922, 'Lambda' => 923, 'Mu' => 924, 'Nu' => 925, 

	  'Xi' => 926, 'Omicron' => 927, 'Pi' => 928, 'Rho' => 929, 'Sigma' => 931,

	  'Tau' => 932, 'Upsilon' => 933, 'Phi' => 934, 'Chi' => 935, 'Psi' => 936,

	  'Omega' => 937, 'alpha' => 945, 'beta' => 946, 'gamma' => 947, 

	  'delta' => 948, 'epsilon' => 949, 'zeta' => 950, 'eta' => 951, 

	  'theta' => 952, 'iota' => 953, 'kappa' => 954, 'lambda' => 955, 

	  'mu' => 956, 'nu' => 957, 'xi' => 958, 'omicron' => 959, 'pi' => 960, 

	  'rho' => 961, 'sigmaf' => 962, 'sigma' => 963, 'tau' => 964, 

	  'upsilon' => 965, 'phi' => 966, 'chi' => 967, 'psi' => 968, 

	  'omega' => 969, 'thetasym' => 977, 'upsih' => 978, 'piv' => 982, 

	  'bull' => 8226, 'hellip' => 8230, 'prime' => 8242, 'Prime' => 8243, 

	  'oline' => 8254, 'frasl' => 8260, 'weierp' => 8472, 'image' => 8465, 

	  'real' => 8476, 'trade' => 8482, 'alefsym' => 8501, 'larr' => 8592, 

	  'uarr' => 8593, 'rarr' => 8594, 'darr' => 8595, 'harr' => 8596, 

	  'crarr' => 8629, 'lArr' => 8656, 'uArr' => 8657, 'rArr' => 8658, 

	  'dArr' => 8659, 'hArr' => 8660, 'forall' => 8704, 'part' => 8706, 

	  'exist' => 8707, 'empty' => 8709, 'nabla' => 8711, 'isin' => 8712, 

	  'notin' => 8713, 'ni' => 8715, 'prod' => 8719, 'sum' => 8721, 

	  'minus' => 8722, 'lowast' => 8727, 'radic' => 8730, 'prop' => 8733, 

	  'infin' => 8734, 'ang' => 8736, 'and' => 8743, 'or' => 8744, 'cap' => 8745, 

	  'cup' => 8746, 'int' => 8747, 'there4' => 8756, 'sim' => 8764, 

	  'cong' => 8773, 'asymp' => 8776, 'ne' => 8800, 'equiv' => 8801, 

	  'le' => 8804, 'ge' => 8805, 'sub' => 8834, 'sup' => 8835, 'nsub' => 8836, 

	  'sube' => 8838, 'supe' => 8839, 'oplus' => 8853, 'otimes' => 8855, 

	  'perp' => 8869, 'sdot' => 8901, 'lceil' => 8968, 'rceil' => 8969, 

	  'lfloor' => 8970, 'rfloor' => 8971, 'lang' => 9001, 'rang' => 9002, 

	  'loz' => 9674, 'spades' => 9824, 'clubs' => 9827, 'hearts' => 9829, 

	  'diams' => 9830

	);

}



/**

 * An iterator for QueryPath.

 *

 * This provides iterator support for QueryPath. You do not need to construct

 * a QueryPathIterator. QueryPath does this when its {@link QueryPath::getIterator()}

 * method is called.

 *

 * @ingroup querypath_util

 */

class QueryPathIterator extends IteratorIterator {

  public $options = array();

  private $qp = NULL;

  

  public function current() {

    if (!isset($this->qp)) {

      $this->qp = qp(parent::current(), NULL, $this->options);

    }

    else {

      $splos = new SplObjectStorage();

      $splos->attach(parent::current());

      $this->qp->setMatches($splos);

    }

    return $this->qp;

  }

}



/**

 * Manage default options.

 *

 * This class stores the default options for QueryPath. When a new 

 * QueryPath object is constructed, options specified here will be 

 * used.

 *

 * <b>Details</b>

 * This class defines no options of its own. Instead, it provides a 

 * central tool for developers to override options set by QueryPath.

 * When a QueryPath object is created, it will evaluate options in the 

 * following order:

 *

 * - Options passed into {@link qp()} have highest priority.

 * - Options in {@link QueryPathOptions} (this class) have the next highest priority.

 * - If the option is not specified elsewhere, QueryPath will use its own defaults.

 *

 * @see qp()

 * @see QueryPathOptions::set()

 * @ingroup querypath_util

 */

class QueryPathOptions {

  

  /**

   * This is the static options array.

   *

   * Use the {@link set()}, {@link get()}, and {@link merge()} to

   * modify this array.

   */

  static $options = array();

  

  /**

   * Set the default options.

   *

   * The passed-in array will be used as the default options list.

   *

   * @param array $array

   *  An associative array of options.

   */

  static function set($array) {

    self::$options = $array;

  }

  

  /**

   * Get the default options.

   *

   * Get all options currently set as default.

   *

   * @return array

   *  An array of options. Note that only explicitly set options are 

   *  returned. {@link QueryPath} defines default options which are not

   *  stored in this object.

   */

  static function get() {

    return self::$options;

  }

  

  /**

   * Merge the provided array with existing options.

   *

   * On duplicate keys, the value in $array will overwrite the 

   * value stored in the options.

   *

   * @param array $array

   *  Associative array of options to merge into the existing options.

   */

  static function merge($array) {

    self::$options = $array + self::$options;

  }

  

  /**

   * Returns true of the specified key is already overridden in this object.

   *

   * @param string $key

   *  The key to search for.

   */

  static function has($key) {

    return array_key_exists($key, self::$options);

  }

  

}



/**

 * Exception indicating that a problem has occured inside of a QueryPath object.

 *

 * @ingroup querypath_core

 */

class QueryPathException extends Exception {}



/**

 * Exception indicating that a parser has failed to parse a file.

 *

 * This will report parser warnings as well as parser errors. It should only be 

 * thrown, though, under error conditions.

 *

 * @ingroup querypath_core

 */

class QueryPathParseException extends QueryPathException {

  const ERR_MSG_FORMAT = 'Parse error in %s on line %d column %d: %s (%d)';

  const WARN_MSG_FORMAT = 'Parser warning in %s on line %d column %d: %s (%d)';

  // trigger_error

  public function __construct($msg = '', $code = 0, $file = NULL, $line = NULL) {



    $msgs = array();

    foreach(libxml_get_errors() as $err) {

      $format = $err->level == LIBXML_ERR_WARNING ? self::WARN_MSG_FORMAT : self::ERR_MSG_FORMAT;

      $msgs[] = sprintf($format, $err->file, $err->line, $err->column, $err->message, $err->code);

    }

    $msg .= implode("\n", $msgs);

    

    if (isset($file)) {

      $msg .= ' (' . $file;

      if (isset($line)) $msg .= ': ' . $line;

      $msg .= ')';

    }

    

    parent::__construct($msg, $code);

  }

  

  public static function initializeFromError($code, $str, $file, $line, $cxt) {

    //printf("\n\nCODE: %s %s\n\n", $code, $str);

    $class = __CLASS__;

    throw new $class($str, $code, $file, $line);

  }

}



/**

 * Indicates that an input/output exception has occurred.

 *

 * @ingroup querypath_core

 */

class QueryPathIOException extends QueryPathParseException {

  public static function initializeFromError($code, $str, $file, $line, $cxt) {

    $class = __CLASS__;

    throw new $class($str, $code, $file, $line);

  }

  

}