2010-03-31 07:42:18 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Symfony\Components\CssSelector\Node;
|
|
|
|
|
|
|
|
use Symfony\Components\CssSelector\SyntaxError;
|
|
|
|
use Symfony\Components\CssSelector\XPathExpr;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file is part of the symfony package.
|
|
|
|
*
|
|
|
|
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
|
|
|
*
|
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* FunctionNode represents a "selector:name(expr)" node.
|
|
|
|
*
|
|
|
|
* This component is a port of the Python lxml library,
|
|
|
|
* which is copyright Infrae and distributed under the BSD license.
|
|
|
|
*
|
|
|
|
* @package symfony
|
|
|
|
* @subpackage css_selector
|
|
|
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
|
|
|
*/
|
|
|
|
class FunctionNode implements NodeInterface
|
|
|
|
{
|
|
|
|
static protected $unsupported = array('target', 'lang', 'enabled', 'disabled');
|
|
|
|
|
|
|
|
protected $selector;
|
|
|
|
protected $type;
|
|
|
|
protected $name;
|
|
|
|
protected $expr;
|
|
|
|
|
|
|
|
public function __construct($selector, $type, $name, $expr)
|
|
|
|
{
|
|
|
|
$this->selector = $selector;
|
|
|
|
$this->type = $type;
|
|
|
|
$this->name = $name;
|
|
|
|
$this->expr = $expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __toString()
|
|
|
|
{
|
|
|
|
return sprintf('%s[%s%s%s(%s)]', __CLASS__, $this->selector, $this->type, $this->name, $this->expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function toXpath()
|
|
|
|
{
|
|
|
|
$sel_path = $this->selector->toXpath();
|
|
|
|
if (in_array($this->name, self::$unsupported))
|
|
|
|
{
|
|
|
|
throw new SyntaxError(sprintf("The pseudo-class %s is not supported", $this->name));
|
|
|
|
}
|
|
|
|
$method = '_xpath_'.str_replace('-', '_', $this->name);
|
|
|
|
if (!method_exists($this, $method))
|
|
|
|
{
|
|
|
|
throw new SyntaxError(sprintf("The pseudo-class %s is unknown", $this->name));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->$method($sel_path, $this->expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = true)
|
|
|
|
{
|
|
|
|
list($a, $b) = $this->parseSeries($expr);
|
|
|
|
if (!$a && !$b && !$last)
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// a=0 means nothing is returned...
|
2010-03-31 07:42:18 +01:00
|
|
|
$xpath->addCondition('false() and position() = 0');
|
|
|
|
|
|
|
|
return $xpath;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($addNameTest)
|
|
|
|
{
|
|
|
|
$xpath->addNameTest();
|
|
|
|
}
|
|
|
|
|
|
|
|
$xpath->addStarPrefix();
|
|
|
|
if ($a == 0)
|
|
|
|
{
|
|
|
|
if ($last)
|
|
|
|
{
|
|
|
|
$b = sprintf('last() - %s', $b);
|
|
|
|
}
|
|
|
|
$xpath->addCondition(sprintf('position() = %s', $b));
|
|
|
|
|
|
|
|
return $xpath;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($last)
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// FIXME: I'm not sure if this is right
|
2010-03-31 07:42:18 +01:00
|
|
|
$a = -$a;
|
|
|
|
$b = -$b;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($b > 0)
|
|
|
|
{
|
|
|
|
$b_neg = -$b;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$b_neg = sprintf('+%s', -$b);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($a != 1)
|
|
|
|
{
|
|
|
|
$expr = array(sprintf('(position() %s) mod %s = 0', $b_neg, $a));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$expr = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($b >= 0)
|
|
|
|
{
|
|
|
|
$expr[] = sprintf('position() >= %s', $b);
|
|
|
|
}
|
|
|
|
elseif ($b < 0 && $last)
|
|
|
|
{
|
|
|
|
$expr[] = sprintf('position() < (last() %s)', $b);
|
|
|
|
}
|
|
|
|
$expr = implode($expr, ' and ');
|
|
|
|
|
|
|
|
if ($expr)
|
|
|
|
{
|
|
|
|
$xpath->addCondition($expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $xpath;
|
2010-04-03 00:48:10 +01:00
|
|
|
/* FIXME: handle an+b, odd, even
|
|
|
|
an+b means every-a, plus b, e.g., 2n+1 means odd
|
|
|
|
0n+b means b
|
|
|
|
n+0 means a=1, i.e., all elements
|
|
|
|
an means every a elements, i.e., 2n means even
|
|
|
|
-n means -1n
|
|
|
|
-1n+6 means elements 6 and previous */
|
2010-03-31 07:42:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_nth_last_child($xpath, $expr)
|
|
|
|
{
|
|
|
|
return $this->_xpath_nth_child($xpath, $expr, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_nth_of_type($xpath, $expr)
|
|
|
|
{
|
|
|
|
if ($xpath->getElement() == '*')
|
|
|
|
{
|
|
|
|
throw new SyntaxError("*:nth-of-type() is not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->_xpath_nth_child($xpath, $expr, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_nth_last_of_type($xpath, $expr)
|
|
|
|
{
|
|
|
|
return $this->_xpath_nth_child($xpath, $expr, true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_contains($xpath, $expr)
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// text content, minus tags, must contain expr
|
2010-03-31 07:42:18 +01:00
|
|
|
if ($expr instanceof ElementNode)
|
|
|
|
{
|
|
|
|
$expr = $expr->formatElement();
|
|
|
|
}
|
|
|
|
$xpath->addCondition(sprintf('contains(css:lower-case(string(.)), %s)', XPathExpr::xpathLiteral(strtolower($expr))));
|
2010-04-03 00:48:10 +01:00
|
|
|
// FIXME: Currently case insensitive matching doesn't seem to be happening
|
2010-03-31 07:42:18 +01:00
|
|
|
|
|
|
|
return $xpath;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function _xpath_not($xpath, $expr)
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// everything for which not expr applies
|
2010-03-31 07:42:18 +01:00
|
|
|
$expr = $expr->toXpath();
|
|
|
|
$cond = $expr->getCondition();
|
2010-04-03 00:48:10 +01:00
|
|
|
// FIXME: should I do something about element_path?
|
2010-03-31 07:42:18 +01:00
|
|
|
$xpath->addCondition(sprintf('not(%s)', $cond));
|
|
|
|
|
|
|
|
return $xpath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parses things like '1n+2', or 'an+b' generally, returning (a, b)
|
|
|
|
protected function parseSeries($s)
|
|
|
|
{
|
|
|
|
if ($s instanceof ElementNode)
|
|
|
|
{
|
|
|
|
$s = $s->formatElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$s || $s == '*')
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// Happens when there's nothing, which the CSS parser thinks of as *
|
2010-03-31 07:42:18 +01:00
|
|
|
return array(0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_string($s))
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// Happens when you just get a number
|
2010-03-31 07:42:18 +01:00
|
|
|
return array(0, $s);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($s == 'odd')
|
|
|
|
{
|
|
|
|
return array(2, 1);
|
|
|
|
}
|
2010-04-03 00:54:42 +01:00
|
|
|
|
|
|
|
if ($s == 'even')
|
2010-03-31 07:42:18 +01:00
|
|
|
{
|
|
|
|
return array(2, 0);
|
|
|
|
}
|
2010-04-03 00:54:42 +01:00
|
|
|
|
|
|
|
if ($s == 'n')
|
2010-03-31 07:42:18 +01:00
|
|
|
{
|
|
|
|
return array(1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (false === strpos($s, 'n'))
|
|
|
|
{
|
2010-04-03 00:48:10 +01:00
|
|
|
// Just a b
|
2010-03-31 07:42:18 +01:00
|
|
|
|
|
|
|
return array(0, intval((string) $s));
|
|
|
|
}
|
|
|
|
|
|
|
|
list($a, $b) = explode('n', $s);
|
|
|
|
if (!$a)
|
|
|
|
{
|
|
|
|
$a = 1;
|
|
|
|
}
|
|
|
|
elseif ($a == '-' || $a == '+')
|
|
|
|
{
|
|
|
|
$a = intval($a.'1');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$a = intval($a);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$b)
|
|
|
|
{
|
|
|
|
$b = 0;
|
|
|
|
}
|
|
|
|
elseif ($b == '-' || $b == '+')
|
|
|
|
{
|
|
|
|
$b = intval($b.'1');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$b = intval($b);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array($a, $b);
|
|
|
|
}
|
|
|
|
}
|