Skip to content

Commit

Permalink
[CssSelector] added the component
Browse files Browse the repository at this point in the history
  • Loading branch information
fabpot committed Mar 31, 2010
1 parent 3628287 commit 14ea0da
Show file tree
Hide file tree
Showing 27 changed files with 2,234 additions and 0 deletions.
137 changes: 137 additions & 0 deletions src/Symfony/Components/CssSelector/Node/AttribNode.php
@@ -0,0 +1,137 @@
<?php

namespace Symfony\Components\CssSelector\Node;

use Symfony\Components\CssSelector\XPathExpr;
use Symfony\Components\CssSelector\SyntaxError;

/*
* 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.
*/

/**
* AttribNode represents a "selector[namespace|attrib operator value]" 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 AttribNode implements NodeInterface
{
protected $selector;
protected $namespace;
protected $attrib;
protected $operator;
protected $value;

public function __construct($selector, $namespace, $attrib, $operator, $value)
{
$this->selector = $selector;
$this->namespace = $namespace;
$this->attrib = $attrib;
$this->operator = $operator;
$this->value = $value;
}

public function __toString()
{
if ($this->operator == 'exists')
{
return sprintf('%s[%s[%s]]', __CLASS__, $this->selector, $this->formatAttrib());
}
else
{
return sprintf('%s[%s[%s %s %s]]', __CLASS__, $this->selector, $this->formatAttrib(), $this->operator, $this->value);
}
}

public function toXpath()
{
$path = $this->selector->toXpath();
$attrib = $this->xpathAttrib();
$value = $this->value;
if ($this->operator == 'exists')
{
$path->addCondition($attrib);
}
elseif ($this->operator == '=')
{
$path->addCondition(sprintf('%s = %s', $attrib, XPathExpr::xpathLiteral($value)));
}
elseif ($this->operator == '!=')
{
# FIXME: this seems like a weird hack...
if ($value)
{
$path->addCondition(sprintf('not(%s) or %s != %s', $attrib, $attrib, XPathExpr::xpathLiteral($value)));
}
else
{
$path->addCondition(sprintf('%s != %s', $attrib, XPathExpr::xpathLiteral($value)));
}
#path.addCondition('%s != %s' % (attrib, xpathLiteral(value)))
}
elseif ($this->operator == '~=')
{
$path->addCondition(sprintf("contains(concat(' ', normalize-space(%s), ' '), %s)", $attrib, XPathExpr::xpathLiteral(' '.$value.' ')));
}
elseif ($this->operator == '|=')
{
# Weird, but true...
$path->addCondition(sprintf('%s = %s or starts-with(%s, %s)', $attrib, XPathExpr::xpathLiteral($value), $attrib, XPathExpr::xpathLiteral($value.'-')));
}
elseif ($this->operator == '^=')
{
$path->addCondition(sprintf('starts-with(%s, %s)', $attrib, XPathExpr::xpathLiteral($value)));
}
elseif ($this->operator == '$=')
{
# Oddly there is a starts-with in XPath 1.0, but not ends-with
$path->addCondition(sprintf('substring(%s, string-length(%s)-%s) = %s', $attrib, $attrib, strlen($value) - 1, XPathExpr::xpathLiteral($value)));
}
elseif ($this->operator == '*=')
{
# FIXME: case sensitive?
$path->addCondition(sprintf('contains(%s, %s)', $attrib, XPathExpr::xpathLiteral($value)));
}
else
{
throw new SyntaxError(sprintf("Unknown operator: %s", $this->operator));
}

return $path;
}

protected function xpathAttrib()
{
# FIXME: if attrib is *?
if ($this->namespace == '*')
{
return '@'.$this->attrib;
}
else
{
return sprintf('@%s:%s', $this->namespace, $this->attrib);
}
}

protected function formatAttrib()
{
if ($this->namespace == '*')
{
return $this->attrib;
}
else
{
return sprintf('%s|%s', $this->namespace, $this->attrib);
}
}
}
49 changes: 49 additions & 0 deletions src/Symfony/Components/CssSelector/Node/ClassNode.php
@@ -0,0 +1,49 @@
<?php

namespace Symfony\Components\CssSelector\Node;

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.
*/

