summaryrefslogtreecommitdiff
path: root/lib/querypath/test/Tests
diff options
context:
space:
mode:
Diffstat (limited to 'lib/querypath/test/Tests')
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php357
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php520
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php828
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php1439
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php120
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php23
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php51
-rw-r--r--lib/querypath/test/Tests/QueryPath/DOMQueryTest.php1865
-rw-r--r--lib/querypath/test/Tests/QueryPath/EntitiesTest.php54
-rw-r--r--lib/querypath/test/Tests/QueryPath/ExtensionTest.php153
-rw-r--r--lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php41
-rw-r--r--lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php60
-rw-r--r--lib/querypath/test/Tests/QueryPath/OptionsTest.php60
-rw-r--r--lib/querypath/test/Tests/QueryPath/QueryPathTest.php56
-rw-r--r--lib/querypath/test/Tests/QueryPath/TestCase.php22
-rw-r--r--lib/querypath/test/Tests/QueryPath/XMLIshTest.php63
16 files changed, 5712 insertions, 0 deletions
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php b/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php
new file mode 100644
index 0000000..9542482
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php
@@ -0,0 +1,357 @@
+<?php
+/**
+ * @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Token;
+use \QueryPath\CSS\DOMTraverser;
+use \QueryPath\CSS\Parser;
+use \QueryPath\CSS\EventHandler;
+
+define('TRAVERSER_XML', __DIR__ . '/../../../DOMTraverserTest.xml');
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class DOMTraverserTest extends TestCase {
+
+ protected $xml_file = TRAVERSER_XML;
+ public function debug($msg) {
+ fwrite(STDOUT, PHP_EOL . $msg);
+ }
+
+ public function testConstructor() {
+ $dom = new \DOMDocument('1.0');
+ $dom->load($this->xml_file);
+
+ $splos = new \SPLObjectStorage();
+ $splos->attach($dom);
+
+ $traverser = new DOMTraverser($splos);
+
+ $this->assertInstanceOf('\QueryPath\CSS\Traverser', $traverser);
+ $this->assertInstanceOf('\QueryPath\CSS\DOMTraverser', $traverser);
+ }
+
+ protected function traverser() {
+ $dom = new \DOMDocument('1.0');
+ $dom->load($this->xml_file);
+
+ $splos = new \SPLObjectStorage();
+ $splos->attach($dom->documentElement);
+
+ $traverser = new DOMTraverser($splos);
+
+ return $traverser;
+ }
+
+ protected function find($selector) {
+ return $this->traverser()->find($selector)->matches();
+ }
+
+ public function testFind() {
+ $res = $this->traverser()->find('root');
+
+ // Ensure that return contract is not violated.
+ $this->assertInstanceOf('\QueryPath\CSS\Traverser', $res);
+ }
+
+ public function testMatches() {
+ $res = $this->traverser()->matches();
+ $this->assertEquals(1, count($res));
+ }
+
+ public function testMatchElement() {
+ // Canary: If element does not exist, must return FALSE.
+ $matches = $this->find('NO_SUCH_ELEMENT');
+ $this->assertEquals(0, count($matches));
+
+ // Test without namespace
+ $matches = $this->find('root');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('crowded');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('outside');
+ $this->assertEquals(3, count($matches));
+
+ // Check nested elements.
+ $matches = $this->find('a');
+ $this->assertEquals(3, count($matches));
+
+ // Test wildcard.
+ $traverser = $this->traverser();
+ $matches = $traverser->find('*')->matches();
+ $actual= $traverser->getDocument()->getElementsByTagName('*');
+ $this->assertEquals($actual->length, count($matches));
+
+
+ // Test with namespace
+ //$this->markTestIncomplete();
+ $matches = $this->find('ns_test');
+ $this->assertEquals(3, count($matches));
+
+ $matches = $this->find('test|ns_test');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('test|ns_test>ns_attr');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('*|ns_test');
+ $this->assertEquals(3, count($matches));
+
+ $matches = $this->find('test|*');
+ $this->assertEquals(1, count($matches));
+
+ // Test where namespace is declared on the element.
+ $matches = $this->find('newns|my_element');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('test|ns_test>newns|my_element');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('test|*>newns|my_element');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('*|ns_test>newns|my_element');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('*|*>newns|my_element');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('*>newns|my_element');
+ $this->assertEquals(1, count($matches));
+ }
+
+ public function testMatchAttributes() {
+
+ $matches = $this->find('crowded[attr1]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('crowded[attr1=one]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('crowded[attr2^=tw]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('classtest[class~=two]');
+ $this->assertEquals(1, count($matches));
+ $matches = $this->find('classtest[class~=one]');
+ $this->assertEquals(1, count($matches));
+ $matches = $this->find('classtest[class~=seven]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('crowded[attr0]');
+ $this->assertEquals(0, count($matches));
+
+ $matches = $this->find('[level=1]');
+ $this->assertEquals(3, count($matches));
+
+ $matches = $this->find('[attr1]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('[test|myattr]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('[test|myattr=foo]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('[*|myattr=foo]');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('[|myattr=foo]');
+ $this->assertEquals(0, count($matches));
+
+ $matches = $this->find('[|level=1]');
+ $this->assertEquals(3, count($matches));
+
+ $matches = $this->find('[*|level=1]');
+ $this->assertEquals(4, count($matches));
+
+ // Test namespace on attr where namespace
+ // is declared on that element
+ $matches = $this->find('[nuther|ping]');
+ $this->assertEquals(1, count($matches));
+
+ // Test multiple namespaces on an element.
+ $matches = $this->find('[*|ping=3]');
+ $this->assertEquals(1, count($matches));
+
+ // Test multiple namespaces on an element.
+ $matches = $this->find('[*|ping]');
+ $this->assertEquals(1, count($matches));
+ }
+
+ public function testMatchId() {
+ $matches = $this->find('idtest#idtest-one');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('#idtest-one');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('outter#fake');
+ $this->assertEquals(0, count($matches));
+
+ $matches = $this->find('#fake');
+ $this->assertEquals(0, count($matches));
+ }
+
+ public function testMatchClasses() {
+ // Basic test.
+ $matches = $this->find('a.a1');
+ $this->assertEquals(1, count($matches));
+
+ // Count multiple.
+ $matches = $this->find('.first');
+ $this->assertEquals(2, count($matches));
+
+ // Grab one in the middle of a list.
+ $matches = $this->find('.four');
+ $this->assertEquals(1, count($matches));
+
+ // One element with two classes.
+ $matches = $this->find('.three.four');
+ $this->assertEquals(1, count($matches));
+ }
+
+ public function testMatchPseudoClasses() {
+
+ $matches = $this->find('ul>li:first');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('ul>li:not(.first)');
+ $this->assertEquals(5, count($matches));
+
+ }
+
+ public function testMatchPseudoElements() {
+ $matches = $this->find('p::first-line');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('p::first-letter');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('p::before');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('p::after');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('bottom::after');
+ $this->assertEquals(0, count($matches));
+ }
+
+ public function testCombineAdjacent() {
+ // Simple test
+ $matches = $this->find('idtest + p');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('p', $m->tagName);
+ }
+
+ // Test ignoring PCDATA
+ $matches = $this->find('p + one');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('one', $m->tagName);
+ }
+
+ // Test that non-adjacent elements don't match.
+ $matches = $this->find('idtest + one');
+ foreach ($matches as $m) {
+ $this->assertEquals('one', $m->tagName);
+ }
+ $this->assertEquals(0, count($matches), 'Non-adjacents should not match.');
+
+ // Test that elements BEFORE don't match
+ $matches = $this->find('one + p');
+ foreach ($matches as $m) {
+ $this->assertEquals('one', $m->tagName);
+ }
+ $this->assertEquals(0, count($matches), 'Match only if b is after a');
+ }
+
+ public function testCombineSibling() {
+ // Canary:
+ $matches = $this->find('one ~ two');
+ $this->assertEquals(0, count($matches));
+
+ // Canary 2:
+ $matches = $this->find('NO_SUCH_ELEMENT ~ two');
+ $this->assertEquals(0, count($matches));
+
+ // Simple test
+ $matches = $this->find('idtest ~ p');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('p', $m->tagName);
+ }
+
+ // Simple test
+ $matches = $this->find('outside ~ p');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('p', $m->tagName);
+ }
+
+ // Matches only go left, not right.
+ $matches = $this->find('p ~ outside');
+ $this->assertEquals(0, count($matches));
+ }
+ public function testCombineDirectDescendant() {
+ // Canary:
+ $matches = $this->find('one > four');
+ $this->assertEquals(0, count($matches));
+
+ $matches = $this->find('two>three');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('three', $m->tagName);
+ }
+
+ $matches = $this->find('one > two > three');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('three', $m->tagName);
+ }
+
+ $matches = $this->find('a>a>a');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('a>a');
+ $this->assertEquals(2, count($matches));
+ }
+ public function testCombineAnyDescendant() {
+ // Canary
+ $matches = $this->find('four one');
+ $this->assertEquals(0, count($matches));
+
+ $matches = $this->find('one two');
+ $this->assertEquals(1, count($matches));
+ foreach ($matches as $m) {
+ $this->assertEquals('two', $m->tagName);
+ }
+
+ $matches = $this->find('one four');
+ $this->assertEquals(1, count($matches));
+
+ $matches = $this->find('a a');
+ $this->assertEquals(2, count($matches));
+
+ $matches = $this->find('root two four');
+ $this->assertEquals(1, count($matches));
+ }
+ public function testMultipleSelectors() {
+ // fprintf(STDOUT, "=========TEST=========\n\n");
+ $matches = $this->find('one, two');
+ $this->assertEquals(2, count($matches));
+ }
+
+}
+
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
new file mode 100644
index 0000000..981ddd1
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
@@ -0,0 +1,520 @@
+<?php
+/**
+ * @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Token;
+use \QueryPath\CSS\QueryPathEventHandler;
+use \QueryPath\CSS\Parser;
+use \QueryPath\CSS\EventHandler;
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class ParserTest extends TestCase {
+
+ private function getMockHandler($method) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array($method));
+ $mock->expects($this->once())
+ ->method($method)
+ ->with($this->equalTo('mytest'));
+ return $mock;
+ }
+
+ public function testElementID() {
+ $mock = $this->getMockHandler('elementID');
+ $parser = new Parser('#mytest', $mock);
+ $parser->parse();
+
+ }
+
+ public function testElement() {
+
+ // Without namespace
+ $mock = $this->getMockHandler('element');
+ $parser = new Parser('mytest', $mock);
+ $parser->parse();
+
+ // With empty namespace
+ $mock = $this->getMockHandler('element');
+ $parser = new Parser('|mytest', $mock);
+ $parser->parse();
+ }
+
+ public function testElementNS() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS'));
+ $mock->expects($this->once())
+ ->method('elementNS')
+ ->with($this->equalTo('mytest'), $this->equalTo('myns'));
+
+ $parser = new Parser('myns|mytest', $mock);
+ $parser->parse();
+
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS'));
+ $mock->expects($this->once())
+ ->method('elementNS')
+ ->with($this->equalTo('mytest'), $this->equalTo('*'));
+
+ $parser = new Parser('*|mytest', $mock);
+ $parser->parse();
+
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS'));
+ $mock->expects($this->once())
+ ->method('anyElementInNS')
+ ->with($this->equalTo('*'));
+
+ $parser = new Parser('*|*', $mock);
+ $parser->parse();
+ }
+
+ public function testAnyElement() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElement', 'element'));
+ $mock->expects($this->once())
+ ->method('anyElement');
+
+ $parser = new Parser('*', $mock);
+ $parser->parse();
+ }
+
+ public function testAnyElementInNS() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS', 'element'));
+ $mock->expects($this->once())
+ ->method('anyElementInNS')
+ ->with($this->equalTo('myns'));
+
+ $parser = new Parser('myns|*', $mock);
+ $parser->parse();
+ }
+
+ public function testElementClass() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementClass'));
+ $mock->expects($this->once())
+ ->method('elementClass')
+ ->with($this->equalTo('myclass'));
+
+ $parser = new Parser('.myclass', $mock);
+ $parser->parse();
+ }
+
+ public function testPseudoClass() {
+
+ // Test empty pseudoclass
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'));
+
+ $parser = new Parser('myele:mypclass', $mock);
+ $parser->parse();
+
+ // Test pseudoclass with value
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'), $this->equalTo('myval'));
+
+ $parser = new Parser('myele:mypclass(myval)', $mock);
+ $parser->parse();
+
+ // Test pseudclass with pseudoclass:
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'), $this->equalTo(':anotherPseudo'));
+
+ $parser = new Parser('myele:mypclass(:anotherPseudo)', $mock);
+ $parser->parse();
+
+ }
+
+ public function testPseudoElement() {
+ // Test pseudo-element
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoElement'));
+ $mock->expects($this->once())
+ ->method('pseudoElement')
+ ->with($this->equalTo('mypele'));
+
+ $parser = new Parser('myele::mypele', $mock);
+ $parser->parse();
+ }
+
+ public function testDirectDescendant() {
+ // Test direct Descendant
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('directDescendant'));
+ $mock->expects($this->once())
+ ->method('directDescendant');
+
+ $parser = new Parser('ele1 > ele2', $mock);
+ $parser->parse();
+
+ }
+
+ public function testAnyDescendant() {
+ // Test direct Descendant
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyDescendant'));
+ $mock->expects($this->once())
+ ->method('anyDescendant');
+
+ $parser = new Parser('ele1 .class', $mock);
+ $parser->parse();
+
+ }
+
+ public function testAdjacent() {
+ // Test sibling
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('adjacent'));
+ $mock->expects($this->once())
+ ->method('adjacent');
+
+ $parser = new Parser('ele1 + ele2', $mock);
+ $parser->parse();
+ }
+
+ public function testSibling() {
+ // Test adjacent
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('sibling'));
+ $mock->expects($this->once())
+ ->method('sibling');
+
+ $parser = new Parser('ele1 ~ ele2', $mock);
+ $parser->parse();
+ }
+
+ public function testAnotherSelector() {
+ // Test adjacent
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anotherSelector'));
+ $mock->expects($this->once())
+ ->method('anotherSelector');
+
+ $parser = new Parser('ele1 , ele2', $mock);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalAttribute() {
+
+ // Note that this is designed to test throwError() as well as
+ // bad selector handling.
+
+ $parser = new Parser('[test=~far]', new TestEventHandler());
+ try {
+ $parser->parse();
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ public function testAttribute() {
+ $selectors = array(
+ 'element[attr]' => 'attr',
+ '*[attr]' => 'attr',
+ 'element[attr]:class' => 'attr',
+ 'element[attr2]' => 'attr2', // Issue #
+ );
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute'));
+ $mock->expects($this->once())
+ ->method('attribute')
+ ->with($this->equalTo($expected));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+
+ $selectors = array(
+ '*[attr="value"]' => array('attr','value',EventHandler::isExactly),
+ '*[attr^="value"]' => array('attr','value',EventHandler::beginsWith),
+ '*[attr$="value"]' => array('attr','value',EventHandler::endsWith),
+ '*[attr*="value"]' => array('attr','value',EventHandler::containsInString),
+ '*[attr~="value"]' => array('attr','value',EventHandler::containsWithSpace),
+ '*[attr|="value"]' => array('attr','value',EventHandler::containsWithHyphen),
+
+ // This should act like [attr="value"]
+ '*[|attr="value"]' => array('attr', 'value', EventHandler::isExactly),
+
+ // This behavior is displayed in the spec, but not accounted for in the
+ // grammar:
+ '*[attr=value]' => array('attr','value',EventHandler::isExactly),
+
+ // Should be able to escape chars using backslash.
+ '*[attr="\.value"]' => array('attr','.value',EventHandler::isExactly),
+ '*[attr="\.\]\]\]"]' => array('attr','.]]]',EventHandler::isExactly),
+
+ // Backslash-backslash should resolve to single backslash.
+ '*[attr="\\\c"]' => array('attr','\\c',EventHandler::isExactly),
+
+ // Should return an empty value. It seems, though, that a value should be
+ // passed here.
+ '*[attr=""]' => array('attr','',EventHandler::isExactly),
+ );
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute'));
+ $mock->expects($this->once())
+ ->method('attribute')
+ ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2]));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+ }
+
+ public function testAttributeNS() {
+ $selectors = array(
+ '*[ns|attr="value"]' => array('attr', 'ns', 'value',EventHandler::isExactly),
+ '*[*|attr^="value"]' => array('attr', '*', 'value',EventHandler::beginsWith),
+ '*[*|attr|="value"]' => array('attr', '*', 'value',EventHandler::containsWithHyphen),
+ );
+
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attributeNS'));
+ $mock->expects($this->once())
+ ->method('attributeNS')
+ ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2]), $this->equalTo($expected[3]));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+ }
+
+ // Test things that should break...
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalCombinators1() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('ele1 > > ele2', $handler);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalCombinators2() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('ele1+ ,ele2', $handler);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalID() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('##ID', $handler);
+ $parser->parse();
+ }
+
+ // Test combinations
+
+ public function testElementNSClassAndAttribute() {
+
+ $expect = array(
+ new TestEvent(TestEvent::elementNS, 'element', 'ns'),
+ new TestEvent(TestEvent::elementClass, 'class'),
+ new TestEvent(TestEvent::attribute, 'name', 'value', EventHandler::isExactly),
+ );
+ $selector = 'ns|element.class[name="value"]';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+ $this->assertTrue($handler->success());
+
+ // Again, with spaces this time:
+ $selector = ' ns|element. class[ name = "value" ]';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ //$handler->dumpStack();
+ $this->assertTrue($handler->success());
+ }
+
+ public function testAllCombo() {
+
+ $selector = '*|ele1 > ele2.class1 + ns1|ele3.class2[attr=simple] ~
+ .class2[attr2~="longer string of text."]:pseudoClass(value)
+ .class3::pseudoElement';
+ $expect = array(
+ new TestEvent(TestEvent::elementNS, 'ele1', '*'),
+ new TestEvent(TestEvent::directDescendant),
+ new TestEvent(TestEvent::element, 'ele2'),
+ new TestEvent(TestEvent::elementClass, 'class1'),
+ new TestEvent(TestEvent::adjacent),
+ new TestEvent(TestEvent::elementNS, 'ele3', 'ns1'),
+ new TestEvent(TestEvent::elementClass, 'class2'),
+ new TestEvent(TestEvent::attribute, 'attr', 'simple', EventHandler::isExactly),
+ new TestEvent(TestEvent::sibling),
+ new TestEvent(TestEvent::elementClass, 'class2'),
+ new TestEvent(TestEvent::attribute, 'attr2', 'longer string of text.', EventHandler::containsWithSpace),
+ new TestEvent(TestEvent::pseudoClass, 'pseudoClass', 'value'),
+ new TestEvent(TestEvent::anyDescendant),
+ new TestEvent(TestEvent::elementClass, 'class3'),
+ new TestEvent(TestEvent::pseudoElement, 'pseudoElement'),
+ );
+
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ //$handler->dumpStack();
+
+ $this->assertTrue($handler->success());
+
+ /*
+ // Again, with spaces this time:
+ $selector = ' *|ele1 > ele2. class1 + ns1|ele3. class2[ attr=simple] ~ .class2[attr2 ~= "longer string of text."]:pseudoClass(value) .class3::pseudoElement';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ $handler->dumpStack();
+ $this->assertTrue($handler->success());
+ */
+ }
+}
+
+/**
+ * Testing harness for the EventHandler.
+ *
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class TestEventHandler implements EventHandler {
+ var $stack = NULL;
+ var $expect = array();
+
+ public function __construct() {
+ $this->stack = array();
+ }
+
+ public function getStack() {
+ return $this->stack;
+ }
+
+ public function dumpStack() {
+ print "\nExpected:\n";
+ $format = "Element %d: %s\n";
+ foreach ($this->expect as $item) {
+ printf($format, $item->eventType(), implode(',', $item->params()));
+ }
+
+ print "Got:\n";
+ foreach($this->stack as $item){
+ printf($format, $item->eventType(), implode(',', $item->params()));
+ }
+ }
+
+ public function expects($stack) {
+ $this->expect = $stack;
+ }
+
+ public function success() {
+ return ($this->expect == $this->stack);
+ }
+
+ public function elementID($id) {
+ $this->stack[] = new TestEvent(TestEvent::elementID, $id);
+ }
+ public function element($name) {
+ $this->stack[] = new TestEvent(TestEvent::element, $name);
+ }
+ public function elementNS($name, $namespace = NULL){
+ $this->stack[] = new TestEvent(TestEvent::elementNS, $name, $namespace);
+ }
+ public function anyElement(){
+ $this->stack[] = new TestEvent(TestEvent::anyElement);
+ }
+ public function anyElementInNS($ns){
+ $this->stack[] = new TestEvent(TestEvent::anyElementInNS, $ns);
+ }
+ public function elementClass($name){
+ $this->stack[] = new TestEvent(TestEvent::elementClass, $name);
+ }
+ public function attribute($name, $value = NULL, $operation = EventHandler::isExactly){
+ $this->stack[] = new TestEvent(TestEvent::attribute, $name, $value, $operation);
+ }
+ public function attributeNS($name, $ns, $value = NULL, $operation = EventHandler::isExactly){
+ $this->stack[] = new TestEvent(TestEvent::attributeNS, $name, $ns, $value, $operation);
+ }
+ public function pseudoClass($name, $value = NULL){
+ $this->stack[] = new TestEvent(TestEvent::pseudoClass, $name, $value);
+ }
+ public function pseudoElement($name){
+ $this->stack[] = new TestEvent(TestEvent::pseudoElement, $name);
+ }
+ public function directDescendant(){
+ $this->stack[] = new TestEvent(TestEvent::directDescendant);
+ }
+ public function anyDescendant() {
+ $this->stack[] = new TestEvent(TestEvent::anyDescendant);
+ }
+ public function adjacent(){
+ $this->stack[] = new TestEvent(TestEvent::adjacent);
+ }
+ public function anotherSelector(){
+ $this->stack[] = new TestEvent(TestEvent::anotherSelector);
+ }
+ public function sibling(){
+ $this->stack[] = new TestEvent(TestEvent::sibling);
+ }
+}
+
+/**
+ * Simple utility object for use with the TestEventHandler.
+ *
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class TestEvent {
+ const elementID = 0;
+ const element = 1;
+ const elementNS = 2;
+ const anyElement = 3;
+ const elementClass = 4;
+ const attribute = 5;
+ const attributeNS = 6;
+ const pseudoClass = 7;
+ const pseudoElement = 8;
+ const directDescendant = 9;
+ const adjacent = 10;
+ const anotherSelector = 11;
+ const sibling = 12;
+ const anyElementInNS = 13;
+ const anyDescendant = 14;
+
+ var $type = NULL;
+ var $params = NULL;
+
+ public function __construct($event_type) {
+ $this->type = $event_type;
+ $args = func_get_args();
+ array_shift($args);
+ $this->params = $args;
+ }
+
+ public function eventType() {
+ return $this->type;
+ }
+
+ public function params() {
+ return $this->params;
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php b/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php
new file mode 100644
index 0000000..6798b1c
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php
@@ -0,0 +1,828 @@
+<?php
+/**
+ * @file
+ * CSS Event handling tests for PseudoClasses.
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\DOMTraverser\PseudoClass;
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class PseudoClassTest extends TestCase {
+
+ protected function doc($string, $tagname) {
+
+ $doc = new \DOMDocument('1.0');
+ $doc->loadXML($string);
+
+ $found = $doc->getElementsByTagName($tagname)->item(0);
+
+ return array($found, $doc->documentElement);
+
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testUnknownPseudoClass() {
+ $xml = '<?xml version="1.0"?><root><foo>test</foo></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ps->elementMatches('TotallyFake', $ele, $root);
+ }
+
+ public function testLang() {
+ $xml = '<?xml version="1.0"?><root><foo lang="en-US">test</foo></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('lang', $ele, $root, 'en-US');
+ $this->assertTrue($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'en');
+ $this->assertTrue($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'fr-FR');
+ $this->assertFalse($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'fr');
+ $this->assertFalse($ret);
+
+
+ // Check on ele that doesn't have lang.
+ $ret = $ps->elementMatches('lang', $root, $root, 'fr');
+ $this->assertFalse($ret);
+
+ }
+
+ public function testLangNS() {
+ $xml = '<?xml version="1.0"?><root><foo xml:lang="en-US">test</foo></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('lang', $ele, $root, 'en-US');
+ $this->assertTrue($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'en');
+ $this->assertTrue($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'fr-FR');
+ $this->assertFalse($ret);
+ $ret = $ps->elementMatches('lang', $ele, $root, 'fr');
+ $this->assertFalse($ret);
+
+
+ // Check on ele that doesn't have lang.
+ $ret = $ps->elementMatches('lang', $root, $root, 'fr');
+ $this->assertFalse($ret);
+ }
+
+ public function testFormType() {
+ $xml = '<?xml version="1.0"?><root><foo type="submit">test</foo></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('submit', $ele, $root);
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('reset', $ele, $root);
+ $this->assertFalse($ret);
+
+ }
+
+ public function testHasAttribute() {
+ $xml = '<?xml version="1.0"?><root><foo enabled="enabled">test</foo></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('enabled', $ele, $root);
+ $this->assertTrue($ret);
+ $ret = $ps->elementMatches('disabled', $ele, $root);
+ $this->assertFalse($ret);
+ }
+
+ public function testHeader() {
+ $xml = '<?xml version="1.0"?><root><h1>TEST</h1><H6></H6><hi/><h12/><h1i/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'h1');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('header', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'H6');
+ $ret = $ps->elementMatches('header', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'hi');
+ $ret = $ps->elementMatches('header', $ele, $root);
+ $this->assertFalse($ret);
+ list($ele, $root) = $this->doc($xml, 'h1i');
+ $ret = $ps->elementMatches('header', $ele, $root);
+ $this->assertFalse($ret);
+ list($ele, $root) = $this->doc($xml, 'h12');
+ $ret = $ps->elementMatches('header', $ele, $root);
+ $this->assertFalse($ret);
+ }
+
+ public function testContains(){
+ $xml = '<?xml version="1.0"?><root><h>This is a test of :contains.</h></root>';
+
+ list($ele, $root) = $this->doc($xml, 'h');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('contains', $ele, $root, 'test');
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('contains', $ele, $root, 'is a test');
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('contains', $ele, $root, 'This is a test of :contains.');
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('contains', $ele, $root, 'Agent P, here is your mission.');
+ $this->assertFalse($ret);
+
+ $ret = $ps->elementMatches('contains', $ele, $root, "'Agent P, here is your mission.'");
+ $this->assertFalse($ret);
+
+ }
+ public function testContainsExactly() {
+ $xml = '<?xml version="1.0"?><root><h>This is a test of :contains-exactly.</h></root>';
+
+ list($ele, $root) = $this->doc($xml, 'h');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'test');
+ $this->assertFalse($ret);
+
+ $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'This is a test of :contains-exactly.');
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'Agent P, here is your mission.');
+ $this->assertFalse($ret);
+
+ $ret = $ps->elementMatches('contains-exactly', $ele, $root, '"Agent P, here is your mission."');
+ $this->assertFalse($ret);
+ }
+ public function testHas() {
+ $xml = '<?xml version="1.0"?><root><button disabled="disabled"/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'button');
+ $ps = new PseudoClass();
+
+ // Example from CSS 4 spec
+ $ret = $ps->elementMatches('matches', $ele, $root, '[disabled]');
+ $this->assertTrue($ret);
+
+ // Regression for Issue #84:
+ $xml = '<?xml version="1.0"?><root><a/><a/><a src="/foo/bar"/><b src="http://example.com/foo/bar"/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $ele->childNodes;
+ $ps = new PseudoClass();
+
+ $i = 0;
+ foreach ($nl as $n) {
+ $ret = $ps->elementMatches('matches', $n, $root, '[src^="/foo/"]');
+ if ($ret) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(1, $i);
+ }
+ public function testParent() {
+ $ps = new PseudoClass();
+
+ $xml = '<?xml version="1.0"?><root><p/></root>';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertFalse($ret);
+
+ $xml = '<?xml version="1.0"?><root><p></p>></root>';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertFalse($ret);
+
+ $xml = '<?xml version="1.0"?><root><p>TEST</p></root>';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertTrue($ret);
+
+ $xml = '<?xml version="1.0"?><root><p><q/></p></root>';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertTrue($ret);
+
+ }
+ public function testFirst() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'q');
+ $ret = $ps->elementMatches('first', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('first', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'b');
+ $ret = $ps->elementMatches('first', $ele, $root);
+ $this->assertFalse($ret);
+
+ }
+ public function testLast() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'q');
+ $ret = $ps->elementMatches('last', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('last', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'b');
+ $ret = $ps->elementMatches('last', $ele, $root);
+ $this->assertTrue($ret);
+ }
+ public function testNot() {
+ $xml = '<?xml version="1.0"?><root><button/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'button');
+ $ps = new PseudoClass();
+
+ // Example from CSS 4 spec
+ $ret = $ps->elementMatches('not', $ele, $root, '[disabled]');
+ $this->assertTrue($ret);
+
+ $xml = '<?xml version="1.0"?><root><b/><b/><c/><b/></root>';
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $ele->childNodes;
+
+ $i = 0;
+ foreach ($nl as $n) {
+ $ret = $ps->elementMatches('not', $n, $root, 'c');
+ if ($ret) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(3, $i);
+
+ }
+ public function testEmpty() {
+ $xml = '<?xml version="1.0"?><root><foo lang="en-US">test</foo><bar/><baz></baz></root>';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ret = $ps->elementMatches('empty', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'bar');
+ $ret = $ps->elementMatches('empty', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'baz');
+ $ret = $ps->elementMatches('empty', $ele, $root);
+ $this->assertTrue($ret);
+ }
+ public function testOnlyChild() {
+ $xml = '<?xml version="1.0"?><root><foo>test<a/></foo><b><c/></b></root>';
+ $ps = new PseudoClass();
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ret = $ps->elementMatches('only-child', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('only-child', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'c');
+ $ret = $ps->elementMatches('only-child', $ele, $root);
+ $this->assertTrue($ret);
+ }
+ public function testLastOfType() {
+ $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>';
+ $ps = new PseudoClass();
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('last-of-type', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $nl = $root->getElementsByTagName('d');
+
+ $ret = $ps->elementMatches('last-of-type', $nl->item(0), $root);
+ $this->assertFalse($ret);
+
+ $ret = $ps->elementMatches('last-of-type', $nl->item(1), $root);
+ $this->assertTrue($ret);
+ }
+ public function testFirstOftype() {
+ $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>';
+ $ps = new PseudoClass();
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('first-of-type', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $nl = $root->getElementsByTagName('d');
+
+ $ret = $ps->elementMatches('first-of-type', $nl->item(0), $root);
+ $this->assertTrue($ret);
+
+ $ret = $ps->elementMatches('first-of-type', $nl->item(1), $root);
+ $this->assertFalse($ret);
+ }
+ public function testOnlyOfType() {
+ $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>';
+ $ps = new PseudoClass();
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('only-of-type', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $nl = $root->getElementsByTagName('d');
+
+ $ret = $ps->elementMatches('only-of-type', $nl->item(0), $root);
+ $this->assertFalse($ret);
+
+ $ret = $ps->elementMatches('only-of-type', $nl->item(1), $root);
+ $this->assertFalse($ret);
+ }
+ public function testNthLastChild() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><b/><c/><d/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // 2n + 1 -- Every odd row, from the last element.
+ $i = 0;
+ $expects = array('b', 'd');
+ $j = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-last-child', $n, $root, '2n+1');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
+ }
+ }
+ $this->assertEquals(10, $i, '2n+1 is ten items.');
+
+ // 3 (0n+3) -- third from the end (b).
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-last-child', $n, $root, '3');
+ if ($res) {
+ ++$i;
+ $this->assertEquals('b', $n->tagName);
+ }
+ }
+ $this->assertEquals(1, $i);
+
+ // -n+3: Last three elements.
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-last-child', $n, $root, '-n+3');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(3, $i);
+ }
+ public function testNthChild() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><b/><c/><d/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // 2n + 1 -- Every odd row.
+ $i = 0;
+ $expects = array('a', 'c');
+ $j = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '2n+1');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
+ }
+ }
+ $this->assertEquals(10, $i, '2n+1 is ten items.');
+
+ // Odd
+ $i = 0;
+ $expects = array('a', 'c');
+ $j = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, 'odd');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
+ }
+ }
+ $this->assertEquals(10, $i, '2n+1 is ten items.');
+
+ // 2n + 0 -- every even row
+ $i = 0;
+ $expects = array('b', 'd');
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '2n');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, 'Expected a or c, got ' . $name);
+ }
+ }
+ $this->assertEquals(10, $i, '2n+0 is ten items.');
+
+ // Even (2n)
+ $i = 0;
+ $expects = array('b', 'd');
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, 'even');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, 'Expected a or c, got ' . $name);
+ }
+ }
+ $this->assertEquals(10, $i, ' even is ten items.');
+
+ // 4n - 1 == 4n + 3
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '4n-1');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertEquals('c', $name, 'Expected c, got ' . $name);
+ }
+ }
+ $this->assertEquals(5, $i);
+
+ // 6n - 1
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '6n-1');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(3, $i);
+
+ // 6n + 1
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '6n+1');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(4, $i);
+
+ // 26n - 1
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '26n-1');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(0, $i);
+
+ // 0n + 0 -- spec says this is always FALSE.
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '0n+0');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(0, $i);
+
+ // 3 (0n+3)
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '3');
+ if ($res) {
+ ++$i;
+ $this->assertEquals('c', $n->tagName);
+ }
+ }
+ $this->assertEquals(1, $i);
+
+ // -n+3: First three elements.
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '-n+3');
+ if ($res) {
+ ++$i;
+ //$this->assertEquals('c', $n->tagName);
+ }
+ }
+ $this->assertEquals(3, $i);
+
+ // BROKEN RULES -- these should always fail to match.
+
+ // 6n + 7;
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-child', $n, $root, '6n+7');
+ if ($res) {
+ ++$i;
+ }
+ }
+ $this->assertEquals(0, $i);
+
+ }
+ public function testEven() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><b/><c/><d/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ $i = 0;
+ $expects = array('b', 'd');
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('even', $n, $root);
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, 'Expected a or c, got ' . $name);
+ }
+ }
+ $this->assertEquals(10, $i, ' even is ten items.');
+ }
+ public function testOdd() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><b/><c/><d/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Odd
+ $i = 0;
+ $expects = array('a', 'c');
+ $j = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('odd', $n, $root);
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
+ }
+ }
+ $this->assertEquals(10, $i, 'Ten odds.');
+ }
+ public function testNthOfTypeChild() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><a/><a/><a/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Odd
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-of-type', $n, $root, '2n+1');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertEquals('a', $name);
+ }
+ }
+ $this->assertEquals(10, $i, 'Ten odds.');
+
+ // Fun with ambiguous pseudoclasses:
+ // This emulates the selector 'root > :nth-of-type(2n+1)'
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= '<a/><b/><c/><a/><a/><a/>';
+ $xml .= '</root>';
+
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Odd
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth-of-type', $n, $root, '2n+1');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ }
+ }
+ // THis should be: 2 x a + 1 x b + 1 x c = 4
+ $this->assertEquals(4, $i, 'Four odds.');
+ }
+ public function testNthLastOfTypeChild() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= '<a/><a/><OOPS/><a/><a/>';
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Third from beginning is second from last.
+ $third = $nl->item(2);
+ $res = $ps->elementMatches('nth-last-of-type', $third, $root, '3');
+ $this->assertFalse($res);
+
+ $first = $nl->item(0);
+ $res = $ps->elementMatches('nth-last-of-type', $first, $root, '3');
+ $this->assertFalse($res);
+
+ $last = $nl->item(3);
+ $res = $ps->elementMatches('nth-last-of-type', $last, $root, '3');
+ $this->assertFalse($res);
+
+ // Second from start is 3rd from last
+ $second = $nl->item(1);
+ $res = $ps->elementMatches('nth-last-of-type', $second, $root, '3');
+ $this->assertTrue($res);
+
+ }
+ public function testLink() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><a href="foo"><b hreff="bar">test</b></a><c/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'c');
+ $ret = $ps->elementMatches('link', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('link', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'b');
+ $ret = $ps->elementMatches('link', $ele, $root);
+ $this->assertFalse($ret);
+ }
+ public function testRoot() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'q');
+ $ret = $ps->elementMatches('root', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'root');
+ $ret = $ps->elementMatches('root', $ele, $root);
+ $this->assertTrue($ret);
+ }
+ /* Deprecated and removed.
+ public function testXRoot() {
+ }
+ public function testXReset() {
+ }
+ */
+ public function testLt() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><a/><a/><a/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Odd
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('lt', $n, $root, '15');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ }
+ }
+ $this->assertEquals(15, $i, 'Less than or equal to 15.');
+ }
+ public function testGt() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><a/><a/><a/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ // Odd
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('gt', $n, $root, '15');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ }
+ }
+ $this->assertEquals(5, $i, 'Greater than the 15th element.');
+ }
+ public function testEq() {
+ $xml = '<?xml version="1.0"?><root>';
+ $xml .= str_repeat('<a/><b/><c/><a/>', 5);
+ $xml .= '</root>';
+
+ $ps = new PseudoClass();
+ list($ele, $root) = $this->doc($xml, 'root');
+ $nl = $root->childNodes;
+
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('eq', $n, $root, '15');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertEquals('c', $name);
+ }
+ }
+ $this->assertEquals(1, $i, 'The 15th element.');
+
+ $i = 0;
+ foreach ($nl as $n) {
+ $res = $ps->elementMatches('nth', $n, $root, '15');
+ if ($res) {
+ ++$i;
+ $name = $n->tagName;
+ $this->assertEquals('c', $name);
+ }
+ }
+ $this->assertEquals(1, $i, 'The 15th element.');
+ }
+
+ public function testAnyLink() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><a href="foo"><b hreff="bar">test</b></a><c/><d src="foo"/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'c');
+ $ret = $ps->elementMatches('any-link', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('any-link', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'd');
+ $ret = $ps->elementMatches('any-link', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'b');
+ $ret = $ps->elementMatches('any-link', $ele, $root);
+ $this->assertFalse($ret);
+ }
+ public function testLocalLink() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><a href="foo"><b href="http://example.com/bar">test</b></a><c/><d href="file://foo"/></root>';
+
+ list($ele, $root) = $this->doc($xml, 'c');
+ $ret = $ps->elementMatches('local-link', $ele, $root);
+ $this->assertFalse($ret);
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $ret = $ps->elementMatches('local-link', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'd');
+ $ret = $ps->elementMatches('local-link', $ele, $root);
+ $this->assertTrue($ret);
+
+ list($ele, $root) = $this->doc($xml, 'b');
+ $ret = $ps->elementMatches('local-link', $ele, $root);
+ $this->assertFalse($ret);
+ }
+ public function testScope() {
+ $ps = new PseudoClass();
+ $xml = '<?xml version="1.0"?><root><a href="foo"><b>test</b></a></root>';
+
+ list($ele, $root) = $this->doc($xml, 'a');
+ $node = $ele->childNodes->item(0);
+ $ret = $ps->elementMatches('scope', $node, $ele);
+ $this->assertFalse($ret);
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php b/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php
new file mode 100644
index 0000000..01af591
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php
@@ -0,0 +1,1439 @@
+<?php
+/** @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Token;
+use \QueryPath\CSS\QueryPathEventHandler;
+use \QueryPath\CSS\Parser;
+use \QueryPath\CSS\EventHandler;
+
+/**
+ * Tests for QueryPathEventHandler class.
+ * @ingroup querypath_tests
+ * @group deprecated
+ */
+class QueryPathEventHandlerTest extends TestCase {
+
+
+ var $xml = '<?xml version="1.0" ?>
+ <html>
+ <head>
+ <title>This is the title</title>
+ </head>
+ <body>
+ <div id="one">
+ <div id="two" class="class-one">
+ <div id="three">
+ Inner text.
+ </div>
+ </div>
+ </div>
+ <span class="class-two">Nada</span>
+ <p><p><p><p><p><p><p class="Odd"><p>8</p></p></p></p></p></p></p></p>
+ <ul>
+ <li class="Odd" id="li-one">Odd</li>
+ <li class="even" id="li-two">Even</li>
+ <li class="Odd" id="li-three">Odd</li>
+ <li class="even" id="li-four">Even</li>
+ <li class="Odd" id="li-five">Odd</li>
+ <li class="even" id="li-six">Even</li>
+ <li class="Odd" id="li-seven">Odd</li>
+ <li class="even" id="li-eight">Even</li>
+ <li class="Odd" id="li-nine">Odd</li>
+ <li class="even" id="li-ten">Even</li>
+ </ul>
+ </body>
+ </html>
+ ';
+
+ private function firstMatch($matches) {
+ $matches->rewind();
+ return $matches->current();
+ }
+ private function nthMatch($matches, $n = 0) {
+ foreach ($matches as $m) {
+ if ($matches->key() == $n) return $m;
+ }
+ }
+
+ public function testGetMatches() {
+ // Test root element:
+ $xml = '<?xml version="1.0" ?><test><inside/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test handing it a DOM Document
+ $handler = new QueryPathEventHandler($doc);
+ $matches = $handler->getMatches();
+ $this->assertTrue($matches->count() == 1);
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('test', $match->tagName);
+
+ // Test handling single element
+ $root = $doc->documentElement;
+ $handler = new QueryPathEventHandler($root);
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('test', $match->tagName);
+
+ // Test handling a node list
+ $eles = $doc->getElementsByTagName('inside');
+ $handler = new QueryPathEventHandler($eles);
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('inside', $match->tagName);
+
+ // Test handling an array of elements
+ $eles = $doc->getElementsByTagName('inside');
+ $array = array();
+ foreach ($eles as $ele) $array[] = $ele;
+ $handler = new QueryPathEventHandler($array);
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('inside', $match->tagName);
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testEmptySelector() {
+ $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Basic test
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('');
+ $matches = $handler->getMatches();
+ $this->assertEquals(0, $matches->count());
+ }
+
+ public function testElementNS() {
+ $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Basic test
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('t|inside');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('t:inside', $match->tagName);
+
+ // Basic test
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('t|test');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('t:test', $match->tagName);
+ }
+
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testFailedElementNS() {
+ $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('myns\:mytest');
+ }
+
+ public function testElement() {
+ $xml = '<?xml version="1.0" ?><test><inside id="first"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Basic test
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('inside', $match->tagName);
+
+ $doc = new \DomDocument();
+ $doc->loadXML($this->xml);
+
+ // Test getting nested
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('div');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('div', $match->tagName);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test getting a list
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('li');
+ $matches = $handler->getMatches();
+ $this->assertEquals(10, $matches->count());
+ $match = $this->firstMatch($matches);
+ //$this->assertEquals('div', $match->tagName);
+ $this->assertEquals('li-one', $match->getAttribute('id'));
+
+ // Test getting the root element
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('html');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('html', $match->tagName);
+ }
+
+ public function testElementId() {
+ // Test root element:
+ $xml = '<?xml version="1.0" ?><test><inside id="first"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#first');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('inside', $match->tagName);
+
+ // Test a search with restricted scope:
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside#first');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('inside', $match->tagName);
+
+ }
+
+ public function testAnyElementInNS() {
+ $xml = '<?xml version="1.0" ?><ns1:test xmlns:ns1="urn:foo/bar"><ns1:inside/>Text<ns1:inside/></ns1:test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test handing it a DOM Document
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('ns1|*');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(3, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('ns1:test', $match->tagName);
+
+ // Test Issue #30:
+ $xml = '<?xml version="1.0" ?>
+ <ns1:test xmlns:ns1="urn:foo/bar">
+ <ns1:inside>
+ <ns1:insideInside>Test</ns1:insideInside>
+ </ns1:inside>
+ </ns1:test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('ns1|test>*');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('ns1:inside', $match->tagName);
+ }
+
+ public function testAnyElement() {
+ $xml = '<?xml version="1.0" ?><test><inside/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test handing it a DOM Document
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('*');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(3, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('test', $match->tagName);
+
+ $doc = new \DomDocument();
+ $doc->loadXML($this->xml);
+
+ // Test handing it a DOM Document
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#two *');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('three', $match->getAttribute('id'));
+
+ // Regression for issue #30
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#one>*');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(1, $matches->count(), 'Should match just top div.');
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('two', $match->getAttribute('id'), 'Should match ID #two');
+ }
+
+ public function testElementClass() {
+ $xml = '<?xml version="1.0" ?><test><inside class="foo" id="one"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test basic class
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('.foo');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test class in element
+ $doc = new \DomDocument();
+ $doc->loadXML($this->xml);
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('li.Odd');
+ $matches = $handler->getMatches();
+ $this->assertEquals(5, $matches->count());
+ $match = $this->nthMatch($matches, 4);
+ $this->assertEquals('li-nine', $match->getAttribute('id'));
+
+ // Test ID/class combo
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('.Odd#li-nine');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('li-nine', $match->getAttribute('id'));
+
+ }
+
+ public function testDirectDescendant() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <inside class="foo" id="one"/>
+ Text
+ <inside id="two">
+ <inside id="inner-one"/>
+ </inside>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test direct descendent
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('test > inside');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->nthMatch($matches, 1);
+ $this->assertEquals('two', $match->getAttribute('id'));
+
+ }
+
+ public function testAttribute() {
+ $xml = '<?xml version="1.0" ?><test><inside id="one" name="antidisestablishmentarianism"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test match on attr name
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test broken form
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[@name]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test match on attr name and equals value
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name="antidisestablishmentarianism"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test match on containsInString
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name*="disestablish"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test match on beginsWith
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name^="anti"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test match on endsWith
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name$="ism"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test containsWithSpace
+ $xml = '<?xml version="1.0" ?><test><inside id="one" name="anti dis establishment arian ism"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name~="dis"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ // Test containsWithHyphen
+ $xml = '<?xml version="1.0" ?><test><inside id="one" name="en-us"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside[name|="us"]');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ }
+
+ public function testPseudoClassLang() {
+
+ $xml = '<?xml version="1.0" ?><test><inside lang="en-us" id="one"/>Text<inside/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':lang(en-us)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside:lang(en)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside:lang(us)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ $xml = '<?xml version="1.0" ?><test><inside lang="en-us" id="one"/>Text<inside lang="us" id="two"/></test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':lang(us)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->nthMatch($matches, 1);
+ $this->assertEquals('two', $match->getAttribute('id'));
+
+ $xml = '<?xml version="1.0" ?>
+ <test xmlns="http://aleph-null.tv/xml" xmlns:xml="http://www.w3.org/XML/1998/namespace">
+ <inside lang="en-us" id="one"/>Text
+ <inside xml:lang="en-us" id="two"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':lang(us)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $match = $this->nthMatch($matches, 1);
+ $this->assertEquals('two', $match->getAttribute('id'));
+ }
+
+ public function testPseudoClassEnabledDisabledChecked() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <inside enabled="enabled" id="one"/>Text
+ <inside disabled="disabled" id="two"/>
+ <inside checked="FOOOOO" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':enabled');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('one', $match->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':disabled');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('two', $match->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':checked()');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $match = $this->firstMatch($matches);
+ $this->assertEquals('three', $match->getAttribute('id'));
+ }
+
+ public function testPseudoClassLink() {
+ $xml = '<?xml version="1.0"?><a><b href="foo"/><c href="foo"/></a>';
+ $qp = qp($xml, ':link');
+ $this->assertEquals(2, $qp->size());
+ }
+
+ public function testPseudoClassXReset() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <inside enabled="enabled" id="one"/>Text
+ <inside disabled="disabled" id="two"/>
+ <inside checked="FOOOOO" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('inside');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $handler->find(':x-reset');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('test', $this->firstMatch($matches)->tagName);
+ }
+
+ public function testPseudoClassRoot() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <inside enabled="enabled" id="one"/>Text
+ <inside disabled="disabled" id="two"/>
+ <inside checked="FOOOOO" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+ $start = $doc->getElementsByTagName('inside');
+
+ // Start "deep in the doc" and traverse backward.
+ $handler = new QueryPathEventHandler($start);
+ $handler->find(':root');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('test', $this->firstMatch($matches)->tagName);
+ }
+
+ // Test removed so I can re-declare
+ // listPeerElements as private.
+ public function xtestListPeerElements() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four"/>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test full list
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#one');
+ $matches = $handler->getMatches();
+ $peers = $handler->listPeerElements($this->firstMatch($matches));
+ $this->assertEquals(6, count($peers));
+ }
+ /*
+ public function testChildAtIndex() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four"/>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test full list
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('test:child-at-index(1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->nthMatch($matches, 1)->getAttribute('id'));
+ }*/
+
+ public function testPseudoClassNthChild() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four"/>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test full list
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':root :even');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test restricted to specific element
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:even');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test restricted to specific element, odd this time
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:odd');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(odd)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(odd)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(2n+1)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(2n+1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(2n) (even)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(2n)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Not totally sure what should be returned here
+ // Test nth-child(-2n)
+ // $handler = new QueryPathEventHandler($doc);
+ // $handler->find('i:nth-child(-2n)');
+ // $matches = $handler->getMatches();
+ // $this->assertEquals(2, $matches->count());
+ // $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(2n-1) (odd, equiv to 2n + 1)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(2n-1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(4n) (every fourth row)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(4n)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('four', $this->nthMatch($matches, 0)->getAttribute('id'));
+
+ // Test nth-child(4n+1) (first of every four rows)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(4n+1)');
+ $matches = $handler->getMatches();
+ // Should match rows one and five
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('five', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // Test nth-child(1) (First row)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ // Test nth-child(0n-0) (Empty list)
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(0n-0)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(0, $matches->count());
+
+ // Test nth-child(-n+3) (First three lines)
+ // $handler = new QueryPathEventHandler($doc);
+ // $handler->find('i:nth-child(-n+3)');
+ // $matches = $handler->getMatches();
+ // $this->assertEquals(3, $matches->count());
+
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three">
+ <i class="odd" id="inner-one"/>
+ <i class="even" id="inner-two"/>
+ </i>
+ <i class="even" id="four"/>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test nested items.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-child(odd)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(4, $matches->count());
+ $matchIDs = array();
+ foreach ($matches as $m) {
+ $matchIDs[] = $m->getAttribute('id');
+ }
+// $matchIDs = sort($matchIDs);
+ $this->assertEquals(array('one', 'three', 'inner-one', 'five'), $matchIDs);
+ //$this->assertEquals('inner-one', $matches[3]->getAttribute('id'));
+
+ }
+ public function testPseudoClassOnlyChild() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:only-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="odd" id="two"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:only-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(0, $matches->count());
+ //$this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassOnlyOfType() {
+ // TODO: Added this late (it was missing in original test),
+ // and I'm not sure if the assumed behavior is correct.
+
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:only-of-type');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="odd" id="two"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:only-of-type');
+ $matches = $handler->getMatches();
+ $this->assertEquals(0, $matches->count());
+ }
+
+ public function testPseudoClassFirstChild() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four">
+ <i class="odd" id="inner-one"/>
+ <i class="even" id="inner-two"/>
+ <i class="odd" id="inner-three"/>
+ <i class="even" id="inner-four"/>
+ </i>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#four > i:first-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('inner-one', $this->firstMatch($matches)->getAttribute('id'));
+
+ // Test for two last children
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:first-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-one', $this->nthMatch($matches, 1)->getAttribute('id'));
+ }
+
+ public function testPseudoClassLastChild() {
+ //print '----' . PHP_EOL;
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four">
+ <i class="odd" id="inner-one"/>
+ <i class="even" id="inner-two"/>
+ <i class="odd" id="inner-three"/>
+ <i class="even" id="inner-four"/>
+ </i>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test single last child.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#four > i:last-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('inner-four', $this->nthMatch($matches, 0)->getAttribute('id'));
+
+ // Test for two last children
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:last-child');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-four', $this->nthMatch($matches, 0)->getAttribute('id'));
+ $this->assertEquals('five', $this->nthMatch($matches, 1)->getAttribute('id'));
+ }
+
+ public function testPseudoClassNthLastChild() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ <i class="even" id="four">
+ <i class="odd" id="inner-one"/>
+ <i class="even" id="inner-two"/>
+ <i class="odd" id="inner-three"/>
+ <i class="even" id="inner-four"/>
+ </i>
+ <i class="odd" id="five"/>
+ <e class="even" id="six"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#four > i:nth-last-child(odd)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-two', $this->nthMatch($matches, 0)->getAttribute('id'));
+ $this->assertEquals('inner-four', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ // According to spec, this should be last two elements.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#four > i:nth-last-child(-1n+2)');
+ $matches = $handler->getMatches();
+ //print $this->firstMatch($matches)->getAttribute('id');
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-three', $this->nthMatch($matches, 0)->getAttribute('id'));
+ $this->assertEquals('inner-four', $this->nthMatch($matches, 1)->getAttribute('id'));
+ }
+
+ public function testPseudoClassFirstOfType() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <n class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:first-of-type(odd)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassNthFirstOfType() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <n class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:first-of-type(1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassLastOfType() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <n class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:last-of-type(odd)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoNthClassLastOfType() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <n class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-last-of-type(1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id'));
+
+
+ // Issue #56: an+b not working.
+ $xml = '<?xml version="1.0"?>
+ <root>
+ <div>I am the first div.</div>
+ <div>I am the second div.</div>
+ <div>I am the third div.</div>
+ <div>I am the fourth div.</div>
+ <div id="five">I am the fifth div.</div>
+ <div id="six">I am the sixth div.</div>
+ <div id="seven">I am the seventh div.</div>
+ </root>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('div:nth-last-of-type(-n+3)');
+ $matches = $handler->getMatches();
+
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('five', $this->firstMatch($matches)->getAttribute('id'));
+
+ }
+
+ public function testPseudoClassEmpty() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <n class="odd" id="one"/>
+ <i class="even" id="two"></i>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('n:empty');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:empty');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassFirst() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:first');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassLast() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:last');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassGT() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:gt(1)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+ public function testPseudoClassLT() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ // Test alternate rows from the end.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:lt(3)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('one', $this->nthMatch($matches,0)->getAttribute('id'));
+ $this->assertEquals('two', $this->nthMatch($matches,1)->getAttribute('id'));
+ }
+ public function testPseudoClassNTH() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth(2)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:eq(2)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+ public function testPseudoClassNthOfType() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <i class="odd" id="one"/>
+ <i class="even" id="two"/>
+ <i class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('i:nth-of-type(2)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassFormElements() {
+ $form = array('text', 'radio', 'checkbox', 'button', 'password');
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <input type="%s" class="odd" id="one"/>
+ </test>';
+
+ foreach ($form as $item) {
+ $doc = new \DomDocument();
+ $doc->loadXML(sprintf($xml, $item));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find(':' . $item);
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+ }
+
+ public function testPseudoClassHeader() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <h1 class="odd" id="one"/>
+ <h2 class="even" id="two"/>
+ <h6 class="odd" id="three"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('test :header');
+ $matches = $handler->getMatches();
+ $this->assertEquals(3, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 2)->getAttribute('id'));
+ }
+
+ public function testPseudoClassContains() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <p id="one">This is text.</p>
+ <p id="two"><i>More text</i></p>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains(This is text.)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('* :contains(More text)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count(), 'Matches two instance of same text?');
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains("This is text.")');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count(), 'Quoted text matches unquoted pcdata');
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains(\\\'This is text.\\\')');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count(), 'One match for quoted string.');
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ // Test for issue #32
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains(text)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count(), 'Two matches for fragment of string.');
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ }
+
+ public function testPseudoClassContainsExactly() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <p id="one">This is text.</p>
+ <p id="two"><i>More text</i></p>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains(This is text.)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('* :contains(More text)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count(), 'Matches two instance of same text.');
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains("This is text.")');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count(), 'Quoted text matches unquoted pcdata');
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('p:contains(\\\'This is text.\\\')');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count(), 'One match for quoted string.');
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassHas() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <outer id="one">
+ <inner/>
+ </outer>
+ <outer id="two"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer:has(inner)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoClassNot() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <outer id="one">
+ <inner/>
+ </outer>
+ <outer id="two" class="notMe"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer:not(#one)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer:not(inner)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer:not(.notMe)');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testPseudoElement() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <outer id="one">Texts
+
+ More text</outer>
+ <outer id="two" class="notMe"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer::first-letter');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('T', $this->firstMatch($matches)->textContent);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('outer::first-line');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('Texts', $this->firstMatch($matches)->textContent);
+ }
+
+ public function testAdjacent() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <li id="one"/><li id="two"/><li id="three">
+ <li id="inner-one">
+ <li id="inner-inner-one"/>
+ <li id="inner-inner-one"/>
+ </li>
+ <li id="inner-two"/>
+ </li>
+ <li id="four"/>
+ <li id="five"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#one + li');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id'));
+
+ // Tell it to ignore whitespace nodes.
+ $doc->loadXML($xml, LIBXML_NOBLANKS);
+
+ // Test with whitespace sensitivity weakened.
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#four + li');
+ $matches = $handler->getMatches();
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('five', $this->firstMatch($matches)->getAttribute('id'));
+ }
+ public function testAnotherSelector() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <li id="one"/><li id="two"/><li id="three">
+ <li id="inner-one">
+ <li id="inner-inner-one"/>
+ <li id="inner-inner-one"/>
+ </li>
+ <li id="inner-two"/>
+ </li>
+ <li id="four"/>
+ <li id="five"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#one, #two');
+ $matches = $handler->getMatches();
+ //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL;
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('two', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ }
+ public function testSibling() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <li id="one"/><li id="two"/><li id="three">
+ <li id="inner-one">
+ <li id="inner-inner-one"/>
+ <il id="inner-inner-two"/>
+ <li id="dont-match-me"/>
+ </li>
+ <li id="inner-two"/>
+ </li>
+ <li id="four"/>
+ <li id="five"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#one ~ li');
+ $matches = $handler->getMatches();
+ //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL;
+ $this->assertEquals(4, $matches->count());
+ $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#two ~ li');
+ $matches = $handler->getMatches();
+ //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL;
+ $this->assertEquals(3, $matches->count());
+ //$this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('#inner-one > li ~ il');
+ $matches = $handler->getMatches();
+ //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL;
+ $this->assertEquals(1, $matches->count());
+ $this->assertEquals('inner-inner-two', $this->firstMatch($matches)->getAttribute('id'));
+ }
+
+ public function testAnyDescendant() {
+ $xml = '<?xml version="1.0" ?>
+ <test>
+ <li id="one"/><li id="two"/><li id="three">
+ <li id="inner-one" class="foo">
+ <li id="inner-inner-one" class="foo"/>
+ <il id="inner-inner-two"/>
+ <li id="dont-match-me"/>
+ </li>
+ <li id="inner-two"/>
+ </li>
+ <li id="four"/>
+ <li id="five"/>
+ </test>';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('*');
+ $matches = $handler->getMatches();
+ $this->assertEquals(11, $matches->count());
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('*.foo');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-inner-one', $this->nthMatch($matches, 1)->getAttribute('id'));
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('test > li *.foo');
+ $matches = $handler->getMatches();
+ $this->assertEquals(2, $matches->count());
+ $this->assertEquals('inner-inner-one', $this->nthMatch($matches, 1)->getAttribute('id'));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php b/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php
new file mode 100644
index 0000000..495f1f5
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php
@@ -0,0 +1,120 @@
+<?php
+namespace QueryPath\Tests;
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Selector,
+ \QueryPath\CSS\SimpleSelector,
+ \QueryPath\CSS\EventHandler;
+
+class SelectorTest extends TestCase {
+
+ protected function parse($selector) {
+ $handler = new \QueryPath\CSS\Selector();
+ $parser = new \QueryPath\CSS\Parser($selector, $handler);
+ $parser->parse();
+ return $handler;
+ }
+
+ public function testElement() {
+ $selector = $this->parse('test')->toArray();
+
+ $this->assertEquals(1, count($selector));
+ $this->assertEquals('test', $selector[0]['0']->element);
+ }
+
+ public function testElementNS() {
+ $selector = $this->parse('foo|test')->toArray();
+
+ $this->assertEquals(1, count($selector));
+ $this->assertEquals('test', $selector[0]['0']->element);
+ $this->assertEquals('foo', $selector[0]['0']->ns);
+ }
+
+ public function testId() {
+ $selector = $this->parse('#test')->toArray();
+
+ $this->assertEquals(1, count($selector));
+ $this->assertEquals('test', $selector[0][0]->id);
+ }
+
+ public function testClasses() {
+ $selector = $this->parse('.test')->toArray();
+
+ $this->assertEquals(1, count($selector));
+ $this->assertEquals('test', $selector[0][0]->classes[0]);
+
+ $selector = $this->parse('.test.foo.bar')->toArray();
+ $this->assertEquals('test', $selector[0][0]->classes[0]);
+ $this->assertEquals('foo', $selector[0][0]->classes[1]);
+ $this->assertEquals('bar', $selector[0][0]->classes[2]);
+
+ }
+
+ public function testAttributes() {
+ $selector = $this->parse('foo[bar=baz]')->toArray();
+ $this->assertEquals(1, count($selector));
+ $attrs = $selector[0][0]->attributes;
+
+ $this->assertEquals(1, count($attrs));
+
+ $attr = $attrs[0];
+ $this->assertEquals('bar', $attr['name']);
+ $this->assertEquals(EventHandler::isExactly, $attr['op']);
+ $this->assertEquals('baz', $attr['value']);
+
+ $selector = $this->parse('foo[bar=baz][size=one]')->toArray();
+ $attrs = $selector[0][0]->attributes;
+
+ $this->assertEquals('one', $attrs[1]['value']);
+ }
+
+ public function testAttributesNS() {
+ $selector = $this->parse('[myns|foo=bar]')->toArray();
+
+ $attr = $selector[0][0]->attributes[0];
+
+ $this->assertEquals('myns', $attr['ns']);
+ $this->assertEquals('foo', $attr['name']);
+ }
+
+ public function testPseudoClasses() {
+ $selector = $this->parse('foo:first')->toArray();
+ $pseudo = $selector[0][0]->pseudoClasses;
+
+ $this->assertEquals(1, count($pseudo));
+
+ $this->assertEquals('first', $pseudo[0]['name']);
+ }
+
+ public function testPseudoElements() {
+ $selector = $this->parse('foo::bar')->toArray();
+ $pseudo = $selector[0][0]->pseudoElements;
+
+ $this->assertEquals(1, count($pseudo));
+
+ $this->assertEquals('bar', $pseudo[0]);
+ }
+
+ public function testCombinators() {
+ // This implies *>foo
+ $selector = $this->parse('>foo')->toArray();
+
+ $this->assertEquals(SimpleSelector::directDescendant, $selector[0][1]->combinator);
+
+ // This will be a selector with three simples:
+ // 'bar'
+ // 'foo '
+ // '*>'
+ $selector = $this->parse('>foo bar')->toArray();
+ $this->assertNull($selector[0][0]->combinator);
+ $this->assertEquals(SimpleSelector::anyDescendant, $selector[0][1]->combinator);
+ $this->assertEquals(SimpleSelector::directDescendant, $selector[0][2]->combinator);
+ }
+
+ public function testIterator() {
+ $selector = $this->parse('foo::bar');
+
+ $iterator = $selector->getIterator();
+ $this->assertInstanceOf('\Iterator', $iterator);
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php b/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php
new file mode 100644
index 0000000..64a2261
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php
@@ -0,0 +1,23 @@
+<?php
+/** @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Token;
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class TokenTest extends TestCase {
+ public function testName() {
+
+ $this->assertEquals('character', (Token::name(0)));
+ $this->assertEquals('a legal non-alphanumeric character', (Token::name(99)));
+ $this->assertEquals('end of file', (Token::name(FALSE)));
+ $this->assertEquals(0, strpos(Token::name(22),'illegal character'));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php b/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php
new file mode 100644
index 0000000..bdb8533
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php
@@ -0,0 +1,51 @@
+<?php
+/** @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\DOMTraverser\Util;
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class UtilTest extends TestCase {
+ public function testRemoveQuotes() {
+ $this->assertEquals('foo', Util::removeQuotes('"foo"'));
+ $this->assertEquals('foo', Util::removeQuotes("'foo'"));
+ $this->assertEquals('"foo\'', Util::removeQuotes("\"foo'"));
+ $this->assertEquals('f"o"o', Util::removeQuotes('f"o"o'));
+
+ }
+ public function testParseAnB() {
+ // even
+ $this->assertEquals(array(2, 0), Util::parseAnB('even'));
+ // odd
+ $this->assertEquals(array(2, 1), Util::parseAnB('odd'));
+ // 5
+ $this->assertEquals(array(0, 5), Util::parseAnB('5'));
+ // +5
+ $this->assertEquals(array(0, 5), Util::parseAnB('+5'));
+ // n
+ $this->assertEquals(array(1, 0), Util::parseAnB('n'));
+ // 2n
+ $this->assertEquals(array(2, 0), Util::parseAnB('2n'));
+ // -234n
+ $this->assertEquals(array(-234, 0), Util::parseAnB('-234n'));
+ // -2n+1
+ $this->assertEquals(array(-2, 1), Util::parseAnB('-2n+1'));
+ // -2n + 1
+ $this->assertEquals(array(-2, 1), Util::parseAnB(' -2n + 1 '));
+ // +2n-1
+ $this->assertEquals(array(2, -1), Util::parseAnB('2n-1'));
+ $this->assertEquals(array(2, -1), Util::parseAnB('2n - 1'));
+ // -n + 3
+ $this->assertEquals(array(-1, 3), Util::parseAnB('-n+3'));
+
+ // Test invalid values
+ $this->assertEquals(array(0, 0), Util::parseAnB('obviously + invalid'));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php b/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php
new file mode 100644
index 0000000..238555e
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php
@@ -0,0 +1,1865 @@
+<?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;
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/EntitiesTest.php b/lib/querypath/test/Tests/QueryPath/EntitiesTest.php
new file mode 100644
index 0000000..62a3426
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/EntitiesTest.php
@@ -0,0 +1,54 @@
+<?php
+/** @file
+ * Tests for the QueryPath library.
+ * @author M Butcher <matt@aleph-null.tv>
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+require_once __DIR__ . '/TestCase.php';
+
+/**
+ * @ingroup querypath_tests
+ */
+class EntitiesTest extends TestCase {
+ public function testReplaceEntity() {
+ $entity = 'amp';
+ $this->assertEquals('38', \QueryPath\Entities::replaceEntity($entity));
+
+ $entity = 'lceil';
+ $this->assertEquals('8968', \QueryPath\Entities::replaceEntity($entity));
+ }
+
+ public function testReplaceAllEntities() {
+ $test = '<?xml version="1.0"?><root>&amp;&copy;&#38;& nothing.</root>';
+ $expect = '<?xml version="1.0"?><root>&#38;&#169;&#38;&#38; nothing.</root>';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+
+ $test = '&&& ';
+ $expect = '&#38;&#38;&#38; ';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+
+ $test = "&eacute;\n";
+ $expect = "&#233;\n";
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+ }
+
+ public function testReplaceHexEntities() {
+ $test = '&#xA9;';
+ $expect = '&#xA9;';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+ }
+
+ public function testQPEntityReplacement() {
+ $test = '<?xml version="1.0"?><root>&amp;&copy;&#38;& nothing.</root>';
+ /*$expect = '<?xml version="1.0"?><root>&#38;&#169;&#38;&#38; nothing.</root>';*/
+ // We get this because the DOM serializer re-converts entities.
+ $expect = '<?xml version="1.0"?>
+<root>&amp;&#xA9;&amp;&amp; nothing.</root>';
+
+ $qp = qp($test, NULL, array('replace_entities' => TRUE));
+ // Interestingly, the XML serializer converts decimal to hex and ampersands
+ // to &amp;.
+ $this->assertEquals($expect, trim($qp->xml()));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/ExtensionTest.php b/lib/querypath/test/Tests/QueryPath/ExtensionTest.php
new file mode 100644
index 0000000..5f98612
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/ExtensionTest.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Tests for the QueryPath library.
+ * @author M Butcher <matt@aleph-null.tv>
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/TestCase.php';
+require_once __DIR__ . '/../../../src/QueryPath/Extension.php';
+//require_once __DIR__ . '/../../../src/QueryPath.php';
+//require_once 'QueryPathTest.php';
+
+use \QueryPath\Extension;
+use \QueryPath\ExtensionRegistry;
+
+/**
+ *
+ */
+//define('self::DATA_FILE', 'test/data.xml');
+
+/**
+ * Run all of the usual tests, plus some extras, with some extensions loaded.
+ * @ingroup querypath_tests
+ * @group extension
+ */
+class QueryPathExtensionTest extends TestCase {
+
+ public static function setUpBeforeClass() {
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionOne');
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionTwo');
+ }
+
+ public function testExtensions() {
+ $this->assertNotNull(qp());
+ }
+
+ public function testHasExtension() {
+ $this->assertTrue(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionOne'));
+ }
+
+ public function testStubToe() {
+ $this->assertEquals(1, qp(self::DATA_FILE, 'unary')->stubToe()->top(':root > toe')->size());
+ }
+
+ public function testStuble() {
+ $this->assertEquals('arg1arg2', qp(self::DATA_FILE)->stuble('arg1', 'arg2'));
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testNoRegistry() {
+ ExtensionRegistry::$useRegistry = FALSE;
+ try {
+ qp(self::DATA_FILE)->stuble('arg1', 'arg2');
+ }
+ catch (\QueryPath\Exception $e) {
+ ExtensionRegistry::$useRegistry = TRUE;
+ throw $e;
+ }
+
+ }
+
+ public function testExtend() {
+ $this->assertFalse(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionThree'));
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionThree');
+ $this->assertTrue(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionThree'));
+ }
+
+ public function tearDown() {
+ ExtensionRegistry::$useRegistry = TRUE;
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testAutoloadExtensions() {
+ // FIXME: This isn't really much of a test.
+ ExtensionRegistry::autoloadExtensions(FALSE);
+ try {
+ qp()->stubToe();
+ }
+ catch (Exception $e) {
+ ExtensionRegistry::autoloadExtensions(TRUE);
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testCallFailure() {
+ qp()->foo();
+ }
+
+ // This does not (and will not) throw an exception.
+ // /**
+ // * @expectedException QueryPathException
+ // */
+ // public function testExtendNoSuchClass() {
+ // ExtensionRegistry::extend('StubExtensionFour');
+ // }
+
+}
+// Create a stub extension:
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionOne implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+
+ public function stubToe() {
+ $this->qp->top()->append('<toe/>')->end();
+ return $this->qp;
+ }
+}
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionTwo implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+ public function stuble($arg1, $arg2) {
+ return $arg1 . $arg2;
+ }
+}
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionThree implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+ public function stuble($arg1, $arg2) {
+ return $arg1 . $arg2;
+ }
+}
+
+//ExtensionRegistry::extend('StubExtensionOne');
+//ExtensionRegistry::extend('StubExtensionTwo');
diff --git a/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php b/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php
new file mode 100644
index 0000000..266003e
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Tests for the QueryPath library.
+ * @author M Butcher <matt@aleph-null.tv>
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/../TestCase.php';
+require_once 'src/QueryPath/Extension/QPXML.php';
+/**
+ * @ingroup querypath_tests
+ * @group extension
+ */
+class QPXMLTests extends TestCase {
+
+ protected $file = './test/advanced.xml';
+ public static function setUpBeforeClass() {
+ \QueryPath::enable('\QueryPath\Extension\QPXML');
+ }
+
+ public function testCDATA() {
+ $this->assertEquals('This is a CDATA section.', qp($this->file, 'first')->cdata());
+
+ $msg = 'Another CDATA Section';
+ $this->assertEquals($msg, qp($this->file, 'second')->cdata($msg)->top()->find('second')->cdata());
+ }
+
+ public function testComment(){
+ $this->assertEquals('This is a comment.', trim(qp($this->file, 'root')->comment()));
+ $msg = "Message";
+ $this->assertEquals($msg, qp($this->file, 'second')->comment($msg)->top()->find('second')->comment());
+ }
+
+ public function testProcessingInstruction() {
+ $this->assertEquals('This is a processing instruction.', trim(qp($this->file, 'third')->pi()));
+ $msg = "Message";
+ $this->assertEquals($msg, qp($this->file, 'second')->pi('qp', $msg)->top()->find('second')->pi());
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php b/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php
new file mode 100644
index 0000000..f0c5ed1
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Tests for the QueryPath library.
+ * @author M Butcher <matt@aleph-null.tv>
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+require_once 'src/QueryPath/Extension/QPXSL.php';
+require_once __DIR__ . '/../TestCase.php';
+/**
+ * @ingroup querypath_tests
+ * @extension
+ */
+class QPXSLTests extends TestCase {
+
+ protected $file = './test/advanced.xml';
+
+ public static function setUpBeforeClass() {
+ \QueryPath::enable('\QueryPath\Extension\QPXSL');
+ }
+ public function testXSLT() {
+ // XML and XSLT taken from http://us.php.net/manual/en/xsl.examples-collection.php
+ // and then modified to be *actually welformed* XML.
+ $orig = '<?xml version="1.0"?><collection>
+ <cd>
+ <title>Fight for your mind</title>
+ <artist>Ben Harper</artist>
+ <year>1995</year>
+ </cd>
+ <cd>
+ <title>Electric Ladyland</title>
+ <artist>Jimi Hendrix</artist>
+ <year>1997</year>
+ </cd>
+ </collection>';
+
+ $template = '<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:param name="owner" select="\'Nicolas Eliaszewicz\'"/>
+ <xsl:output method="html" encoding="iso-8859-1" indent="no"/>
+ <xsl:template match="collection">
+ <div>
+ Hey! Welcome to <xsl:value-of select="$owner"/>\'s sweet CD collection!
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+ <xsl:template match="cd">
+ <h1><xsl:value-of select="title"/></h1>
+ <h2>by <xsl:value-of select="artist"/> - <xsl:value-of select="year"/></h2>
+ <hr />
+ </xsl:template>
+ </xsl:stylesheet>
+ ';
+
+ $qp = qp($orig)->xslt($template);
+ $this->assertEquals(2, $qp->top('h1')->size(), 'Make sure that data was formatted');
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/OptionsTest.php b/lib/querypath/test/Tests/QueryPath/OptionsTest.php
new file mode 100644
index 0000000..d2efbc9
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/OptionsTest.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Tests for the QueryPath library.
+ * @author M Butcher <matt@aleph-null.tv>
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+require_once __DIR__ . '/TestCase.php';
+use \QueryPath\Options;
+
+/**
+ * @ingroup querypath_tests
+ */
+class OptionsTest extends TestCase {
+
+ public function testOptions() {
+ $expect = array('test1' => 'val1', 'test2' => 'val2');
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+
+ $results = Options::get();
+ $this->assertEquals($expect, $results);
+
+ $this->assertEquals('val1', $results['test1']);
+ }
+
+ public function testQPOverrideOrder() {
+ $expect = array('test1' => 'val3', 'test2' => 'val2');
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+ $qpOpts = qp(NULL, NULL, array('test1'=>'val3', 'replace_entities' => TRUE))->getOptions();
+
+ $this->assertEquals($expect['test1'], $qpOpts['test1']);
+ $this->assertEquals(TRUE, $qpOpts['replace_entities']);
+ $this->assertNull($qpOpts['parser_flags']);
+ $this->assertEquals($expect['test2'], $qpOpts['test2']);
+ }
+
+ public function testQPHas() {
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+ $this->assertTrue(Options::has('test1'));
+ $this->assertFalse(Options::has('test3'));
+ }
+ public function testQPMerge() {
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+ $options2 = array('test1' => 'val3', 'test4' => 'val4');
+
+ Options::set($options);
+ Options::merge($options2);
+
+ $results = Options::get();
+ $this->assertTrue(Options::has('test4'));
+ $this->assertEquals('val3', $results['test1']);
+ }
+
+}
diff --git a/lib/querypath/test/Tests/QueryPath/QueryPathTest.php b/lib/querypath/test/Tests/QueryPath/QueryPathTest.php
new file mode 100644
index 0000000..71fd225
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/QueryPathTest.php
@@ -0,0 +1,56 @@
+<?php
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/TestCase.php';
+//require_once __DIR__ . '/../../../src/qp.php';
+require_once __DIR__ . '/../../../src/QueryPath.php';
+
+class QueryPathTest extends TestCase {
+
+ public function testWith() {
+ $qp = \QueryPath::with(\QueryPath::XHTML_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+
+ }
+
+ public function testWithHTML() {
+ $qp = \QueryPath::with(\QueryPath::HTML_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+ public function testWithHTML5() {
+ $qp = \QueryPath::withHTML5(\QueryPath::HTML5_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+
+ public function testWithXML() {
+ $qp = \QueryPath::with(\QueryPath::XHTML_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+
+ public function testEnable() {
+ \QueryPath::enable('\QueryPath\Tests\DummyExtension');
+
+ $qp = \QueryPath::with(\QueryPath::XHTML_STUB);
+
+ $this->assertTrue($qp->grrrrrrr());
+
+ }
+
+}
+
+class DummyExtension implements \QueryPath\Extension {
+
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+
+ public function grrrrrrr() {
+ return TRUE;
+ }
+
+}
diff --git a/lib/querypath/test/Tests/QueryPath/TestCase.php b/lib/querypath/test/Tests/QueryPath/TestCase.php
new file mode 100644
index 0000000..7e212b0
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/TestCase.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @file
+ *
+ * The master test case.
+ */
+
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+//require_once __DIR__ . '/../../../src/qp.php';
+require __DIR__ . '/../../../vendor/autoload.php';
+
+class TestCase extends \PHPUnit_Framework_TestCase {
+ const DATA_FILE = 'test/data.xml';
+ public static function setUpBeforeClass(){
+ }
+
+ public function testFoo() {
+ // Placeholder. Why is PHPUnit emitting warnings about no tests?
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/XMLIshTest.php b/lib/querypath/test/Tests/QueryPath/XMLIshTest.php
new file mode 100644
index 0000000..c55810f
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/XMLIshTest.php
@@ -0,0 +1,63 @@
+<?php
+/** @file
+ * Tests for the QueryPath library.
+ *
+ *
+ * @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 QueryPath.
+ */
+
+/** */
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/TestCase.php';
+
+/**
+ * Test the XMLish functions of QueryPath.
+ *
+ * This uses a testing harness, XMLishMock, to test
+ * a protected method of QueryPath.
+ *
+ * @ingroup querypath_test
+ */
+class XMLishTest extends TestCase {
+ public function testXMLishMock() {
+ $tests = array(
+ 'this/is/a/path' => FALSE,
+ "this is just some plain\ntext with a line break." => FALSE,
+ '2 > 1' => FALSE,
+ '1 < 2' => FALSE,
+ //'1 < 2 > 1' => FALSE,
+ '<html/>' => TRUE,
+ '<?xml version="1.0"?><root/>' => TRUE,
+ '<tag/><tag/><tag/>' => TRUE, // It's not valid, but HTML parser will try it.
+ );
+ foreach ($tests as $test => $correct) {
+ $mock = new XMLishMock();
+ $this->assertEquals($correct, $mock->exposedIsXMLish($test), "Testing $test");
+ }
+ }
+
+ public function testXMLishWithBrokenHTML() {
+ $html = '<div id="qp-top"><div class=header>Abe H. Rosenbloom Field<br></div> <p> Located in a natural bowl north of 10th Avenue, Rosenbloom Field was made possible by a gift from Virginia Whitney Rosenbloom \'36 and Abe H. Rosenbloom \'34. The Pioneers observed the occasion of the field\'s dedication on Oct. 4, 1975, by defeating Carleton 36-26. Rosenbloom Field has a seating capacity of 1,500. <br> <br> A former member of the Grinnell Advisory Board and other college committees, Abe Rosenbloom played football at Grinnell from 1931 to 1933. He played guard and was one of the Missouri Valley Conference\'s smallest gridders (5\'6" and 170 pounds). He averaged more than 45 minutes a game playing time during a 24-game varsity career and was named to the Des Moines Register\'s all-Missouri Valley Conference squad in 1932 and 1933. <br> <br> On the south side of the field, a memorial recalls the 100th anniversary of the first intercollegiate football game played west of the Mississippi. The game took place on the Grinnell campus on Nov. 16, 1889. On the north side, a marker commemorates the first 50 years of football in the west, and recalls the same game, played in 1889, Grinnell College vs. the University of Iowa. Grinnell won, 24-0. </p></div>';
+ $mock = new XMLishMock();
+ $this->assertEquals(TRUE, $mock->exposedIsXMLish($html), "Testing broken HTML");
+ }
+
+}
+
+/**
+ * A testing class for XMLish tests.
+ *
+ * @ingroup querypath_tests
+ */
+class XMLishMock extends \QueryPath\DOMQuery {
+ public function exposedIsXMLish($str) {
+ return $this->isXMLish($str);
+ }
+}