Joey Mazzarelli avatar Joey Mazzarelli committed ef8e824

rewrote grammar, implemented attributes, lots of tests

Comments (0)

Files changed (15)

src/php/Trekel/Node/Attribute.php

     return $this->attributeId;
   }
 
+  protected function getData ($node) {
+    if (substr($this->attributeId, -2) == "()") {
+      $method = substr($this->attributeId, 0, -2);
+      return $node->$method();
+    } else {
+      return $node->{$this->attributeId};
+    }
+  }
+
 }

src/php/Trekel/Node/Attribute/Contains.php

 <?php
 
 class Trekel_Node_Attribute_Contains extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    if (strpos($data, $value) !== false) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
+
 }// end Trekel_Node_Attribute_Contains

src/php/Trekel/Node/Attribute/EndsWith.php

 <?php
 
 class Trekel_Node_Attribute_EndsWith extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    $expectedPos = strlen($data) - strlen($value);
+
+    if (strpos($data, $value) === $expectedPos) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
+
 }// end Trekel_Node_Attribute_EndsWith

src/php/Trekel/Node/Attribute/Equals.php

 <?php
 
 class Trekel_Node_Attribute_Equals extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    if ($data == $value) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
 }// end Trekel_Node_Attribute_Equals

src/php/Trekel/Node/Attribute/Exists.php

 <?php
 
 class Trekel_Node_Attribute_Exists extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    if ($data) {
+      $this->matchNode($node);
+    }
+    return $this->getMatches();
+  }
 }// end Trekel_Node_Attribute_Exists

src/php/Trekel/Node/Attribute/NotEquals.php

 <?php
 
 class Trekel_Node_Attribute_NotEquals extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    if ($data != $value) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
 }// end Trekel_Node_Attribute_NotEquals

src/php/Trekel/Node/Attribute/Regex.php

+<?php
+
+class Trekel_Node_Attribute_Regex extends Trekel_Node_Attribute {
+
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    if (preg_match($value, $data)) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
+
+}// end Trekel_Node_Attribute_Regex

src/php/Trekel/Node/Attribute/StartsWith.php

 <?php
 
 class Trekel_Node_Attribute_StartsWith extends Trekel_Node_Attribute {
-  public function reduce ($node, $idx, $total) { return array(); }
+  public function reduce ($node, $idx, $total) {
+    $this->initMatches();
+    $data = $this->getData($node);
+    $value = $this->getValue();
+
+    if (strpos($data, $value) === 0) {
+      $this->matchNode($node);
+    }
+
+    return $this->getMatches();
+  }
 }// end Trekel_Node_Attribute_StartsWith

src/php/Trekel/Node/Selector.php

 <?php
 
-abstract class Trekel_Node_Selector extends Trekel_Node {
-
-  public function coerce ($type) {
-    if (get_class($this) == $type) {
-      return $this;
-    } else {
-      $obj = new $type();
-      $obj->setIdentifier($this->getIdentifier());
-      $obj->setChild($this->getChild());
-      return $obj;
-    }
-  }
-
-
-}
+abstract class Trekel_Node_Selector extends Trekel_Node { }

