Skip to content

Commit 14ea0da

Browse files
committed
[CssSelector] added the component
1 parent 3628287 commit 14ea0da

27 files changed

+2234
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace Symfony\Components\CssSelector\Node;
4+
5+
use Symfony\Components\CssSelector\XPathExpr;
6+
use Symfony\Components\CssSelector\SyntaxError;
7+
8+
/*
9+
* This file is part of the symfony package.
10+
*
11+
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
12+
*
13+
* For the full copyright and license information, please view the LICENSE
14+
* file that was distributed with this source code.
15+
*/
16+
17+
/**
18+
* AttribNode represents a "selector[namespace|attrib operator value]" 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+
* @package symfony
24+
* @subpackage css_selector
25+
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
26+
*/
27+
class AttribNode implements NodeInterface
28+
{
29+
protected $selector;
30+
protected $namespace;
31+
protected $attrib;
32+
protected $operator;
33+
protected $value;
34+
35+
public function __construct($selector, $namespace, $attrib, $operator, $value)
36+
{
37+
$this->selector = $selector;
38+
$this->namespace = $namespace;
39+
$this->attrib = $attrib;
40+
$this->operator = $operator;
41+
$this->value = $value;
42+
}
43+
44+
public function __toString()
45+
{
46+
if ($this->operator == 'exists')
47+
{
48+
return sprintf('%s[%s[%s]]', __CLASS__, $this->selector, $this->formatAttrib());
49+
}
50+
else
51+
{
52+
return sprintf('%s[%s[%s %s %s]]', __CLASS__, $this->selector, $this->formatAttrib(), $this->operator, $this->value);
53+
}
54+
}
55+
56+
public function toXpath()
57+
{
58+
$path = $this->selector->toXpath();
59+
$attrib = $this->xpathAttrib();
60+
$value = $this->value;
61+
if ($this->operator == 'exists')
62+
{
63+
$path->addCondition($attrib);
64+
}
65+
elseif ($this->operator == '=')
66+
{
67+
$path->addCondition(sprintf('%s = %s', $attrib, XPathExpr::xpathLiteral($value)));
68+
}
69+
elseif ($this->operator == '!=')
70+
{
71+
# FIXME: this seems like a weird hack...
72+
if ($value)
73+
{
74+
$path->addCondition(sprintf('not(%s) or %s != %s', $attrib, $attrib, XPathExpr::xpathLiteral($value)));
75+
}
76+
else
77+
{
78+
$path->addCondition(sprintf('%s != %s', $attrib, XPathExpr::xpathLiteral($value)));
79+
}
80+
#path.addCondition('%s != %s' % (attrib, xpathLiteral(value)))
81+
}
82+
elseif ($this->operator == '~=')
83+
{
84+
$path->addCondition(sprintf("contains(concat(' ', normalize-space(%s), ' '), %s)", $attrib, XPathExpr::xpathLiteral(' '.$value.' ')));
85+
}
86+
elseif ($this->operator == '|=')
87+
{
88+
# Weird, but true...
89+
$path->addCondition(sprintf('%s = %s or starts-with(%s, %s)', $attrib, XPathExpr::xpathLiteral($value), $attrib, XPathExpr::xpathLiteral($value.'-')));
90+
}
91+
elseif ($this->operator == '^=')
92+
{
93+
$path->addCondition(sprintf('starts-with(%s, %s)', $attrib, XPathExpr::xpathLiteral($value)));
94+
}
95+
elseif ($this->operator == '$=')
96+
{
97+
# Oddly there is a starts-with in XPath 1.0, but not ends-with
98+
$path->addCondition(sprintf('substring(%s, string-length(%s)-%s) = %s', $attrib, $attrib, strlen($value) - 1, XPathExpr::xpathLiteral($value)));
99+
}
100+
elseif ($this->operator == '*=')
101+
{
102+
# FIXME: case sensitive?
103+
$path->addCondition(sprintf('contains(%s, %s)', $attrib, XPathExpr::xpathLiteral($value)));
104+
}
105+
else
106+
{
107+
throw new SyntaxError(sprintf("Unknown operator: %s", $this->operator));
108+
}
109+
110+
return $path;
111+
}
112+
113+
protected function xpathAttrib()
114+
{
115+
# FIXME: if attrib is *?
116+
if ($this->namespace == '*')
117+
{
118+
return '@'.$this->attrib;
119+
}
120+
else
121+
{
122+
return sprintf('@%s:%s', $this->namespace, $this->attrib);
123+
}
124+
}
125+
126+
protected function formatAttrib()
127+
{
128+
if ($this->namespace == '*')
129+
{
130+
return $this->attrib;
131+
}
132+
else
133+
{
134+
return sprintf('%s|%s', $this->namespace, $this->attrib);
135+
}
136+
}
137+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Symfony\Components\CssSelector\Node;
4+
5+
use Symfony\Components\CssSelector\XPathExpr;
6+
7+
/*
8+
* This file is part of the symfony package.
9+
*
10+
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
11+
*
12+
* For the full copyright and license information, please view the LICENSE
13+
* file that was distributed with this source code.
14+
*/
15+
16+
/**
17+
* ClassNode represents a "selector.className" node.
18+
*
19+
* This component is a port of the Python lxml library,
20+
* which is copyright Infrae and distributed under the BSD license.
21+
*
22+
* @package symfony
23+
* @subpackage css_selector
24+
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
25+
*/
26+
class ClassNode implements NodeInterface
27+
{
28+
protected $selector;
29+
protected $className;
30+
31+
public function __construct($selector, $className)
32+
{
33+
$this->selector = $selector;
34+
$this->className = $className;
35+
}
36+
37+
public function __toString()
38+
{
39+
return sprintf('%s[%s.%s]', __CLASS__, $this->selector, $this->className);
40+
}
41+
42+
public function toXpath()
43+
{
44+
$selXpath = $this->selector->toXpath();
45+
$selXpath->addCondition(sprintf("contains(concat(' ', normalize-space(@class), ' '), %s)", XPathExpr::xpathLiteral(' '.$this->className.' ')));
46+
47+
return $selXpath;
48+
}
49+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace Symfony\Components\CssSelector\Node;
4+
5+
use Symfony\Components\CssSelector\SyntaxError;
6+
7+
/*
8+
* This file is part of the symfony package.
9+
*
10+
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
11+
*
12+
* For the full copyright and license information, please view the LICENSE
13+
* file that was distributed with this source code.
14+
*/
15+
16+
/**
17+
* CombinedSelectorNode represents a combinator node.
18+
*
19+
* This component is a port of the Python lxml library,
20+
* which is copyright Infrae and distributed under the BSD license.
21+
*
22+
* @package symfony
23+
* @subpackage css_selector
24+
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
25+
*/
26+
class CombinedSelectorNode implements NodeInterface
27+
{
28+
static protected $_method_mapping = array(
29+
' ' => 'descendant',
30+
'>' => 'child',
31+
'+' => 'direct_adjacent',
32+
'~' => 'indirect_adjacent',
33+
);
34+
35+
protected $selector;
36+
protected $combinator;
37+
protected $subselector;
38+
39+
public function __construct($selector, $combinator, $subselector)
40+
{
41+
$this->selector = $selector;
42+
$this->combinator = $combinator;
43+
$this->subselector = $subselector;
44+
}
45+
46+
public function __toString()
47+
{
48+
$comb = $this->combinator == ' ' ? '<followed>' : $this->combinator;
49+
50+
return sprintf('%s[%s %s %s]', __CLASS__, $this->selector, $comb, $this->subselector);
51+
}
52+
53+
public function toXpath()
54+
{
55+
if (!isset(self::$_method_mapping[$this->combinator]))
56+
{
57+
throw new SyntaxError(sprintf("Unknown combinator: %s", $this->combinator));
58+
}
59+
60+
$method = '_xpath_'.self::$_method_mapping[$this->combinator];
61+
$path = $this->selector->toXpath();
62+
63+
return $this->$method($path, $this->subselector);
64+
}
65+
66+
protected function _xpath_descendant($xpath, $sub)
67+
{
68+
# when sub is a descendant in any way of xpath
69+
$xpath->join('/descendant::', $sub->toXpath());
70+
71+
return $xpath;
72+
}
73+
74+
protected function _xpath_child($xpath, $sub)
75+
{
76+
# when sub is an immediate child of xpath
77+
$xpath->join('/', $sub->toXpath());
78+
79+
return $xpath;
80+
}
81+
82+
protected function _xpath_direct_adjacent($xpath, $sub)
83+
{
84+
# when sub immediately follows xpath
85+
$xpath->join('/following-sibling::', $sub->toXpath());
86+
$xpath->addNameTest();
87+
$xpath->addCondition('position() = 1');
88+
89+
return $xpath;
90+
}
91+
92+
protected function _xpath_indirect_adjacent($xpath, $sub)
93+
{
94+
# when sub comes somewhere after xpath as a sibling
95+
$xpath->join('/following-sibling::', $sub->toXpath());
96+
97+
return $xpath;
98+
}
99+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Symfony\Components\CssSelector\Node;
4+
5+
use Symfony\Components\CssSelector\XPathExpr;
6+
7+
/*
8+
* This file is part of the symfony package.
9+
*
10+
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
11+
*
12+
* For the full copyright and license information, please view the LICENSE
13+
* file that was distributed with this source code.
14+
*/
15+
16+
/**
17+
* ElementNode represents a "namespace|element" node.
18+
*
19+
* This component is a port of the Python lxml library,
20+
* which is copyright Infrae and distributed under the BSD license.
21+
*
22+
* @package symfony
23+
* @subpackage css_selector
24+
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
25+
*/
26+
class ElementNode implements NodeInterface
27+
{
28+
protected $namespace;
29+
protected $element;
30+
31+
public function __construct($namespace, $element)
32+
{
33+
$this->namespace = $namespace;
34+
$this->element = $element;
35+
}
36+
37+
public function __toString()
38+
{
39+
return sprintf('%s[%s]', __CLASS__, $this->formatElement());
40+
}
41+
42+
public function formatElement()
43+
{
44+
if ($this->namespace == '*')
45+
{
46+
return $this->element;
47+
}
48+
else
49+
{
50+
return sprintf('%s|%s', $this->namespace, $this->element);
51+
}
52+
}
53+
54+
public function toXpath()
55+
{
56+
if ($this->namespace == '*')
57+
{
58+
$el = strtolower($this->element);
59+
}
60+
else
61+
{
62+
# FIXME: Should we lowercase here?
63+
$el = sprintf('%s:%s', $this->namespace, $this->element);
64+
}
65+
66+
return new XPathExpr(null, null, $el);
67+
}
68+
}

0 commit comments

Comments
 (0)