<?php
/** @file
 * Tests for the DOMQuery class.
 *
 *
 * @author M Butcher <matt@aleph-null.tv>
 * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
 */

namespace QueryPath\Tests;

/** @addtogroup querypath_tests Tests
 * Unit tests and regression tests for DOMQuery.
 */

use QueryPath\DOMQuery;
use \Masterminds\HTML5;

/** */
//require_once 'PHPUnit/Autoload.php';
require __DIR__ . '/../../../vendor/autoload.php';
require_once __DIR__ . '/TestCase.php';

define('DATA_FILE', __DIR__ . '/../../data.xml');
define('DATA_HTML_FILE', __DIR__ . '/../../data.html');
define('NO_WRITE_FILE', __DIR__ . '/../../no-write.xml');
define('MEDIUM_FILE', __DIR__ . '/../../amplify.xml');
define('HTML_IN_XML_FILE', __DIR__ . '/../../html.xml');

/**
 * Tests for DOM Query. Primarily, this is focused on the DomQueryImpl
 * class which is exposed through the DomQuery interface and the dq()
 * factory function.
 * @ingroup querypath_tests
 */
class DOMQueryTest extends TestCase {

  /**
   * @group basic
   */
  public function testDOMQueryConstructors() {

    // From XML file
    $file = DATA_FILE;
    $qp = qp($file);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // From XML file with context
    $cxt = stream_context_create();
    $qp = qp($file, NULL, array('context' => $cxt));
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // From XML string
    $str = '<?xml version="1.0" ?><root><inner/></root>';
    $qp = qp($str);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // From SimpleXML
    $str = '<?xml version="1.0" ?><root><inner/></root>';
    $qp = qp(simplexml_load_string($str));
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // Test from DOMDocument
    $qp = qp(\DOMDocument::loadXML($str));
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // Now with a selector:
    $qp = qp($file, '#head');
    $this->assertEquals(1, count($qp->get()));
    $this->assertEquals($qp->get(0)->tagName, 'head');

    // Test HTML:
    $htmlFile = DATA_HTML_FILE;
    $qp = qp($htmlFile);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // Test with another DOMQuery.
    $qp = qp($qp);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // Test from array of DOMNodes
    $array = $qp->get();
    $qp = qp($array);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

  }
  /**
   * Test alternate constructors.
   * @group basic
   */
  public function testDOMQueryHtmlConstructors() {
    $qp = htmlqp(\QueryPath::HTML_STUB);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // Bad BR tag.
    $borken = '<html><head></head><body><br></body></html>';
    $qp = htmlqp($borken);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // XHTML Faker
    $borken = '<?xml version="1.0"?><html><head></head><body><br></body></html>';
    $qp = htmlqp($borken);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

    // HTML in a file that looks like XML.
    $qp = htmlqp(HTML_IN_XML_FILE);
    $this->assertEquals(1, count($qp->get()));
    $this->assertTrue($qp->get(0) instanceof \DOMNode);

	// HTML5
	$html5 = new \Masterminds\HTML5();
	$dom = $html5->loadHTML(\QueryPath::HTML_STUB);
	qp($dom,'html');

    // Stripping #13 (CR) from HTML.
    $borken = '<html><head></head><body><p>' . chr(13) . '</p><div id="after"/></body></html>';
    $this->assertFalse(strpos(htmlqp($borken)->html(), '&#13;'), 'Test that CRs are not encoded.');

    // Regression for #58: Make sure we aren't getting &#10; encoded.
    $borken = '<html><head><style>
        .BlueText {
          color:red;
        }</style><body></body></html>';

    $this->assertFalse(strpos(htmlqp($borken)->html(), '&#10;'), 'Test that LF is not encoded.');

    // Low ASCII in a file
    $borken = '<html><head></head><body><p>' . chr(27) . '</p><div id="after"/></body></html>';
    $this->assertEquals(1, htmlqp($borken, '#after')->size());
  }

  public function testForTests() {
    $qp_methods = get_class_methods('\QueryPath\DOMQuery');
    $test_methods = get_class_methods('\QueryPath\Tests\DOMQueryTest');

    $ignore = array("__construct", "__call", "__clone", "get", "getOptions", "setMatches", "toArray", "getIterator");

    $test_methods = array_map('strtolower', $test_methods);

    foreach($qp_methods as $q) {
      if(in_array($q, $ignore)) continue;
      $this->assertTrue(in_array(strtolower("test".$q), $test_methods), $q . ' does not have a test method.');
    }
  }

  public function testOptionXMLEncoding() {
    $xml = qp(NULL, NULL, array('encoding' => 'iso-8859-1'))->append('<test/>')->xml();
    $iso_found = preg_match('/iso-8859-1/', $xml) == 1;

    $this->assertTrue($iso_found, 'Encoding should be iso-8859-1 in ' . $xml . 'Found ' . $iso_found);

    $iso_found = preg_match('/utf-8/', $xml) == 1;
    $this->assertFalse($iso_found, 'Encoding should not be utf-8 in ' . $xml);

    $xml = qp('<?xml version="1.0" encoding="utf-8"?><test/>', NULL, array('encoding' => 'iso-8859-1'))->xml();
    $iso_found = preg_match('/utf-8/', $xml) == 1;
    $this->assertTrue($iso_found, 'Encoding should be utf-8 in ' . $xml);

    $iso_found = preg_match('/iso-8859-1/', $xml) == 1;
    $this->assertFalse($iso_found, 'Encoding should not be utf-8 in ' . $xml);

  }

  public function testQPAbstractFactory() {
    $options = array('QueryPath_class' => '\QueryPath\Tests\QueryPathExtended');
    $qp = qp(NULL, NULL, $options);
    $this->assertTrue($qp instanceof QueryPathExtended, 'Is instance of extending class.');
    $this->assertTrue($qp->foonator(), 'Has special foonator() function.');
  }