/**
* ClassNode represents a "selector.className" 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 ClassNode implements NodeInterface
{
protected $selector;
protected $className;

public function __construct($selector, $className)
{
$this->selector = $selector;
$this->className = $className;
}

public function __toString()
{
return sprintf('%s[%s.%s]', __CLASS__, $this->selector, $this->className);
}

public function toXpath()
{
$selXpath = $this->selector->toXpath();
$selXpath->addCondition(sprintf("contains(concat(' ', normalize-space(@class), ' '), %s)", XPathExpr::xpathLiteral(' '.$this->className.' ')));

return $selXpath;
}
}
99 changes: 99 additions & 0 deletions src/Symfony/Components/CssSelector/Node/CombinedSelectorNode.php
@@ -0,0 +1,99 @@
<?php

namespace Symfony\Components\CssSelector\Node;

use Symfony\Components\CssSelector\SyntaxError;

/*
* 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.
*/

/**
* CombinedSelectorNode represents a combinator 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 CombinedSelectorNode implements NodeInterface
{
static protected $_method_mapping = array(
' ' => 'descendant',
'>' => 'child',
'+' => 'direct_adjacent',
'~' => 'indirect_adjacent',
);

protected $selector;
protected $combinator;
protected $subselector;

public function __construct($selector, $combinator, $subselector)
{
$this->selector = $selector;
$this->combinator = $combinator;
$this->subselector = $subselector;
}

public function __toString()
{
$comb = $this->combinator == ' ' ? '<followed>' : $this->combinator;

return sprintf('%s[%s %s %s]', __CLASS__, $this->selector, $comb, $this->subselector);
}

public function toXpath()
{
if (!isset(self::$_method_mapping[$this->combinator]))
{
throw new SyntaxError(sprintf("Unknown combinator: %s", $this->combinator));
}

$method = '_xpath_'.self::$_method_mapping[$this->combinator];
$path = $this->selector->toXpath();

return $this->$method($path, $this->subselector);
}

protected function _xpath_descendant($xpath, $sub)
{
# when sub is a descendant in any way of xpath
$xpath->join('/descendant::', $sub->toXpath());

return $xpath;
}

protected function _xpath_child($xpath, $sub)
{
# when sub is an immediate child of xpath
$xpath->join('/', $sub->toXpath());

return $xpath;
}

protected function _xpath_direct_adjacent($xpath, $sub)
{
# when sub immediately follows xpath
$xpath->join('/following-sibling::', $sub->toXpath());
$xpath->addNameTest();
$xpath->addCondition('position() = 1');

return $xpath;
}

protected function _xpath_indirect_adjacent($xpath, $sub)
{
# when sub comes somewhere after xpath as a sibling
$xpath->join('/following-sibling::', $sub->toXpath());

return $xpath;
}
}
68 changes: 68 additions & 0 deletions src/Symfony/Components/CssSelector/Node/ElementNode.php
@@ -0,0 +1,68 @@
<?php

namespace Symfony\Components\CssSelector\Node;

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.
*/

/**
* ElementNode represents a "namespace|element" 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 ElementNode implements NodeInterface
{
protected $namespace;
protected $element;

public function __construct($namespace, $element)
{
$this->namespace = $namespace;
$this->element = $element;
}

public function __toString()
{
return sprintf('%s[%s]', __CLASS__, $this->formatElement());
}

public function formatElement()
{
if ($this->namespace == '*')
{
return $this->element;
}
else
{
return sprintf('%s|%s', $this->namespace, $this->element);
}
}

public function toXpath()
{
if ($this->namespace == '*')
{
$el = strtolower($this->element);
}
else
{
# FIXME: Should we lowercase here?
$el = sprintf('%s:%s', $this->namespace, $this->element);
}

return new XPathExpr(null, null, $el);
}
}

0 comments on commit 14ea0da

Please sign in to comment.