src/php/Trekel/Node/Selector/Sibling.php

     if ($parent = $node->getParent()) {
       $children = $parent->getChildren();
       $hash = spl_object_hash($node);
-      foreach ($children as $child) {
-        if (spl_object_hash($child) != $hash) {
-          $this->matchType($child);
+      while ($child = array_shift($children)) {
+        if (spl_object_hash($child) == $hash) {
+          break;
         }
       }
+      while ($child = array_shift($children)) {
+        $this->matchType($child);
+      }
     }
     return $this->getMatches();
   }

src/phplemon/Trekel/Parser.y

   private $tree = null;
 
   public function setSelector ($selector) {
-    $this->selector = $selector;
+    $this->selector = trim($selector);
   }
 
   public function parse () {
-    $lexer = new Trekel_Lexer($this->selector);
-    while ($lexer->yylex()) {
-      $this->column = $lexer->column;
-      $this->doParse($lexer->token, $lexer->value);
+    if ($this->selector) {
+      $lexer = new Trekel_Lexer($this->selector);
+      while ($lexer->yylex()) {
+        $this->column = $lexer->column;
+        $this->doParse($lexer->token, $lexer->value);
+      }
+      $this->doParse(0, 0);
     }
-    $this->doParse(0, 0);
     return $this->tree;
   } // end parse
 
 %token_prefix  T_
 %left ADJACENT CHILD SIBLING.
 
-// Just a selector statement
-start ::= selectors(IN). {
-  if (!IN) {
-    $this->tree = null;
-  } else {
-    $this->tree = IN->coerce('Trekel_Node_Selector_Descendant');
-  }
+start ::= starting_selector(A) selectors(B). {
+  if (B) { A->pushChild(B); }
+  $this->tree = (A);
 }
 
-// selector statement, but starting with the '>'.
-// This allows you to match the root node
-start ::= CHILD selectors(IN). {
-  if (!IN) {
-    $this->tree = null;
-  } else {
-    $this->tree = IN->coerce('Trekel_Node_Selector_Child');
-  }
+selectors(OUT) ::= selectors(A) selector(B). {
+  if (A) { A->pushChild(B); OUT = A; }
+  else   { OUT = B; }
+}
+selectors(OUT) ::= . { OUT = null; }
+selector(OUT) ::= finder(A) filters(B). {
+  if (B) { A->pushChild(B); }
+  OUT = A;
 }
 
-// Build the recursion
-selectors(OUT) ::= selectors(A) selector(B). {
-  if (A) {
-    A->pushChild(B);
-    OUT = A;
-  } else {
-    OUT = B;
-  }
+starting_selector(OUT) ::= starting_finder(A) filters(B). {
+  if (B) { A->pushChild(B); }
+  OUT = A;
 }
-selectors(OUT) ::= . { OUT = null; }
 
-selector(OUT) ::= type_selector(IN). { OUT = IN; }
-selector(OUT) ::= attribute_selector(IN). { OUT = IN; }
-selector(OUT) ::= pseudo_selector(IN). { OUT = IN; }
-selector(OUT) ::= relational_selector(IN). { OUT = IN; }
-
-// Just some identifier.
-// In CSS, "div ul li a" would be a sequence of four type selectors
-type_selector(OUT) ::= IDENTIFIER(A). {
-  #echo "found type selector:";
-  #print_r(A);
-  #echo "\n";
+finder(OUT) ::= starting_finder(IN). { OUT = IN; }
+finder(OUT) ::= SIBLING IDENTIFIER(A). {
+  $desc = new Trekel_Node_Selector_Sibling();
+  $desc->setIdentifier(A);
+  OUT = $desc;
+}
+finder(OUT) ::= ADJACENT IDENTIFIER(A). {
+  $desc = new Trekel_Node_Selector_Adjacent();
+  $desc->setIdentifier(A);
+  OUT = $desc;
+}
+starting_finder(OUT) ::= CHILD IDENTIFIER(A) . {
+  $desc = new Trekel_Node_Selector_Child();
+  $desc->setIdentifier(A);
+  OUT = $desc;
+}
+starting_finder(OUT) ::= IDENTIFIER(A) . {
   $desc = new Trekel_Node_Selector_Descendant();
   $desc->setIdentifier(A);
   OUT = $desc;
 }
 
-// Same, but with attributes: div[id='header']
-attribute_selector(OUT) ::= IDENTIFIER(A) attribute(B). {
-  $desc = new Trekel_Node_Selector_Descendant();
-  $desc->setIdentifier(A);
-  $desc->setChild(B);
-  OUT = $desc;
+filters(OUT) ::= filters(A) filter(B). {
+  if (A) { A->pushChild(B); OUT = A; }
+  else   { OUT = B; }
+}
+filters(OUT) ::= . { OUT = null; }
+filter(OUT) ::= attribute_filter(IN). { OUT = IN; }
+filter(OUT) ::= pseudo_filter(IN). { OUT = IN; }
+
+// [<id>]
+attribute_filter(OUT) ::= LBRACKET property(ID) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_Exists();
+  $attr->setIdentifier(ID);
+  OUT = $attr;
 }
 
-// With pseudo classes, div:first
-pseudo_selector(OUT) ::= IDENTIFIER(A) pseudo(B). {
-  $desc = new Trekel_Node_Selector_Descendant();
-  $desc->setIdentifier(A);
-  $desc->setChild(B);
-  OUT = $desc;
+// [<id>="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) EQ string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_Equals();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
 }
 
-pseudo_selector(OUT) ::= IDENTIFIER(A) pseudo(B) attribute(C). {
-  $desc = new Trekel_Node_Selector_Descendant();
-  $desc->setIdentifier(A);
-  $desc->setChild(B);
-  B->setChild(C);
-  OUT = $desc;
+// [<id>!="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) NEQ string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_NotEquals();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
 }
 
-pseudo_selector(OUT) ::= IDENTIFIER(A) attribute(B) pseudo(C). {
-  $desc = new Trekel_Node_Selector_Descendant();
-  $desc->setIdentifier(A);
-  $desc->setChild(B);
-  B->setChild(C);
-  OUT = $desc;
+// [<id>^="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) STARTSWITH string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_StartsWith();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
 }
 
-pseudo(OUT) ::= PSEUDO(IN). {
+// [<id>$="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) ENDSWITH string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_EndsWith();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
+}
+
+// [<id>*="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) CONTAINS string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_Contains();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
+}
+
+// [<id>@="value"]
+attribute_filter(OUT) ::= LBRACKET property(ID) REGEX string(VAL) RBRACKET. {
+  $attr = new Trekel_Node_Attribute_Regex();
+  $attr->setIdentifier(ID);
+  $attr->setValue(VAL);
+  OUT = $attr;
+}
+
+// :something
+pseudo_filter(OUT) ::= PSEUDO(IN). {
   $type = substr(IN, 1);
   $node = Trekel_Node_Pseudo::create($type);
   if (!$node) {
   OUT = $node;
 }
 
-relational_selector(OUT) ::= selector(A) SIBLING selector(B). {
-  B = B->coerce('Trekel_Node_Selector_Sibling');
-  A->pushChild(B);
-  OUT = A;
-}
-relational_selector(OUT) ::= selector(A) ADJACENT selector(B). {
-  B = B->coerce('Trekel_Node_Selector_Adjacent');
-  A->pushChild(B);
-  OUT = A;
-}
-relational_selector(OUT) ::= selector(A) CHILD selector(B). {
-  B = B->coerce('Trekel_Node_Selector_Child');
-  A->pushChild(B);
-  OUT = A;
-}
-
-// [<id>]
-attribute(OUT) ::= LBRACKET IDENTIFIER(ID) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_Exists();
-  $attr->setIdentifier(ID);
-  OUT = $attr;
-}
-
-// [<id>="value"]
-attribute(OUT) ::= LBRACKET property(ID) EQ string(VAL) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_Equals();
-  $attr->setIdentifier(ID);
-  $attr->setValue(VAL);
-  OUT = $attr;
-}
-
-// [<id>!="value"]
-attribute(OUT) ::= LBRACKET property(ID) NEQ string(VAL) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_NotEquals();
-  $attr->setIdentifier(ID);
-  $attr->setValue(VAL);
-  OUT = $attr;
-}
-
-// [<id>^="value"]
-attribute(OUT) ::= LBRACKET property(ID) STARTSWITH string(VAL) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_StartsWith();
-  $attr->setIdentifier(ID);
-  $attr->setValue(VAL);
-  OUT = $attr;
-}
-
-// [<id>$="value"]
-attribute(OUT) ::= LBRACKET property(ID) ENDSWITH string(VAL) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_EndsWith();
-  $attr->setIdentifier(ID);
-  $attr->setValue(VAL);
-  OUT = $attr;
-}
-
-// [<id>*="value"]
-attribute(OUT) ::= LBRACKET property(ID) CONTAINS string(VAL) RBRACKET. {
-  $attr = new Trekel_Node_Attribute_Contains();
-  $attr->setIdentifier(ID);
-  $attr->setValue(VAL);
-  OUT = $attr;
-}
-
 // When the attribute is an identifier, use as an instance variable
-property(OUT) ::= IDENTIFIER(ID). {
-  OUT = ID;
-}
+property(OUT) ::= IDENTIFIER(ID). { OUT = ID; }
 
 // When the attribute is a method, use as a getter
-property(OUT) ::= GETTER(ID). {
-  OUT = ID;
-}
+property(OUT) ::= GETTER(ID). { OUT = ID; }
 
 // Strip of the quotes and just use the value
-string(OUT) ::= STRING(VAL). {
-  OUT = substr(VAL, 1, -1);
-}
+string(OUT) ::= STRING(VAL). { OUT = substr(VAL, 1, -1); }
 
 // This seems to be the most useful filetype for highlighting and indenting
 // vim: set ft=php et ts=2 sw=2:

src/plex/Trekel/Lexer.plex

   $this->token = Trekel_Parser::T_NEQ;
 }
 
