vendor/symfony/src/Symfony/Component/CssSelector/Node/FunctionNode.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of the Symfony package.
       
     5  *
       
     6  * (c) Fabien Potencier <fabien@symfony.com>
       
     7  *
       
     8  * For the full copyright and license information, please view the LICENSE
       
     9  * file that was distributed with this source code.
       
    10  */
       
    11 
       
    12 namespace Symfony\Component\CssSelector\Node;
       
    13 
       
    14 use Symfony\Component\CssSelector\Exception\ParseException;
       
    15 use Symfony\Component\CssSelector\XPathExpr;
       
    16 
       
    17 /**
       
    18  * FunctionNode represents a "selector:name(expr)" node.
       
    19  *
       
    20  * This component is a port of the Python lxml library,
       
    21  * which is copyright Infrae and distributed under the BSD license.
       
    22  *
       
    23  * @author Fabien Potencier <fabien@symfony.com>
       
    24  */
       
    25 class FunctionNode implements NodeInterface
       
    26 {
       
    27     static protected $unsupported = array('target', 'lang', 'enabled', 'disabled');
       
    28 
       
    29     protected $selector;
       
    30     protected $type;
       
    31     protected $name;
       
    32     protected $expr;
       
    33 
       
    34     /**
       
    35      * Constructor.
       
    36      *
       
    37      * @param NodeInterface $selector The XPath expression
       
    38      * @param string $type
       
    39      * @param string $name
       
    40      * @param XPathExpr $expr
       
    41      */
       
    42     public function __construct($selector, $type, $name, $expr)
       
    43     {
       
    44         $this->selector = $selector;
       
    45         $this->type = $type;
       
    46         $this->name = $name;
       
    47         $this->expr = $expr;
       
    48     }
       
    49 
       
    50     /**
       
    51      * {@inheritDoc}
       
    52      */
       
    53     public function __toString()
       
    54     {
       
    55         return sprintf('%s[%s%s%s(%s)]', __CLASS__, $this->selector, $this->type, $this->name, $this->expr);
       
    56     }
       
    57 
       
    58     /**
       
    59      * {@inheritDoc}
       
    60      * @throws ParseException When unsupported or unknown pseudo-class is found
       
    61      */
       
    62     public function toXpath()
       
    63     {
       
    64         $selPath = $this->selector->toXpath();
       
    65         if (in_array($this->name, self::$unsupported)) {
       
    66             throw new ParseException(sprintf('The pseudo-class %s is not supported', $this->name));
       
    67         }
       
    68         $method = '_xpath_'.str_replace('-', '_', $this->name);
       
    69         if (!method_exists($this, $method)) {
       
    70             throw new ParseException(sprintf('The pseudo-class %s is unknown', $this->name));
       
    71         }
       
    72 
       
    73         return $this->$method($selPath, $this->expr);
       
    74     }
       
    75 
       
    76     /**
       
    77      * undocumented function
       
    78      *
       
    79      * @param XPathExpr $xpath
       
    80      * @param mixed     $expr
       
    81      * @param Boolean   $last
       
    82      * @param Boolean   $addNameTest
       
    83      * @return XPathExpr
       
    84      */
       
    85     protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = true)
       
    86     {
       
    87         list($a, $b) = $this->parseSeries($expr);
       
    88         if (!$a && !$b && !$last) {
       
    89             // a=0 means nothing is returned...
       
    90             $xpath->addCondition('false() and position() = 0');
       
    91 
       
    92             return $xpath;
       
    93         }
       
    94 
       
    95         if ($addNameTest) {
       
    96             $xpath->addNameTest();
       
    97         }
       
    98 
       
    99         $xpath->addStarPrefix();
       
   100         if ($a == 0) {
       
   101             if ($last) {
       
   102                 $b = sprintf('last() - %s', $b);
       
   103             }
       
   104             $xpath->addCondition(sprintf('position() = %s', $b));
       
   105 
       
   106             return $xpath;
       
   107         }
       
   108 
       
   109         if ($last) {
       
   110             // FIXME: I'm not sure if this is right
       
   111             $a = -$a;
       
   112             $b = -$b;
       
   113         }
       
   114 
       
   115         if ($b > 0) {
       
   116             $bNeg = -$b;
       
   117         } else {
       
   118             $bNeg = sprintf('+%s', -$b);
       
   119         }
       
   120 
       
   121         if ($a != 1) {
       
   122             $expr = array(sprintf('(position() %s) mod %s = 0', $bNeg, $a));
       
   123         } else {
       
   124             $expr = array();
       
   125         }
       
   126 
       
   127         if ($b >= 0) {
       
   128             $expr[] = sprintf('position() >= %s', $b);
       
   129         } elseif ($b < 0 && $last) {
       
   130             $expr[] = sprintf('position() < (last() %s)', $b);
       
   131         }
       
   132         $expr = implode($expr, ' and ');
       
   133 
       
   134         if ($expr) {
       
   135             $xpath->addCondition($expr);
       
   136         }
       
   137 
       
   138         return $xpath;
       
   139         /* FIXME: handle an+b, odd, even
       
   140              an+b means every-a, plus b, e.g., 2n+1 means odd
       
   141              0n+b means b
       
   142              n+0 means a=1, i.e., all elements
       
   143              an means every a elements, i.e., 2n means even
       
   144              -n means -1n
       
   145              -1n+6 means elements 6 and previous */
       
   146     }
       
   147 
       
   148     /**
       
   149      * undocumented function
       
   150      *
       
   151      * @param XPathExpr $xpath
       
   152      * @param XPathExpr $expr
       
   153      * @return XPathExpr
       
   154      */
       
   155     protected function _xpath_nth_last_child($xpath, $expr)
       
   156     {
       
   157         return $this->_xpath_nth_child($xpath, $expr, true);
       
   158     }
       
   159 
       
   160     /**
       
   161      * undocumented function
       
   162      *
       
   163      * @param XPathExpr $xpath
       
   164      * @param XPathExpr $expr
       
   165      * @return XPathExpr
       
   166      */
       
   167     protected function _xpath_nth_of_type($xpath, $expr)
       
   168     {
       
   169         if ($xpath->getElement() == '*') {
       
   170             throw new ParseException('*:nth-of-type() is not implemented');
       
   171         }
       
   172 
       
   173         return $this->_xpath_nth_child($xpath, $expr, false, false);
       
   174     }
       
   175 
       
   176     /**
       
   177      * undocumented function
       
   178      *
       
   179      * @param XPathExpr $xpath
       
   180      * @param XPathExpr $expr
       
   181      * @return XPathExpr
       
   182      */
       
   183     protected function _xpath_nth_last_of_type($xpath, $expr)
       
   184     {
       
   185         return $this->_xpath_nth_child($xpath, $expr, true, false);
       
   186     }
       
   187 
       
   188     /**
       
   189      * undocumented function
       
   190      *
       
   191      * @param XPathExpr $xpath
       
   192      * @param XPathExpr $expr
       
   193      * @return XPathExpr
       
   194      */
       
   195     protected function _xpath_contains($xpath, $expr)
       
   196     {
       
   197         // text content, minus tags, must contain expr
       
   198         if ($expr instanceof ElementNode) {
       
   199             $expr = $expr->formatElement();
       
   200         }
       
   201 
       
   202         // FIXME: lower-case is only available with XPath 2
       
   203         //$xpath->addCondition(sprintf('contains(lower-case(string(.)), %s)', XPathExpr::xpathLiteral(strtolower($expr))));
       
   204         $xpath->addCondition(sprintf('contains(string(.), %s)', XPathExpr::xpathLiteral($expr)));
       
   205 
       
   206         // FIXME: Currently case insensitive matching doesn't seem to be happening
       
   207 
       
   208         return $xpath;
       
   209     }
       
   210 
       
   211     /**
       
   212      * undocumented function
       
   213      *
       
   214      * @param XPathExpr $xpath
       
   215      * @param XPathExpr $expr
       
   216      * @return XPathExpr
       
   217      */
       
   218     protected function _xpath_not($xpath, $expr)
       
   219     {
       
   220         // everything for which not expr applies
       
   221         $expr = $expr->toXpath();
       
   222         $cond = $expr->getCondition();
       
   223         // FIXME: should I do something about element_path?
       
   224         $xpath->addCondition(sprintf('not(%s)', $cond));
       
   225 
       
   226         return $xpath;
       
   227     }
       
   228 
       
   229     /**
       
   230      * Parses things like '1n+2', or 'an+b' generally, returning (a, b)
       
   231      *
       
   232      * @param mixed $s
       
   233      * @return array
       
   234      */
       
   235     protected function parseSeries($s)
       
   236     {
       
   237         if ($s instanceof ElementNode) {
       
   238             $s = $s->formatElement();
       
   239         }
       
   240 
       
   241         if (!$s || '*' == $s) {
       
   242             // Happens when there's nothing, which the CSS parser thinks of as *
       
   243             return array(0, 0);
       
   244         }
       
   245 
       
   246         if (is_string($s)) {
       
   247             // Happens when you just get a number
       
   248             return array(0, $s);
       
   249         }
       
   250 
       
   251         if ('odd' == $s) {
       
   252             return array(2, 1);
       
   253         }
       
   254 
       
   255         if ('even' == $s) {
       
   256             return array(2, 0);
       
   257         }
       
   258 
       
   259         if ('n' == $s) {
       
   260             return array(1, 0);
       
   261         }
       
   262 
       
   263         if (false === strpos($s, 'n')) {
       
   264             // Just a b
       
   265 
       
   266             return array(0, intval((string) $s));
       
   267         }
       
   268 
       
   269         list($a, $b) = explode('n', $s);
       
   270         if (!$a) {
       
   271             $a = 1;
       
   272         } elseif ('-' == $a || '+' == $a) {
       
   273             $a = intval($a.'1');
       
   274         } else {
       
   275             $a = intval($a);
       
   276         }
       
   277 
       
   278         if (!$b) {
       
   279             $b = 0;
       
   280         } elseif ('-' == $b || '+' == $b) {
       
   281             $b = intval($b.'1');
       
   282         } else {
       
   283             $b = intval($b);
       
   284         }
       
   285 
       
   286         return array($a, $b);
       
   287     }
       
   288 }