  public function testQPAbstractFactoryIterating() {
    $xml = '<?xml version="1.0"?><l><i/><i/><i/><i/><i/></l>';
    $options = array('QueryPath_class' => '\QueryPath\Tests\QueryPathExtended');
    foreach(qp($xml, 'i', $options) as $item) {
      $this->assertTrue($item instanceof QueryPathExtended, 'Is instance of extending class.');
    }

  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testFailedCall() {
    // This should hit __call() and then fail.
    qp()->fooMethod();
  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testFailedObjectConstruction() {
    qp(new \stdClass());
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedHTTPLoad() {
    try {
      qp('http://localhost:8877/no_such_file.xml');
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedHTTPLoadWithContext() {
    try {
      qp('http://localhost:8877/no_such_file.xml', NULL, array('foo' => 'bar'));
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedParseHTMLElement() {
    try {
      qp('<foo>&foonator;</foo>', NULL);
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedParseXMLElement() {
    try {
      qp('<?xml version="1.0"?><foo><bar>foonator;</foo>', NULL);
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }
  public function testIgnoreParserWarnings() {
    $qp = @qp('<html><body><b><i>BAD!</b></i></body>', NULL, array('ignore_parser_warnings' => TRUE));
    $this->assertTrue(strpos($qp->html(), '<i>BAD!</i>') !== FALSE);

    \QueryPath\Options::merge(array('ignore_parser_warnings' => TRUE));
    $qp = @qp('<html><body><b><i>BAD!</b></i></body>');
    $this->assertTrue(strpos($qp->html(), '<i>BAD!</i>') !== FALSE);

    $qp = @qp('<html><body><blarg>BAD!</blarg></body>');
    $this->assertTrue(strpos($qp->html(), '<blarg>BAD!</blarg>') !== FALSE, $qp->html());
    \QueryPath\Options::set(array()); // Reset to empty options.
  }
  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedParseNonMarkup() {
    try {
      qp('<23dfadf', NULL);
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testFailedParseEntity() {
    try {
      qp('<?xml version="1.0"?><foo>&foonator;</foo>', NULL);
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  public function testReplaceEntitiesOption() {
    $path = '<?xml version="1.0"?><root/>';
    $xml = qp($path, NULL, array('replace_entities' => TRUE))->xml('<foo>&</foo>')->xml();
    $this->assertTrue(strpos($xml, '<foo>&amp;</foo>') !== FALSE);

    $xml = qp($path, NULL, array('replace_entities' => TRUE))->html('<foo>&</foo>')->xml();
    $this->assertTrue(strpos($xml, '<foo>&amp;</foo>') !== FALSE);

    $xml = qp($path, NULL, array('replace_entities' => TRUE))->xhtml('<foo>&</foo>')->xml();
    $this->assertTrue(strpos($xml, '<foo>&amp;</foo>') !== FALSE);

    \QueryPath\Options::set(array('replace_entities' => TRUE));
    $this->assertTrue(strpos($xml, '<foo>&amp;</foo>') !== FALSE);
    \QueryPath\Options::set(array());
  }

  /**
   * @group basic
   */
  public function testFind() {
    $file = DATA_FILE;
    $qp = qp($file)->find('#head');
    $this->assertEquals(1, count($qp->get()));
    $this->assertEquals($qp->get(0)->tagName, 'head');

    $this->assertEquals('inner', qp($file)->find('.innerClass')->tag());

    $string = '<?xml version="1.0"?><root><a/>Test</root>';
    $qp = qp($string)->find('root');
    $this->assertEquals(1, count($qp->get()), 'Check tag.');
    $this->assertEquals($qp->get(0)->tagName, 'root');

    $string = '<?xml version="1.0"?><root class="findme">Test</root>';
    $qp = qp($string)->find('.findme');
    $this->assertEquals(1, count($qp->get()), 'Check class.');
    $this->assertEquals($qp->get(0)->tagName, 'root');
  }
  public function testFindInPlace() {
    $file = DATA_FILE;
    $qp = qp($file)->find('#head');
    $this->assertEquals(1, count($qp->get()));
    $this->assertEquals($qp->get(0)->tagName, 'head');

    $this->assertEquals('inner', qp($file)->find('.innerClass')->tag());

    $string = '<?xml version="1.0"?><root><a/>Test</root>';
    $qp = qp($string)->find('root');
    $this->assertEquals(1, count($qp->get()), 'Check tag.');
    $this->assertEquals($qp->get(0)->tagName, 'root');

    $string = '<?xml version="1.0"?><root class="findme">Test</root>';
    $qp = qp($string)->find('.findme');
    $this->assertEquals(1, count($qp->get()), 'Check class.');
    $this->assertEquals($qp->get(0)->tagName, 'root');
  }

  /**
   * @group basic
   */
  public function testTop() {
    $file = DATA_FILE;
    $qp = qp($file)->find('li');
    $this->assertGreaterThan(2, $qp->size());
    $this->assertEquals(1, $qp->top()->size());

    // Added for QP 2.0
    $xml = '<?xml version="1.0"?><root><u><l/><l/><l/></u><u/></root>';
    $qp = qp($xml, 'l');
    $this->assertEquals(3, $qp->size());
    $this->assertEquals(2, $qp->top('u')->size());
  }

  /**
   * @group basic
   */
  public function testAttr() {
    $file = DATA_FILE;

    $qp = qp($file)->find('#head');
    $this->assertEquals(1, $qp->size());
    $this->assertEquals($qp->get(0)->getAttribute('id'), $qp->attr('id'));

    $qp->attr('foo', 'bar');
    $this->assertEquals('bar', $qp->attr('foo'));

    $qp->attr(array('foo2' => 'bar', 'foo3' => 'baz'));
    $this->assertEquals('baz', $qp->attr('foo3'));

    // Check magic nodeType attribute:
    $this->assertEquals(XML_ELEMENT_NODE, qp($file)->find('#head')->attr('nodeType'));

    // Since QP 2.1
    $xml = '<?xml version="1.0"?><root><one a1="1" a2="2" a3="3"/></root>';
    $qp = qp($xml, 'one');
    $attrs = $qp->attr();
    $this->assertEquals(3, count($attrs), 'Three attributes');
    $this->assertEquals('1', $attrs['a1'], 'Attribute a1 has value 1.');
  }

  /**
   * @group basic
   */
  public function testHasAttr() {
    $xml = '<?xml version="1.0"?><root><div foo="bar"/></root>';

    $this->assertFalse(qp($xml, 'root')->hasAttr('foo'));
    $this->assertTrue(qp($xml, 'div')->hasAttr('foo'));

    $xml = '<?xml version="1.0"?><root><div foo="bar"/><div foo="baz"></div></root>';
    $this->assertTrue(qp($xml, 'div')->hasAttr('foo'));

    $xml = '<?xml version="1.0"?><root><div bar="bar"/><div foo="baz"></div></root>';
    $this->assertFalse(qp($xml, 'div')->hasAttr('foo'));

    $xml = '<?xml version="1.0"?><root><div bar="bar"/><div bAZ="baz"></div></root>';
    $this->assertFalse(qp($xml, 'div')->hasAttr('foo'));
  }

  public function testVal() {
    $qp = qp('<?xml version="1.0"?><foo><bar value="test"/></foo>', 'bar');
    $this->assertEquals('test', $qp->val());

    $qp = qp('<?xml version="1.0"?><foo><bar/></foo>', 'bar')->val('test');
    $this->assertEquals('test', $qp->attr('value'));
  }

  public function testCss() {
    $file = DATA_FILE;
    $this->assertEquals('foo: bar;', qp($file, 'unary')->css('foo', 'bar')->attr('style'));
    $this->assertEquals('foo: bar;', qp($file, 'unary')->css('foo', 'bar')->css());
    $this->assertEquals('foo: bar;', qp($file, 'unary')->css(array('foo' =>'bar'))->css());

    // Issue #28: Setting styles in sequence should not result in the second
    // style overwriting the first style:
    $qp = qp($file, 'unary')->css('color', 'blue')->css('background-color', 'white');

    $expects = 'color: blue;background-color: white;';
    $actual = $qp->css();
    $this->assertEquals(bin2hex($expects), bin2hex($actual), 'Two css calls should result in two attrs.');

    // Make sure array merges work.
    $qp = qp($file, 'unary')->css('a','a')->css(array('b'=>'b', 'c'=>'c'));
    $this->assertEquals('a: a;b: b;c: c;', $qp->css());

    // Make sure that second assignment overrides first assignment.
    $qp = qp($file, 'unary')->css('a','a')->css(array('b'=>'b', 'a'=>'c'));
    $this->assertEquals('a: c;b: b;', $qp->css());
  }

  public function testRemoveAttr() {
    $file = DATA_FILE;

    $qp = qp($file, 'inner')->removeAttr('class');
    $this->assertEquals(2, $qp->size());
    $this->assertFalse($qp->get(0)->hasAttribute('class'));

  }

  public function testEq() {
    $file = DATA_FILE;
    $qp = qp($file)->find('li')->eq(0);
    $this->assertEquals(1, $qp->size());
    $this->assertEquals($qp->attr('id'), 'one');
    $this->assertEquals(1, qp($file, 'inner')->eq(0)->size());
    $this->assertEquals(1, qp($file, 'li')->eq(0)->size());
    $this->assertEquals("Hello", qp($file, 'li')->eq(0)->text());
    $this->assertEquals("Last", qp($file, 'li')->eq(3)->text());
  }

  public function testIs() {
    $file = DATA_FILE;
    $this->assertTrue(qp($file)->find('#one')->is('#one'));
    $this->assertTrue(qp($file)->find('li')->is('#one'));

    $qp = qp($file)->find('#one');
    $ele = $qp->get(0);
    $this->assertTrue($qp->top('#one')->is($ele));

    $qp = qp($file)->find('#one');
    $ele = $qp->get(0);
    $ele2 = $qp->top('#two')->get(0);

    $list = new \SplDoublyLinkedList();
    $list->push($ele);
    $list->push($ele2);
    $this->assertEquals(2, count($list));
    //$this->assertEquals(2, )
    $this->assertTrue($qp->top('#one,#two')->is($list));

  }

  public function testIndex() {
    $xml = '<?xml version="1.0"?><foo><bar id="one"/><baz id="two"/></foo>';
    $qp = qp($xml, 'bar');
    $e1 = $qp->get(0);
    $this->assertEquals(0, $qp->find('bar')->index($e1));
    $this->assertFalse($qp->top()->find('#two')->index($e1));
  }

  public function testFilter() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file)->filter('li')->size());
    $this->assertEquals(2, qp($file, 'inner')->filter('li')->size());
    $this->assertEquals('inner-two', qp($file, 'inner')->filter('li')->eq(1)->attr('id'));
  }

  public function testFilterPreg() {
    $xml = '<?xml version="1.0"?><root><div id="one">Foo</div><div>Moo</div></root>';
    $qp = qp($xml, 'div')->filterPreg('/Foo/');

    $this->assertEquals(1, $qp->Size());

    // Check to make sure textContent is collected correctly.
    $xml = '<?xml version="1.0"?><root><div>Hello <i>World</i></div></root>';
    $qp = qp($xml, 'div')->filterPreg('/Hello\sWorld/');

    $this->assertEquals(1, $qp->Size());
  }

  public function testFilterLambda() {
    $file = DATA_FILE;
    // Get all evens:
    $l = 'return (($index + 1) % 2 == 0);';
    $this->assertEquals(2, qp($file, 'li')->filterLambda($l)->size());
  }

  public function filterCallbackFunction($index, $item) {
    return (($index + 1) % 2 == 0);
  }


  public function testFilterCallback() {
    $file = DATA_FILE;
    $cb = array($this, 'filterCallbackFunction');
    $this->assertEquals(2, qp($file, 'li')->filterCallback($cb)->size());
  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testFailedFilterCallback() {
    $file = DATA_FILE;
    $cb = array($this, 'noSuchFunction');
    qp($file, 'li')->filterCallback($cb)->size();
  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testFailedMapCallback() {
    $file = DATA_FILE;
    $cb = array($this, 'noSuchFunction');
    qp($file, 'li')->map($cb)->size();
  }


  public function testNot() {
    $file = DATA_FILE;

    // Test with selector
    $qp = qp($file, 'li:odd')->not('#one');
    $this->assertEquals(2, $qp->size());

    // Test with DOM Element
    $qp = qp($file, 'li');
    $el = $qp->branch()->filter('#one')->get(0);
    $this->assertTrue($el instanceof \DOMElement, "Is DOM element.");
    $this->assertEquals(4, $qp->not($el)->size());

    // Test with array of DOM Elements
    $qp = qp($file, 'li');
    $arr = $qp->get();
    $this->assertEquals(count($arr), $qp->size());
    array_shift($arr);
    $this->assertEquals(1, $qp->not($arr)->size());
  }

  public function testSlice() {
    $file = DATA_FILE;
    // There are five <li> elements
    $qp = qp($file, 'li')->slice(1);
    $this->assertEquals(4, $qp->size());

    // The first item in the matches should be #two.
    $this->assertEquals('two', $qp->attr('id'));

    // THe last item should be #five
    $this->assertEquals('five', $qp->eq(3)->attr('id'));

    // This should not throw an error.
    $this->assertEquals(4, qp($file, 'li')->slice(1, 9)->size());

    $this->assertEquals(0, qp($file, 'li')->slice(9)->size());

    // The first item should be #two, the last #three
    $qp = qp($file, 'li')->slice(1, 2);
    $this->assertEquals(2, $qp->size());
    $this->assertEquals('two', $qp->attr('id'));
    $this->assertEquals('three', $qp->eq(1)->attr('id'));
  }

  public function mapCallbackFunction($index, $item) {
    if ($index == 1) {
      return FALSE;
    }
    if ($index == 2) {
      return array(1, 2, 3);
    }
    return $index;
  }

  public function testMap() {
    $file = DATA_FILE;
    $fn = 'mapCallbackFunction';
    $this->assertEquals(7, qp($file, 'li')->map(array($this, $fn))->size());
  }

  public function eachCallbackFunction($index, $item) {
    if ($index < 2) {
      qp($item)->attr('class', 'test');
    }
    else {
      return FALSE;
    }
  }

  public function testEach() {
    $file = DATA_FILE;
    $fn = 'eachCallbackFunction';
    $res = qp($file, 'li')->each(array($this, $fn));
    $this->assertEquals(5, $res->size());
    $this->assertFalse($res->get(4)->getAttribute('class') === NULL);
    $this->assertEquals('test', $res->eq(1)->attr('class'));

    // Test when each runs out of things to test before returning.
    $res = qp($file, '#one')->each(array($this, $fn));
    $this->assertEquals(1, $res->size());
  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testEachOnInvalidCallback() {
    $file = DATA_FILE;
    $fn = 'eachCallbackFunctionFake';
    $res = qp($file, 'li')->each(array($this, $fn));
  }

  public function testEachLambda() {
    $file = DATA_FILE;
    $fn = 'qp($item)->attr("class", "foo");';
    $res = qp($file, 'li')->eachLambda($fn);
    $this->assertEquals('foo', $res->eq(1)->attr('class'));
  }

  public function testDeepest() {
    $str = '<?xml version="1.0" ?>
    <root>
      <one/>
      <one><two/></one>
      <one><two><three/></two></one>
      <one><two><three><four/></three></two></one>
      <one/>
      <one><two><three><banana/></three></two></one>
    </root>';
    $deepest = qp($str)->deepest();
    $this->assertEquals(2, $deepest->size());
    $this->assertEquals('four', $deepest->get(0)->tagName);
    $this->assertEquals('banana', $deepest->get(1)->tagName);

    $deepest = qp($str, 'one')->deepest();
    $this->assertEquals(2, $deepest->size());
    $this->assertEquals('four', $deepest->get(0)->tagName);
    $this->assertEquals('banana', $deepest->get(1)->tagName);

    $str = '<?xml version="1.0" ?>
    <root>
      CDATA
    </root>';
    $this->assertEquals(1, qp($str)->deepest()->size());
  }

  public function testTag() {
    $file = DATA_FILE;
    $this->assertEquals('li', qp($file, 'li')->tag());
  }

  public function testAppend() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file,'unary')->append('<test/>')->find(':root > unary > test')->size());
    $qp = qp($file,'#inner-one')->append('<li id="appended"/>');

    $appended = $qp->find('#appended');
    $this->assertEquals(1, $appended->size());
    $this->assertNull($appended->get(0)->nextSibling);

    $this->assertEquals(2, qp($file, 'inner')->append('<test/>')->top()->find('test')->size());
    $this->assertEquals(2, qp($file, 'inner')->append(qp('<?xml version="1.0"?><test/>'))->top()->find('test')->size());
    $this->assertEquals(4, qp($file, 'inner')->append(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size());

    // Issue #6: This seems to break on Debian Etch systems... no idea why.
    $this->assertEquals('test', qp()->append('<test/>')->top()->tag());

    // Issue #7: Failure issues warnings
    // This seems to be working as expected -- libxml emits
    // parse errors.
    //$this->assertEquals(NULL, qp()->append('<test'));

    // Test loading SimpleXML.
    $simp = simplexml_load_file($file);
    $qp = qp('<?xml version="1.0"?><foo/>')->append($simp);
    $this->assertEquals(1, $qp->find('root')->size());

    // Test with replace entities turned on:
    $qp = qp($file, 'root', array('replace_entities' => TRUE))->append('<p>&raquo;</p>');
    // Note that we are using a UTF-8 » character, not an ASCII 187. This seems to cause
    // problems on some Windows IDEs. So here we do it the ugly way.
    $utf8raquo = '<p>' . mb_convert_encoding(chr(187), 'utf-8', 'iso-8859-1') . '</p>';
    //$this->assertEquals('<p>»</p>', $qp->find('p')->html(), 'Entities are decoded to UTF-8 correctly.');
    $this->assertEquals($utf8raquo, $qp->find('p')->html(), 'Entities are decoded to UTF-8 correctly.');

    // Test with empty, mainly to make sure it doesn't explode.
    $this->assertTrue(qp($file)->append('') instanceof DOMQuery);
  }

  /**
   * @expectedException \QueryPath\ParseException
   */
  public function testAppendBadMarkup() {
    $file = DATA_FILE;
    try{
      qp($file, 'root')->append('<foo><bar></foo>');
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }
  }

  /**
    * @expectedException \QueryPath\Exception
    */
   public function testAppendBadObject() {
     $file = DATA_FILE;
     try{
       qp($file, 'root')->append(new \stdClass);
     }
     catch (Exception $e) {
       //print $e->getMessage();
       throw $e;
     }
   }

  public function testAppendTo() {
    $file = DATA_FILE;
    $dest = qp('<?xml version="1.0"?><root><dest/></root>', 'dest');
    $qp = qp($file,'li')->appendTo($dest);
    $this->assertEquals(5, $dest->find(':root li')->size());
  }

  public function testPrepend() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file,'unary')->prepend('<test/>')->find(':root > unary > test')->size());
    $qp = qp($file,'#inner-one')->prepend('<li id="appended"/>')->find('#appended');
    $this->assertEquals(1, $qp->size());
    $this->assertNull($qp->get(0)->previousSibling);

    // Test repeated insert
    $this->assertEquals(2, qp($file,'inner')->prepend('<test/>')->top()->find('test')->size());
    $this->assertEquals(4, qp($file,'inner')->prepend(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size());
  }

  public function testPrependTo() {
    $file = DATA_FILE;
    $dest = qp('<?xml version="1.0"?><root><dest/></root>', 'dest');
    $qp = qp($file,'li')->prependTo($dest);
    $this->assertEquals(5, $dest->find(':root li')->size());
  }

  public function testBefore() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file,'unary')->before('<test/>')->find(':root > test ~ unary')->size());
    $this->assertEquals(1, qp($file,'unary')->before('<test/>')->top('head ~ test')->size());
    $this->assertEquals('unary', qp($file,'unary')->before('<test/>')->top(':root > test')->get(0)->nextSibling->tagName);

    // Test repeated insert
    $this->assertEquals(2, qp($file,'inner')->before('<test/>')->top()->find('test')->size());
    $this->assertEquals(4, qp($file,'inner')->before(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size());
  }

  public function testAfter() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file,'unary')->after('<test/>')->top(':root > unary ~ test')->size());
    $this->assertEquals('unary', qp($file,'unary')->after('<test/>')->top(':root > test')->get(0)->previousSibling->tagName);

    $this->assertEquals(2, qp($file,'inner')->after('<test/>')->top()->find('test')->size());
    $this->assertEquals(4, qp($file,'inner')->after(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size());
  }

  public function testInsertBefore() {
    $file = DATA_FILE;
    $dest = qp('<?xml version="1.0"?><root><dest/></root>', 'dest');
    $qp = qp($file,'li')->insertBefore($dest);
    $this->assertEquals(5, $dest->top(':root > li')->size());
    $this->assertEquals('li', $dest->end()->find('dest')->get(0)->previousSibling->tagName);
  }
  public function testInsertAfter() {
    $file = DATA_FILE;
    $dest = qp('<?xml version="1.0"?><root><dest/></root>', 'dest');
    $qp = qp($file,'li')->insertAfter($dest);
    //print $dest->get(0)->ownerDocument->saveXML();
    $this->assertEquals(5, $dest->top(':root > li')->size());
  }
  public function testReplaceWith() {
    $file = DATA_FILE;
    $qp = qp($file,'unary')->replaceWith('<test><foo/></test>')->top('test');
    //print $qp->get(0)->ownerDocument->saveXML();
    $this->assertEquals(1, $qp->size());

    $qp = qp($file,'unary')->replaceWith(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'));
    //print $qp->get(0)->ownerDocument->saveXML();
    $this->assertEquals(2, $qp->top()->find('test')->size());
  }

  public function testReplaceAll() {
    $qp1 = qp('<?xml version="1.0"?><root><l/><l/></root>');
    $doc = qp('<?xml version="1.0"?><bob><m/><m/></bob>')->get(0)->ownerDocument;

    $qp2 = $qp1->find('l')->replaceAll('m', $doc);

    $this->assertEquals(2, $qp2->top()->find('l')->size());
  }

  public function testUnwrap() {

    // Unwrap center, and make sure junk goes away.
    $xml = '<?xml version="1.0"?><root><wrapper><center/><junk/></wrapper></root>';
    $qp = qp($xml, 'center')->unwrap();
    $this->assertEquals('root', $qp->top('center')->parent()->tag());
    $this->assertEquals(0, $qp->top('junk')->size());

    // Make sure it works on two nodes in the same parent.
    $xml = '<?xml version="1.0"?><root><wrapper><center id="1"/><center id="2"/></wrapper></root>';
    $qp = qp($xml, 'center')->unwrap();

    // Make sure they were unwrapped
    $this->assertEquals('root', $qp->top('center')->parent()->tag());

    // Make sure both get copied.
    $this->assertEquals(2, $qp->top('center')->size());

    // Make sure they are in order.
    $this->assertEquals('2', $qp->top('center:last')->attr('id'));

    // Test on root element.
    $xml = '<?xml version="1.0"?><root><center/></root>';
    $qp = qp($xml, 'center')->unwrap();
    $this->assertEquals('center', $qp->top()->tag());

  }

  /**
   * @expectedException \QueryPath\Exception
   */
  public function testFailedUnwrap() {
    // Cannot unwrap the root element.
    $xml = '<?xml version="1.0"?><root></root>';
    $qp = qp($xml, 'root')->unwrap();
    $this->assertEquals('center', $qp->top()->tag());
  }

  public function testWrap() {
    $file = DATA_FILE;
    $xml = qp($file,'unary')->wrap('');
    $this->assertTrue($xml instanceof DOMQuery);

    $xml = qp($file,'unary')->wrap('<test id="testWrap"></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);

    $xml = qp($file,'unary')->wrap(qp('<?xml version="1.0"?><root><test id="testWrap"></test><test id="ignored"></test></root>', 'test'))->get(0)->ownerDocument->saveXML();
    $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);

    $xml = qp($file,'li')->wrap('<test class="testWrap"></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(5, qp($xml, '.testWrap')->size());

    $xml = qp($file,'li')->wrap('<test class="testWrap"><inside><center/></inside></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(5, qp($xml, '.testWrap > inside > center > li')->size());
  }

  public function testWrapAll() {
    $file = DATA_FILE;

    $xml = qp($file,'unary')->wrapAll('');
    $this->assertTrue($xml instanceof DOMQuery);

    $xml = qp($file,'unary')->wrapAll('<test id="testWrap"></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);

    $xml = qp($file,'unary')->wrapAll(qp('<?xml version="1.0"?><root><test id="testWrap"></test><test id="ignored"></test></root>', 'test'))->get(0)->ownerDocument->saveXML();
    $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);

    $xml = qp($file,'li')->wrapAll('<test class="testWrap"><inside><center/></inside></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(5, qp($xml, '.testWrap > inside > center > li')->size());

  }

  public function testWrapInner() {
    $file = DATA_FILE;

    $this->assertTrue(qp($file,'#inner-one')->wrapInner('') instanceof DOMQuery);

    $xml = qp($file,'#inner-one')->wrapInner('<test class="testWrap"></test>')->get(0)->ownerDocument->saveXML();
    // FIXME: 9 includes text nodes. Should fix this.
    $this->assertEquals(9, qp($xml, '.testWrap')->get(0)->childNodes->length);

    $xml = qp($file,'inner')->wrapInner('<test class="testWrap"></test>')->get(0)->ownerDocument->saveXML();
    $this->assertEquals(9, qp($xml, '.testWrap')->get(0)->childNodes->length);
    $this->assertEquals(3, qp($xml, '.testWrap')->get(1)->childNodes->length);

    $qp = qp($file,'inner')->wrapInner(qp('<?xml version="1.0"?><root><test class="testWrap"/><test class="ignored"/></root>', 'test'));
    $this->assertEquals(2, $qp->find('inner > .testWrap')->size());
    $this->assertEquals(0, $qp->find('.ignore')->size());
  }

  public function testRemove() {
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $start = $qp->size();
    $finish = $qp->remove()->size();
    $this->assertEquals($start, $finish);
    $this->assertEquals(0, $qp->find(':root li')->size());

    // Test for Issue #55
    $data = '<?xml version="1.0"?><root><a>test</a><b> FAIL</b></root>';
    $qp = qp($data);
    $rem = $qp->remove('b');


    $this->assertEquals(' FAIL', $rem->text());
    $this->assertEquals('test', $qp->text());

    // Test for Issue #63
    $qp = qp($data);
    $rem = $qp->remove('noSuchElement');
    $this->assertEquals(0, count($rem));
  }

  public function testHasClass() {
    $file = DATA_FILE;
    $this->assertTrue(qp($file, '#inner-one')->hasClass('innerClass'));

    $file = DATA_FILE;
    $this->assertFalse(qp($file, '#inner-one')->hasClass('noSuchClass'));
  }

  public function testAddClass() {
    $file = DATA_FILE;
    $this->assertTrue(qp($file, '#inner-one')->addClass('testClass')->hasClass('testClass'));
  }
  public function testRemoveClass() {
    $file = DATA_FILE;
    // The add class tests to make sure that this works with multiple values.
    $this->assertFalse(qp($file, '#inner-one')->removeClass('innerClass')->hasClass('innerClass'));
    $this->assertTrue(qp($file, '#inner-one')->addClass('testClass')->removeClass('innerClass')->hasClass('testClass'));
  }

  public function testAdd() {
    $file = DATA_FILE;
    $this->assertEquals(7, qp($file, 'li')->add('inner')->size());
  }

  public function testEnd() {
    $file = DATA_FILE;
    $this->assertEquals(2, qp($file, 'inner')->find('li')->end()->size());
  }

  public function testAndSelf() {
    $file = DATA_FILE;
    $this->assertEquals(7, qp($file, 'inner')->find('li')->andSelf()->size());
  }

  public function testChildren() {
    $file = DATA_FILE;
    $this->assertEquals(5, qp($file, 'inner')->children()->size());
    foreach (qp($file, 'inner')->children('li') as $kid) {
      $this->assertEquals('li', $kid->tag());
    }
    $this->assertEquals(5, qp($file, 'inner')->children('li')->size());
    $this->assertEquals(1, qp($file, ':root')->children('unary')->size());
  }
  public function testRemoveChildren() {
    $file = DATA_FILE;
    $this->assertEquals(0, qp($file, '#inner-one')->removeChildren()->find('li')->size());
  }

  public function testContents() {
    $file = DATA_FILE;
    $this->assertGreaterThan(5, qp($file, 'inner')->contents()->size());
    // Two cdata nodes and one element node.
    $this->assertEquals(3, qp($file, '#inner-two')->contents()->size());

    // Issue #51: Under certain recursive conditions, this returns error.
    // Warning: Whitespace is important in the markup beneath.
    $xml = '<html><body><div>Hello
        <div>how are you
          <div>fine thank you
            <div>and you ?</div>
          </div>
        </div>
      </div>
    </body></html>';
    $cr = $this->contentsRecurse(qp($xml));
    $this->assertEquals(14, count($cr), implode("\n", $cr));
  }

  public function testNS() {
    $xml = '<?xml version="1.0"?><root xmlns="foo:bar"><e>test</e></root>';

    $q = qp($xml, "e");

    $this->assertEquals(1, $q->size());

    $this->assertEquals("foo:bar", $q->ns());
  }

  /**
   * Helper function for testContents().
   * Based on problem reported in issue 51.
   */
  private function contentsRecurse($source, &$pack = array()) {
    //static $i = 0;
    //static $filter = "%d. Node type: %d, Content: '%s'\n";
    $children = $source->contents();
    //$node = $source->get(0);
    $pack[] = 1; //sprintf($filter, ++$i, $node->nodeType, $source->html());

    foreach ($children as $child) {
      $pack += $this->contentsRecurse($child, $pack);
    }

    return $pack;
  }

  public function testSiblings() {
    $file = DATA_FILE;
    $this->assertEquals(3, qp($file, '#one')->siblings()->size());
    $this->assertEquals(2, qp($file, 'unary')->siblings('inner')->size());
  }

  public function testXinclude() {

  }

  public function testHTML() {
    $file = DATA_FILE;
    $qp = qp($file, 'unary');
    $html = '<b>test</b>';
    $this->assertEquals($html, $qp->html($html)->find('b')->html());

    $html = '<html><head><title>foo</title></head><body>bar</body></html>';
    // We expect a DocType to be prepended:
    $this->assertEquals('<!DOCTYPE', substr(qp($html)->html(), 0, 9));

    // Check that HTML is not added to empty finds. Note the # is for a special
    // case.
    $this->assertEquals('', qp($html, '#nonexistant')->html('<p>Hello</p>')->html());
    $this->assertEquals('', qp($html, 'nonexistant')->html('<p>Hello</p>')->html());

    // We expect NULL if the document is empty.
    $this->assertNull(qp()->html());

    // Non-DOMNodes should not be rendered:
    $fn = 'mapCallbackFunction';
    $this->assertNull(qp($file, 'li')->map(array($this, $fn))->html());
  }

  public function testInnerHTML() {
    $html = '<html><head></head><body><div id="me">Test<p>Again</p></div></body></html>';

    $this->assertEquals('Test<p>Again</p>', qp($html,'#me')->innerHTML());
  }

  public function testInnerXML() {
    $html = '<?xml version="1.0"?><root><div id="me">Test<p>Again1</p></div></root>';
    $test = 'Test<p>Again1</p>';

    $this->assertEquals($test, qp($html,'#me')->innerXML());

    $html = '<?xml version="1.0"?><root><div id="me">Test<p>Again2<br/></p><![CDATA[Hello]]><?pi foo ?></div></root>';
    $test = 'Test<p>Again2<br/></p><![CDATA[Hello]]><?pi foo ?>';

    $this->assertEquals($test, qp($html,'#me')->innerXML());

    $html = '<?xml version="1.0"?><root><div id="me"/></root>';
    $test = '';
    $this->assertEquals($test, qp($html,'#me')->innerXML());

    $html = '<?xml version="1.0"?><root id="me">test</root>';
    $test = 'test';
    $this->assertEquals($test, qp($html,'#me')->innerXML());
  }

  public function testInnerXHTML() {
    $html = '<?xml version="1.0"?><html><head></head><body><div id="me">Test<p>Again</p></div></body></html>';

    $this->assertEquals('Test<p>Again</p>', qp($html,'#me')->innerXHTML());

    // Regression for issue #10: Tags should not be unary (e.g. we want <script></script>, not <script/>)
    $xml = '<html><head><title>foo</title></head><body><div id="me">Test<p>Again<br/></p></div></body></html>';
    // Look for a closing </br> tag
    $regex = '/<\/br>/';
    $this->assertRegExp($regex, qp($xml, '#me')->innerXHTML(), 'BR should have a closing tag.');
  }

  public function testXML() {
    $file = DATA_FILE;
    $qp = qp($file, 'unary');
    $xml = '<b>test</b>';
    $this->assertEquals($xml, $qp->xml($xml)->find('b')->xml());

    $xml = '<html><head><title>foo</title></head><body>bar</body></html>';
    // We expect an XML declaration to be prepended:
    $this->assertEquals('<?xml', substr(qp($xml, 'html')->xml(), 0, 5));

    // We don't want an XM/L declaration if xml(TRUE).
    $xml = '<?xml version="1.0"?><foo/>';
    $this->assertFalse(strpos(qp($xml)->xml(TRUE), '<?xml'));

    // We expect NULL if the document is empty.
    $this->assertNull(qp()->xml());

    // Non-DOMNodes should not be rendered:
    $fn = 'mapCallbackFunction';
    $this->assertNull(qp($file, 'li')->map(array($this, $fn))->xml());
  }

  public function testXHTML() {
    // throw new Exception();

    $file = DATA_FILE;
    $qp = qp($file, 'unary');
    $xml = '<b>test</b>';
    $this->assertEquals($xml, $qp->xml($xml)->find('b')->xhtml());

    $xml = '<html><head><title>foo</title></head><body>bar</body></html>';
    // We expect an XML declaration to be prepended:
    $this->assertEquals('<?xml', substr(qp($xml, 'html')->xhtml(), 0, 5));

    // We don't want an XM/L declaration if xml(TRUE).
    $xml = '<?xml version="1.0"?><foo/>';
    $this->assertFalse(strpos(qp($xml)->xhtml(TRUE), '<?xml'));

    // We expect NULL if the document is empty.
    $this->assertNull(qp()->xhtml());

    // Non-DOMNodes should not be rendered:
    $fn = 'mapCallbackFunction';
    $this->assertNull(qp($file, 'li')->map(array($this, $fn))->xhtml());

    // Regression for issue #10: Tags should not be unary (e.g. we want <script></script>, not <script/>)
    $xml = '<html><head><title>foo</title></head>
      <body>
      bar<br/><hr width="100">
      <script></script>
      <script>
      alert("Foo");
      </script>
      <frameset id="fooframeset"></frameset>
      </body></html>';

    $xhtml = qp($xml)->xhtml();

    //throw new Exception($xhtml);

    // Look for a properly formatted BR unary tag:
    $regex = '/<br \/>/';
    $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');

    // Look for a properly formatted HR tag:
    $regex = '/<hr width="100" \/>/';
    $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');

    // Ensure that script tag is not collapsed:
    $regex = '/<script><\/script>/';
    $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');

    // Ensure that frameset tag is not collapsed (it looks like <frame>):
    $regex = '/<frameset id="fooframeset"><\/frameset>/';
    $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');

    // Ensure that script gets wrapped in CDATA:
    $find = '/* <![CDATA[ ';
    $this->assertTrue(strpos($xhtml, $find) > 0, 'CDATA section should be escaped.');

    // Regression: Make sure it parses.
    $xhtml = qp('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head></head><body><br /></body></html>')->xhtml();

    qp($xhtml);

  }

  public function testWriteXML() {
    $xml = '<?xml version="1.0"?><html><head><title>foo</title></head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeXML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect an XML declaration at the top.
    $this->assertEquals('<?xml', substr($out, 0, 5));

    $xml = '<?xml version="1.0"?><html><head><script>
    <!--
    1 < 2;
    -->
    </script>
    <![CDATA[This is CDATA]]>
    <title>foo</title></head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeXML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect an XML declaration at the top.
    $this->assertEquals('<?xml', substr($out, 0, 5));

    // Test writing to a file:
    $name = './' . __FUNCTION__ . '.xml';
    qp($xml)->writeXML($name);
    $this->assertTrue(file_exists($name));
    $this->assertTrue(qp($name) instanceof DOMQuery);
    unlink($name);
  }

  public function testWriteXHTML() {
    $xml = '<?xml version="1.0"?><html><head><title>foo</title></head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeXHTML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect an XML declaration at the top.
    $this->assertEquals('<?xml', substr($out, 0, 5));

    $xml = '<?xml version="1.0"?><html><head><script>
    <!--
    1 < 2;
    -->
    </script>
    <![CDATA[This is CDATA]]>
    <title>foo</title></head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'html')->writeXHTML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect an XML declaration at the top.
    $this->assertEquals('<?xml', substr($out, 0, 5));

    // Test writing to a file:
    $name = './' . __FUNCTION__ . '.xml';
    qp($xml)->writeXHTML($name);
    $this->assertTrue(file_exists($name));
    $this->assertTrue(qp($name) instanceof DOMQuery);
    unlink($name);

    // Regression for issue #10 (keep closing tags in XHTML)
    $xhtml = '<?xml version="1.0"?><html><head><title>foo</title><script></script><br/></head><body>bar</body></html>';
    if (!ob_start()) die ("Could not start OB.");
    qp($xhtml, 'html')->writeXHTML();
    $out = ob_get_contents();
    ob_end_clean();

    $pattern = '/<\/script>/';
    $this->assertRegExp($pattern, $out, 'Should be closing script tag.');

    $pattern = '/<\/br>/';
    $this->assertRegExp($pattern, $out, 'Should be closing br tag.');
  }

  /**
   * @expectedException \QueryPath\IOException
   */
  public function testFailWriteXML() {
    try {
      qp()->writeXML('./test/no-writing.xml');
    }
    catch (Exception $e) {
      //print $e->getMessage();
      throw $e;
    }

  }

  /**
   * @expectedException \QueryPath\IOException
   */
  public function testFailWriteXHTML() {
    try {
      qp()->writeXHTML('./test/no-writing.xml');
    }
    catch (\QueryPath\IOException $e) {
      //print $e->getMessage();
      throw $e;
    }

  }

  /**
   * @expectedException \QueryPath\IOException
   */
  public function testFailWriteHTML() {
    try {
      qp('<?xml version="1.0"?><foo/>')->writeXML('./test/no-writing.xml');
    }
    catch (\QueryPath\IOException $e) {
      // print $e->getMessage();
      throw $e;
    }

  }

  public function testWriteHTML() {
    $xml = '<html><head><title>foo</title></head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeHTML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect a doctype declaration at the top.
    $this->assertEquals('<!DOC', substr($out, 0, 5));

    $xml = '<html><head><title>foo</title>
    <script><!--
    var foo = 1 < 5;
    --></script>
    </head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeHTML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect a doctype declaration at the top.
    $this->assertEquals('<!DOC', substr($out, 0, 5));

    $xml = '<html><head><title>foo</title>
    <script><![CDATA[
    var foo = 1 < 5;
    ]]></script>
    </head><body>bar</body></html>';

    if (!ob_start()) die ("Could not start OB.");
    qp($xml, 'tml')->writeHTML();
    $out = ob_get_contents();
    ob_end_clean();

    // We expect a doctype declaration at the top.
    $this->assertEquals('<!DOC', substr($out, 0, 5));

    // Test writing to a file:
    $name = './' . __FUNCTION__ . '.html';
    qp($xml)->writeXML($name);
    $this->assertTrue(file_exists($name));
    $this->assertTrue(qp($name) instanceof DOMQuery);
    unlink($name);
  }

  public function testText() {
    $xml = '<?xml version="1.0"?><root><div>Text A</div><div>Text B</div></root>';
    $this->assertEquals('Text AText B', qp($xml)->text());
    $this->assertEquals('Foo', qp($xml, 'div')->eq(0)->text('Foo')->text());
    $this->assertEquals('BarBar', qp($xml, 'div')->text('Bar')->text());
  }

  public function testTextAfter() {
    $xml = '<?xml version="1.0"?><root><br/>After<foo/><br/>After2<div/>After3</root>';
    $this->assertEquals('AfterAfter2', qp($xml, 'br')->textAfter());
    $this->assertEquals('Blarg', qp($xml, 'foo')->textAfter('Blarg')->top('foo')->textAfter());
  }

  public function testTextBefore() {
    $xml = '<?xml version="1.0"?><root>Before<br/><foo/>Before2<br/>Before3<div/></root>';
    $this->assertEquals('BeforeBefore2', qp($xml, 'br')->textBefore());
    $this->assertEquals('Blarg', qp($xml, 'foo')->textBefore('Blarg')->top('foo')->textBefore());

  }

  public function testTextImplode() {
    $xml = '<?xml version="1.0"?><root><div>Text A</div><div>Text B</div></root>';
    $this->assertEquals('Text A, Text B', qp($xml, 'div')->textImplode());
    $this->assertEquals('Text A--Text B', qp($xml, 'div')->textImplode('--'));

    $xml = '<?xml version="1.0"?><root><div>Text A </div><div>Text B</div></root>';
    $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode());

    $xml = '<?xml version="1.0"?><root><div>Text A </div>
    <div>
    </div><div>Text B</div></root>';
    $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode(', ', TRUE));

    // Test with empties
    $xml = '<?xml version="1.0"?><root><div>Text A</div><div> </div><div>Text B</div></root>';
    $this->assertEquals('Text A- -Text B', qp($xml, 'div')->textImplode('-', FALSE));
  }

  public function testChildrenText() {
    $xml = '<?xml version="1.0"?><root><wrapper>
    NOT ME!
    <div>Text A </div>
    <div>
    </div><div>Text B</div></wrapper></root>';
    $this->assertEquals('Text A , Text B', qp($xml, 'div')->childrenText(', ', TRUE), 'Just inner text.');
  }

  public function testNext() {
    $file = DATA_FILE;
    $this->assertEquals('inner', qp($file, 'unary')->next()->tag());
    $this->assertEquals('foot', qp($file, 'inner')->next()->eq(1)->tag());

    $this->assertEquals('foot', qp($file, 'unary')->next('foot')->tag());

    // Regression test for issue eabrand identified:

    $qp = qp(\QueryPath::HTML_STUB, 'body')->append('<div></div><p>Hello</p><p>Goodbye</p>')
      ->children('p')
      ->after('<p>new paragraph</p>');

    $testarray = array('new paragraph', 'Goodbye', 'new paragraph');

    //throw new Exception($qp->top()->xml());

    $qp = $qp->top('p:first-of-type');
    $this->assertEquals('Hello', $qp->text(), "Test First P " . $qp->top()->html());
    $i = 0;
    while($qp->next('p')->html() != null) {
      $qp = $qp->next('p');
      $this->assertEquals(1, count($qp));
      $this->assertEquals($testarray[$i], $qp->text(), $i . " didn't match " . $qp->top()->xml() );
      $i++;
    }
    $this->assertEquals(3, $i);
//    $this->assertEquals('new paragraph', $qp->next()->text(), "Test Newly Added P");
//    $this->assertEquals('Goodbye', $qp->next()->text(), "Test third P");
//    $this->assertEquals('new paragraph', $qp->next()->text(), "Test Other Newly Added P");
  }
  public function testPrev() {
    $file = DATA_FILE;
    $this->assertEquals('head', qp($file, 'unary')->prev()->tag());
    $this->assertEquals('inner', qp($file, 'inner')->prev()->eq(1)->tag());
    $this->assertEquals('head', qp($file, 'foot')->prev('head')->tag());
  }
  public function testNextAll() {
    $file = DATA_FILE;
    $this->assertEquals(3, qp($file, '#one')->nextAll()->size());
    $this->assertEquals(2, qp($file, 'unary')->nextAll('inner')->size());
  }
  public function testPrevAll() {
    $file = DATA_FILE;
    $this->assertEquals(3, qp($file, '#four')->prevAll()->size());
    $this->assertEquals(2, qp($file, 'foot')->prevAll('inner')->size());
  }
  public function testParent() {
    $file = DATA_FILE;
    $this->assertEquals('root', qp($file, 'unary')->parent()->tag());
    $this->assertEquals('root', qp($file, 'li')->parent('root')->tag());
    $this->assertEquals(2, qp($file, 'li')->parent()->size());
  }
  public function testClosest() {
    $file = DATA_FILE;
    $this->assertEquals('root', qp($file, 'li')->parent('root')->tag());

    $xml = '<?xml version="1.0"?>
    <root>
      <a class="foo">
        <b/>
      </a>
      <b class="foo"/>
    </root>';
    $this->assertEquals(2, qp($xml, 'b')->closest('.foo')->size());
  }

  public function testParents() {
    $file = DATA_FILE;

    // Three: two inners and a root.
    $this->assertEquals(3, qp($file, 'li')->parents()->size());
    $this->assertEquals('root', qp($file, 'li')->parents('root')->tag());
  }

  public function testCloneAll() {
    $file = DATA_FILE;

    // Shallow test
    $qp = qp($file, 'unary');
    $one = $qp->get(0);
    $two = $qp->cloneAll()->get(0);
    $this->assertTrue($one !== $two);
    $this->assertEquals('unary', $two->tagName);

    // Deep test: make sure children are also cloned.
    $qp = qp($file, 'inner');
    $one = $qp->find('li')->get(0);
    $two = $qp->top('inner')->cloneAll(TRUE)->findInPlace('li')->get(0);
    $this->assertEquals('li', $two->tagName);
    $this->assertTrue($one !== $two);
  }

  public function testBranch() {
    $qp = qp(\QueryPath::HTML_STUB);
    $branch = $qp->branch();
    $branch->top('title')->text('Title');
    $qp->top('title')->text('FOOOOO')->top();
    $qp->find('body')->text('This is the body');

    $this->assertEquals($qp->top('title')->text(), $branch->top('title')->text(), $branch->top()->html());

    $qp = qp(\QueryPath::HTML_STUB);
    $branch = $qp->branch('title');
    $branch->find('title')->text('Title');
    $qp->find('body')->text('This is the body');
    $this->assertEquals($qp->top()->find('title')->text(), $branch->text());
  }

  public function testXpath() {
    $file = DATA_FILE;

    $this->assertEquals('head', qp($file)->xpath("//*[@id='head']")->tag());
  }

  public function test__clone() {
    $file = DATA_FILE;

    $qp = qp($file, 'inner:first-of-type');
    $qp2 = clone $qp;
    $this->assertFalse($qp === $qp2);
    $qp2->findInPlace('li')->attr('foo', 'bar');
    $this->assertEquals('', $qp->find('li')->attr('foo'));
    $this->assertEquals('bar', $qp2->attr('foo'), $qp2->top()->xml());
  }

  public function testStub() {
    $this->assertEquals(1, qp(\QueryPath::HTML_STUB)->find('title')->size());
  }

  public function testIterator() {

    $qp = qp(\QueryPath::HTML_STUB, 'body')->append('<li/><li/><li/><li/>');

    $this->assertEquals(4, $qp->find('li')->size());
    $i = 0;
    foreach ($qp->find('li') as $li) {
      ++$i;
      $li->text('foo');
    }
    $this->assertEquals(4, $i);
    $this->assertEquals('foofoofoofoo', $qp->top()->find('li')->text());
  }

  public function testModeratelySizedDocument() {

    $this->assertEquals(1, qp(MEDIUM_FILE)->size());

    $contents = file_get_contents(MEDIUM_FILE);
    $this->assertEquals(1, qp($contents)->size());
  }

  /**
   * @deprecated
   */
  public function testSize() {
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $this->assertEquals(5, $qp->size());
  }

  public function testCount() {
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $this->assertEquals(5, $qp->count());

    // Test that this is exposed to PHP's Countable logic.
    $this->assertEquals(5, count(qp($file, 'li')));

  }

  public function testLength() {

    // Test that the length attribute works exactly the same as size.
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $this->assertEquals(5, $qp->length);


  }

  public function testDocument() {
    $file = DATA_FILE;
    $doc1 = new \DOMDocument('1.0');
    $doc1->load($file);
    $qp = qp($doc1);

    $this->assertEquals($doc1, $qp->document());

    // Ensure that adding to the DOMDocument is accessible to QP:
    $ele = $doc1->createElement('testDocument');
    $doc1->documentElement->appendChild($ele);

    $this->assertEquals(1, $qp->find('testDocument')->size());
  }

  /*
  public function test__get() {
    // Test that other properties are not interferred with by __get().
    $file = DATA_FILE;
    $options = array('QueryPath_class' => 'QueryPathExtended');
    $foo = qp($file,'li', $options)->foo;

    $this->assertEquals('bar', $foo);
  }
  */

  /**
   * @  expectedException \QueryPath\Exception
   */
   /*
  public function testFailed__get() {
    // This should generate an error because 'last' is protected.
    qp(DATA_FILE)->last;
  }
  */

  public function testDetach() {
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $start = $qp->size();
    $finish = $qp->detach()->size();
    $this->assertEquals($start, $finish);
    $this->assertEquals(0, $qp->find(':root li')->size());
  }

  public function testAttach() {
    $file = DATA_FILE;
    $qp = qp($file, 'li');
    $start = $qp->size();
    $finish = $qp->detach()->size();
    $dest = qp('<?xml version="1.0"?><root><dest/></root>', 'dest');
    $qp = $qp->attach($dest);
    $this->assertEquals(5, $dest->find(':root li')->size());
  }

  public function testEmptyElement() {
    $file = DATA_FILE;
    $this->assertEquals(0, qp($file, '#inner-two')->emptyElement()->find('li')->size());
    $this->assertEquals('<inner id="inner-two"/>', qp($file, '#inner-two')->emptyElement()->html());

    // Make sure text children get wiped out, too.
    $this->assertEquals('', qp($file, 'foot')->emptyElement()->text());
  }

  public function testHas() {
    $file = DATA_FILE;

    // Test with DOMNode object
    $qp = qp($file, 'foot');
    $selector = $qp->get(0);
    $qp = $qp->top('root')->has($selector);

    // This should have one element named 'root'.
    $this->assertEquals(1, $qp->size(), 'One element is a parent of foot');
    $this->assertEquals('root', $qp->tag(), 'Root has foot.');

    // Test with CSS selector
    $qp = qp($file, 'root')->has('foot');

    // This should have one element named 'root'.
    $this->assertEquals(1, $qp->size(), 'One element is a parent of foot');
    $this->assertEquals('root', $qp->tag(), 'Root has foot.');

    // Test multiple matches.
    $qp = qp($file, '#docRoot, #inner-two')->has('#five');
    $this->assertEquals(2, $qp->size(), 'Two elements are parents of #five');
    $this->assertEquals('inner', $qp->get(0)->tagName, 'Inner has li.');

    /*
    $this->assertEquals(qp($file, '#one')->children()->get(), qp($file, '#inner-one')->has($selector)->get(), "Both should be empty/false");
    $qp = qp($file, 'root')->children("inner");
    $selector = qp($file, '#two');
    $this->assertNotEquals(qp($file, '#head'), qp($file, '#inner-one')->has($selector));
    $this->assertEquals(qp($file, 'root'), qp($file, 'root')->has($selector), "Should both have 1 element - root");
    */
  }

  public function testNextUntil() {
    $file = DATA_FILE;
    $this->assertEquals(3, qp($file, '#one')->nextUntil()->size());
    $this->assertEquals(2, qp($file, 'li')->nextUntil('#three')->size());
  }

  public function testPrevUntil() {
    $file = DATA_FILE;
    $this->assertEquals(3, qp($file, '#four')->prevUntil()->size());
    $this->assertEquals(2, qp($file, 'foot')->prevUntil('unary')->size());
  }

  public function testEven() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, 'inner')->even()->size());
    $this->assertEquals(2, qp($file, 'li')->even()->size());
  }

  public function testOdd() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, 'inner')->odd()->size());
    $this->assertEquals(3, qp($file, 'li')->odd()->size());
  }

  public function testFirst() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, 'inner')->first()->size());
    $this->assertEquals(1, qp($file, 'li')->first()->size());
    $this->assertEquals("Hello", qp($file, 'li')->first()->text());
  }

  public function testFirstChild() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, '#inner-one')->firstChild()->size());
    $this->assertEquals("Hello", qp($file, '#inner-one')->firstChild()->text());
  }

  public function testLast() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, 'inner')->last()->size());
    $this->assertEquals(1, qp($file, 'li')->last()->size());
    $this->assertEquals('', qp($file, 'li')->last()->text());
  }

  public function testLastChild() {
    $file = DATA_FILE;
    $this->assertEquals(1, qp($file, '#inner-one')->lastChild()->size());
    $this->assertEquals("Last", qp($file, '#inner-one')->lastChild()->text());
  }

  public function testParentsUntil() {
    $file = DATA_FILE;

    // Three: two inners and a root.
    $this->assertEquals(3, qp($file, 'li')->parentsUntil()->size());
    $this->assertEquals(2, qp($file, 'li')->parentsUntil('root')->size());
  }

  public function testSort() {
    $xml = '<?xml version="1.0"?><r><s/><i>1</i><i>5</i><i>2</i><i>1</i><e/></r>';

    // Canary.
    $qp = qp($xml, 'i');
    $expect = array(1, 5, 2, 1);
    foreach($qp as $item) {
      $this->assertEquals(array_shift($expect), $item->text());
    }

    // Test simple ordering.
    $comp = function (\DOMNode $a, \DOMNode $b) {
      if ($a->textContent == $b->textContent) {
        return 0;
      }
      return $a->textContent > $b->textContent ? 1 : -1;
    };
    $qp = qp($xml, 'i')->sort($comp);
    $expect = array(1, 1, 2, 5);
    foreach($qp as $item) {
      $this->assertEquals(array_shift($expect), $item->text());
    }

    $comp = function (\DOMNode $a, \DOMNode $b) {
      $qpa = qp($a);
      $qpb = qp($b);

      if ($qpa->text() == $qpb->text()) {
        return 0;
      }
      return $qpa->text()> $qpb->text()? 1 : -1;
    };
    $qp = qp($xml, 'i')->sort($comp);
    $expect = array(1, 1, 2, 5);
    foreach($qp as $item) {
      $this->assertEquals(array_shift($expect), $item->text());
    }

    // Test DOM re-ordering
    $comp = function (\DOMNode $a, \DOMNode $b) {
      if ($a->textContent == $b->textContent) {
        return 0;
      }
      return $a->textContent > $b->textContent ? 1 : -1;
    };
    $qp = qp($xml, 'i')->sort($comp, TRUE);
    $expect = array(1, 1, 2, 5);
    foreach($qp as $item) {
      $this->assertEquals(array_shift($expect), $item->text());
    }
    $res = $qp->top()->xml();
    $expect_xml = '<?xml version="1.0"?><r><s/><i>1</i><i>1</i><i>2</i><i>5</i><e/></r>';
    $this->assertXmlStringEqualsXmlString($expect_xml, $res);
  }

  /**
   * Regression test for issue #14.
   */
  public function testRegressionFindOptimizations() {
    $xml = '<?xml version="1.0"?><root>
      <item id="outside">
        <item>
          <item id="inside">Test</item>
        </item>
      </item>
    </root>';

    // From inside, should not be able to find outside.
    $this->assertEquals(0, qp($xml, '#inside')->find('#outside')->size());

    $xml = '<?xml version="1.0"?><root>
      <item class="outside">
        <item>
          <item class="inside">Test</item>
        </item>
      </item>
    </root>';
    // From inside, should not be able to find outside.
    $this->assertEquals(0, qp($xml, '.inside')->find('.outside')->size());
  }

  public function testDataURL() {

    $text = 'Hi!'; // Base-64 encoded value would be SGkh
    $xml = '<?xml version="1.0"?><root><item/></root>';

    $qp = qp($xml, 'item')->dataURL('secret', $text, 'text/plain');

    $this->assertEquals(1, $qp->top('item[secret]')->size(), 'One attr should be added.');

    $this->assertEquals('data:text/plain;base64,SGkh', $qp->attr('secret'), 'Attr value should be data URL.');

    $result = $qp->dataURL('secret');
    $this->assertEquals(2, count($result), 'Should return two-array.');
    $this->assertEquals($text, $result['data'] , 'Should return original data, decoded.');
    $this->assertEquals('text/plain', $result['mime'], 'Should return the original MIME');
  }

  public function testEncodeDataURL() {
    $data = \QueryPath::encodeDataURL('Hi!', 'text/plain');
    $this->assertEquals('data:text/plain;base64,SGkh', $data);
  }
}

/**
 * A simple mock for testing qp()'s abstract factory.
 *
 * @ingroup querypath_tests
 */
class QueryPathExtended extends DOMQuery {
  public $foo = 'bar';
  public function foonator() {
    return TRUE;
  }
}