+'@=' {
+  $this->updateColumn();
+  $this->token = Trekel_Parser::T_REGEX;
+}
+
 '>' {
   $this->updateColumn();
   $this->token = Trekel_Parser::T_CHILD;

src/test/php/Trekel/AttributeTest.php

+<?php
+/**
+ * Copyright 2010 Joey Mazzarelli. All rights reserved.
+ *
+ * Redistribution and use in source, with or without modification, is
+ * permitted provided that the following condition is met:
+ *
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JOEY MAZZARELLI ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL JOEY MAZZARELLI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of Joey Mazzarelli.
+ */
+
+require_once dirname(__FILE__) . '/TestCase.php';
+
+class Trekel_AttributeTest extends Trekel_TestCase {
+
+  public function testOne () {
+
+    $a = new Trekel_AttributeTest_A("foo");
+    $b = new Trekel_AttributeTest_B("bar");
+    $c1 = new Trekel_AttributeTest_C2a();
+    $c2 = new Trekel_AttributeTest_C2b("barf");
+    $a->addChild($b);
+    $a->addChild($c1);
+    $a->addChild($c2);
+
+    /**
+     * This is what this looks like graphically
+     *
+     *          A
+     *         /|\
+     *        / | \
+     *       B  C1 C2
+     */
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[name="foo"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $node = $nodes[0];
+    $this->assertEquals("Trekel_AttributeTest_A", get_class($node));
+    $this->assertEquals("foo", $node->getName());
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[name^="b"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(2, count($nodes));
+    $node = $nodes[0];
+    $this->assertEquals("Trekel_AttributeTest_B", get_class($node));
+    $this->assertEquals("bar", $node->getName());
+    $node = $nodes[1];
+    $this->assertEquals("Trekel_AttributeTest_C2b", get_class($node));
+    $this->assertEquals("barf", $node->getName());
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[name$="ar"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $node = $nodes[0];
+    $this->assertEquals("Trekel_AttributeTest_B", get_class($node));
+    $this->assertEquals("bar", $node->getName());
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[getName() *= "ar"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(2, count($nodes));
+    $node = $nodes[0];
+    $this->assertEquals("Trekel_AttributeTest_B", get_class($node));
+    $this->assertEquals("bar", $node->getName());
+    $node = $nodes[1];
+    $this->assertEquals("Trekel_AttributeTest_C2b", get_class($node));
+    $this->assertEquals("barf", $node->getName());
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[name]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(3, count($nodes));
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[getName()!="foo"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(3, count($nodes));
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[getName()@="/^b.+rf?/"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(2, count($nodes));
+
+
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*[getName()][name^="b"][name*="r"][name$="f"]');
+    $parseTree = $parser->parse();
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $node = $nodes[0];
+    $this->assertEquals("Trekel_AttributeTest_C2b", get_class($node));
+    $this->assertEquals("barf", $node->getName());
+  }
+
+} // end Trekel_AttributeTest
+
+class Trekel_AttributeTest_Base {
+  private $children = array();
+  private $parent;
+  public $name = "";
+  public function __construct ($name = "") { $this->name = $name; }
+  public function getChildren () { return $this->children; }
+  public function getParent () { return $this->parent; }
+  public function setParent ($parent) { $this->parent = $parent; }
+  public function getName () { return $this->name; }
+  public function addChild ($child) {
+    $child->setParent($this);
+    $this->children[] = $child;
+  }
+}
+class Trekel_AttributeTest_A extends Trekel_AttributeTest_Base { }
+class Trekel_AttributeTest_B extends Trekel_AttributeTest_Base { }
+class Trekel_AttributeTest_C1 extends Trekel_AttributeTest_Base { }
+class Trekel_AttributeTest_C2a extends Trekel_AttributeTest_C1 { }
+class Trekel_AttributeTest_C2b extends Trekel_AttributeTest_C1 { }
+

src/test/php/Trekel/PseudoTest.php

+<?php
+/**
+ * Copyright 2010 Joey Mazzarelli. All rights reserved.
+ *
+ * Redistribution and use in source, with or without modification, is
+ * permitted provided that the following condition is met:
+ *
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JOEY MAZZARELLI ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL JOEY MAZZARELLI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of Joey Mazzarelli.
+ */
+
+require_once dirname(__FILE__) . '/TestCase.php';
+
+class Trekel_PseudoTest extends Trekel_TestCase {
+
+  public function testOne () {
+
+    $a = new Trekel_PseudoTest_A();
+    $b = new Trekel_PseudoTest_B();
+    $c1 = new Trekel_PseudoTest_C2a();
+    $c2 = new Trekel_PseudoTest_C2b();
+    $a->addChild($b);
+    $a->addChild($c1);
+    $a->addChild($c2);
+
+    /**
+     * This is what this looks like graphically
+     *
+     *          A
+     *         /|\
+     *        / | \
+     *       B  C1 C2
+     */
+
+    // Get B
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_A *:first');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_B", get_class($nodes[0]));
+
+    // Get C2b
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_A *:last');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2b", get_class($nodes[0]));
+
+    // Get B and C2b
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_A *:odd');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(2, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_B", get_class($nodes[0]));
+    $this->assertEquals("Trekel_PseudoTest_C2b", get_class($nodes[1]));
+
+    // Get C2a
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_A *:even');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2a", get_class($nodes[0]));
+
+    // Get A
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*:first');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_A", get_class($nodes[0]));
+
+    // Get C2b
+    $parser = new Trekel_Parser();
+    $parser->setSelector('*:last');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2b", get_class($nodes[0]));
+
+    // Get C2a
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_C1:first');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2a", get_class($nodes[0]));
+
+    // Get C2b
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_C1:last');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2b", get_class($nodes[0]));
+
+    // Get C2b
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_Base Trekel_PseudoTest_C1:last');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2b", get_class($nodes[0]));
+
+    // Get C2a
+    $parser = new Trekel_Parser();
+    $parser->setSelector('Trekel_PseudoTest_A > Trekel_PseudoTest_C1:first');
+    $parseTree = $parser->parse();
+
+    $evaluator = new Trekel_Evaluator();
+    $evaluator->setParseTree($parseTree);
+    $evaluator->setRootNode($a);
+
+    $nodes = $evaluator->evaluate();
+    $this->assertEquals(1, count($nodes));
+    $this->assertEquals("Trekel_PseudoTest_C2a", get_class($nodes[0]));
+  }
+
+
+} // end Trekel_PseudoTest
+
+class Trekel_PseudoTest_Base {
+  private $children = array();
+  private $parent;
+  public function getChildren () {
+    return $this->children;
+  }
+  public function getParent () {
+    return $this->parent;
+  }
+  public function addChild ($child) {
+    $child->setParent($this);
+    $this->children[] = $child;
+  }
+  public function setParent ($parent) {
+    $this->parent = $parent;
+  }
+}
+class Trekel_PseudoTest_A extends Trekel_PseudoTest_Base { }
+class Trekel_PseudoTest_B extends Trekel_PseudoTest_Base { }
+class Trekel_PseudoTest_C1 extends Trekel_PseudoTest_Base { }
+class Trekel_PseudoTest_C2a extends Trekel_PseudoTest_C1 { }
+class Trekel_PseudoTest_C2b extends Trekel_PseudoTest_C1 { }
+

src/test/php/Trekel/SiblingTest.php

     $evaluator->setParseTree($parseTree);
     $evaluator->setRootNode($a);
 
-    // Make sure all three are siblings
+    // Make sure the latter two are siblings
     $parser = new Trekel_Parser();
     $parser->setSelector('* ~ *');
     $parseTree = $parser->parse();
     $evaluator->setRootNode($a);
 
     $nodes = $evaluator->evaluate();
-    $this->assertEquals(3, count($nodes));
+    $this->assertEquals(2, count($nodes));
 
     // Make sure there are two siblings
     $parser = new Trekel_Parser();
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.