Source for file css_selector.php

Documentation is available at css_selector.php

  1. <?php
  2.  
  3. /**
  4.  *    @package    SimpleTest
  5.  *    @subpackage    DomTestCase
  6.  *  @author     Perrick Penet <perrick@noparking.net>
  7.  *    @version    $Id: css_selector.php 1538 2007-06-08 20:37:35Z pp11 $
  8.  */
  9.  
  10. /**
  11.  * CssSelector
  12.  * 
  13.  * Allow to navigate a DOM with CSS selector.
  14.  *
  15.  * based on getElementsBySelector version 0.4 - Simon Willison, 2003-03-25
  16.  * http://simon.incutio.com/archive/2003/03/25/getElementsBySelector
  17.  *
  18.  * derived from sfDomCssSelector Id 3053 (Symfony version 1.0.2) - Fabien Potencier, 2006-12-16
  19.  * http://www.symfony-project.com/api/symfony/util/sfDomCssSelector.html
  20.  *
  21.  * @param DomDocument $dom 
  22.  *
  23.  */
  24.  
  25. class CssSelector {
  26.   protected $dom = null;
  27.  
  28.   public function __construct($dom)
  29.   {
  30.     $this->dom = $dom;
  31.   }
  32.  
  33.   public function getTexts($selector)
  34.   {
  35.     $texts array();
  36.     foreach ($this->getElements($selectoras $element)
  37.     {
  38.       $texts[$element->nodeValue;
  39.     }
  40.  
  41.     return $texts;
  42.   }
  43.  
  44.   public function getElements($selector)
  45.   {
  46.     $all_nodes array();
  47.     foreach ($this->tokenize_selectors($selectoras $selector)
  48.     {
  49.       $nodes array($this->dom);
  50.       foreach ($this->tokenize($selectoras $token)
  51.       {
  52.         $combinator $token['combinator'];
  53.         $token trim($token['name']);
  54.         $pos strpos($token'#');
  55.         if (false !== $pos && preg_match('/^[A-Za-z0-9]*$/'substr($token0$pos)))
  56.         {
  57.           // Token is an ID selector
  58.           $tagName substr($token0$pos);
  59.           $id substr($token$pos 1);
  60.           $xpath new DomXPath($this->dom);
  61.           $element $xpath->query(sprintf("//*[@id = '%s']"$id))->item(0);
  62.           if (!$element || ($tagName && strtolower($element->nodeName!= $tagName))
  63.           {
  64.             // tag with that ID not found
  65.             return array();
  66.           }
  67.  
  68.           // Set nodes to contain just this element
  69.           $nodes array($element);
  70.  
  71.           continue// Skip to next token
  72.         }
  73.  
  74.         $pos strpos($token'.');
  75.         if (false !== $pos && preg_match('/^[A-Za-z0-9]*$/'substr($token0$pos)))
  76.         {
  77.           // Token contains a class selector
  78.           $tagName substr($token0$pos);
  79.           if (!$tagName)
  80.           {
  81.             $tagName '*';
  82.           }
  83.           $className substr($token$pos 1);
  84.  
  85.           // Get elements matching tag, filter them for class selector
  86.           $founds $this->getElementsByTagName($nodes$tagName$combinator);
  87.           $nodes array();
  88.           foreach ($founds as $found)
  89.           {
  90.             if (preg_match('/\b'.$className.'\b/'$found->getAttribute('class')))
  91.             {
  92.               $nodes[$found;
  93.             }
  94.           }
  95.  
  96.           continue// Skip to next token
  97.         }
  98.  
  99.         // Code to deal with attribute selectors
  100.         if (preg_match('/^(\w*)(\[.+\])$/'$token$matches))
  101.         {
  102.           $tagName $matches[1$matches[1'*';
  103.           preg_match_all('/
  104.             \[
  105.               (\w+)                 # attribute
  106.               ([=~\|\^\$\*]?)       # modifier (optional)
  107.               =?                    # equal (optional)
  108.               (
  109.                 "([^"]*)"           # quoted value (optional)
  110.                 |
  111.                 ([^\]]*)            # non quoted value (optional)
  112.               )
  113.             \]
  114.           /x'$matches[2]$matchesPREG_SET_ORDER);
  115.  
  116.           // Grab all of the tagName elements within current node
  117.           $founds $this->getElementsByTagName($nodes$tagName$combinator);
  118.           $nodes array();
  119.           foreach ($founds as $found)
  120.           {
  121.             $ok false;
  122.             foreach ($matches as $match)
  123.             {
  124.               $attrName $match[1];
  125.               $attrOperator $match[2];
  126.               $attrValue $match[4];
  127.  
  128.               switch ($attrOperator)
  129.               {
  130.                 case '='// Equality
  131.                   $ok $found->getAttribute($attrName== $attrValue;
  132.                   break;
  133.                 case '~'// Match one of space seperated words
  134.                   $ok preg_match('/\b'.preg_quote($attrValue'/').'\b/'$found->getAttribute($attrName));
  135.                   break;
  136.                 case '|'// Match start with value followed by optional hyphen
  137.                   $ok preg_match('/^'.preg_quote($attrValue'/').'-?/'$found->getAttribute($attrName));
  138.                   break;
  139.                 case '^'// Match starts with value
  140.                   $ok === strpos($found->getAttribute($attrName)$attrValue);
  141.                   break;
  142.                 case '$'// Match ends with value
  143.                   $ok $attrValue == substr($found->getAttribute($attrName)-strlen($attrValue));
  144.                   break;
  145.                 case '*'// Match ends with value
  146.                   $ok false !== strpos($found->getAttribute($attrName)$attrValue);
  147.                   break;
  148.                 default :
  149.                   // Just test for existence of attribute
  150.                   $ok $found->hasAttribute($attrName);
  151.               }
  152.  
  153.               if (false == $ok)
  154.               {
  155.                 break;
  156.               }
  157.             }
  158.  
  159.             if ($ok)
  160.             {
  161.               $nodes[$found;
  162.             }
  163.           }
  164.  
  165.           continue// Skip to next token
  166.         }
  167.  
  168.         if (preg_match('/^(\w*)(:first-child)$/'$token$matches)) {
  169.           $token $matches[1$matches[1'*';
  170.           $combinator $matches[2$matches[2'';
  171.         }
  172.         
  173.         // If we get here, token is JUST an element (not a class or ID selector)
  174.         $nodes $this->getElementsByTagName($nodes$token$combinator);
  175.       }
  176.  
  177.       foreach ($nodes as $node)
  178.       {
  179.         if (!$node->getAttribute('sf_matched'))
  180.         {
  181.           $node->setAttribute('sf_matched'true);
  182.           $all_nodes[$node;
  183.         }
  184.       }
  185.     }
  186.  
  187.     foreach ($all_nodes as $node)
  188.     {
  189.       $node->removeAttribute('sf_matched');
  190.     }
  191.  
  192.     return $all_nodes;
  193.   }
  194.  
  195.   protected function getElementsByTagName($nodes$tagName$combinator ' ')
  196.   {
  197.     $founds array();
  198.     foreach ($nodes as $node)
  199.     {
  200.       switch ($combinator)
  201.       {
  202.         case ' ':
  203.           foreach ($node->getElementsByTagName($tagNameas $element)
  204.           {
  205.             $founds[$element;
  206.           }
  207.           break;
  208.         case '>':
  209.           foreach ($node->childNodes as $element)
  210.           {
  211.             if ($tagName == $element->nodeName)
  212.             {
  213.               $founds[$element;
  214.             }
  215.           }
  216.           break;
  217.         case '+':
  218.             $element $node->nextSibling;
  219.             if ($element->nodeName == "#text"{
  220.                 $element $element->nextSibling;
  221.             }
  222.             if ($element && $tagName == $element->nodeName{
  223.                 $founds[$element;
  224.             }
  225.             break;
  226.         case ':first-child':
  227.           foreach ($node->getElementsByTagName($tagNameas $element{
  228.             if (count($founds== 0{
  229.               $founds[$element;
  230.             }
  231.           }
  232.           break;
  233.       }
  234.     }
  235.  
  236.     return $founds;
  237.   }
  238.  
  239.   protected function tokenize_selectors($selector)
  240.   {
  241.     // split tokens by , except in an attribute selector
  242.     $tokens array();
  243.     $quoted false;
  244.     $token '';
  245.     for ($i 0$max strlen($selector)$i $max$i++)
  246.     {
  247.       if (',' == $selector[$i&& !$quoted)
  248.       {
  249.         $tokens[trim($token);
  250.         $token '';
  251.       }
  252.       else if ('"' == $selector[$i])
  253.       {
  254.         $token .= $selector[$i];
  255.         $quoted $quoted false true;
  256.       }
  257.       else
  258.       {
  259.         $token .= $selector[$i];
  260.       }
  261.     }
  262.     if ($token)
  263.     {
  264.       $tokens[trim($token);
  265.     }
  266.  
  267.     return $tokens;
  268.   }
  269.  
  270.   protected function tokenize($selector)
  271.   {
  272.     // split tokens by space except if space is in an attribute selector
  273.     $tokens array();
  274.     $combinators array(' ''>''+');
  275.     $quoted false;
  276.     $token array('combinator' => ' ''name' => '');
  277.     for ($i 0$max strlen($selector)$i $max$i++)
  278.     {
  279.       if (in_array($selector[$i]$combinators&& !$quoted)
  280.       {
  281.         // remove all whitespaces around the combinator
  282.         $combinator $selector[$i];
  283.         while (in_array($selector[$i 1]$combinators))
  284.         {
  285.           if (' ' != $selector[++$i])
  286.           {
  287.             $combinator $selector[$i];
  288.           }
  289.         }
  290.  
  291.         $tokens[$token;
  292.         $token array('combinator' => $combinator'name' => '');
  293.       }
  294.       else if ('"' == $selector[$i])
  295.       {
  296.         $token['name'.= $selector[$i];
  297.         $quoted $quoted false true;
  298.       }
  299.       else
  300.       {
  301.         $token['name'.= $selector[$i];
  302.       }
  303.     }
  304.     if ($token['name'])
  305.     {
  306.       $tokens[$token;
  307.     }
  308.  
  309.     return $tokens;
  310.   }
  311. }

Documentation generated on Sun, 04 May 2008 09:21:24 -0500 by phpDocumentor 1.3.0