--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/symfony/src/Symfony/Component/CssSelector/CssSelector.php Sat Sep 24 15:40:41 2011 +0200
@@ -0,0 +1,313 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\CssSelector;
+
+use Symfony\Component\CssSelector\Exception\ParseException;
+
+/**
+ * CssSelector is the main entry point of the component and can convert CSS
+ * selectors to XPath expressions.
+ *
+ * $xpath = CssSelector::toXpath('h1.foo');
+ *
+ * This component is a port of the Python lxml library,
+ * which is copyright Infrae and distributed under the BSD license.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @api
+ */
+class CssSelector
+{
+ /**
+ * Translates a CSS expression to its XPath equivalent.
+ * Optionally, a prefix can be added to the resulting XPath
+ * expression with the $prefix parameter.
+ *
+ * @param mixed $cssExpr The CSS expression.
+ * @param string $prefix An optional prefix for the XPath expression.
+ *
+ * @return string
+ *
+ * @throws ParseException When got None for xpath expression
+ *
+ * @api
+ */
+ static public function toXPath($cssExpr, $prefix = 'descendant-or-self::')
+ {
+ if (is_string($cssExpr)) {
+ if (preg_match('#^\w+\s*$#u', $cssExpr, $match)) {
+ return $prefix.trim($match[0]);
+ }
+
+ if (preg_match('~^(\w*)#(\w+)\s*$~u', $cssExpr, $match)) {
+ return sprintf("%s%s[@id = '%s']", $prefix, $match[1] ? $match[1] : '*', $match[2]);
+ }
+
+ if (preg_match('#^(\w*)\.(\w+)\s*$#u', $cssExpr, $match)) {
+ return sprintf("%s%s[contains(concat(' ', normalize-space(@class), ' '), ' %s ')]", $prefix, $match[1] ? $match[1] : '*', $match[2]);
+ }
+
+ $parser = new self();
+ $cssExpr = $parser->parse($cssExpr);
+ }
+
+ $expr = $cssExpr->toXpath();
+
+ // @codeCoverageIgnoreStart
+ if (!$expr) {
+ throw new ParseException(sprintf('Got None for xpath expression from %s.', $cssExpr));
+ }
+ // @codeCoverageIgnoreEnd
+
+ if ($prefix) {
+ $expr->addPrefix($prefix);
+ }
+
+ return (string) $expr;
+ }
+
+ /**
+ * Parses an expression and returns the Node object that represents
+ * the parsed expression.
+ *
+ * @throws \Exception When tokenizer throws it while parsing
+ *
+ * @param string $string The expression to parse
+ *
+ * @return Node\NodeInterface
+ */
+ public function parse($string)
+ {
+ $tokenizer = new Tokenizer();
+
+ $stream = new TokenStream($tokenizer->tokenize($string), $string);
+
+ try {
+ return $this->parseSelectorGroup($stream);
+ } catch (\Exception $e) {
+ $class = get_class($e);
+
+ throw new $class(sprintf('%s at %s -> %s', $e->getMessage(), implode($stream->getUsed(), ''), $stream->peek()), 0, $e);
+ }
+ }
+
+ /**
+ * Parses a selector group contained in $stream and returns
+ * the Node object that represents the expression.
+ *
+ * @param TokenStream $stream The stream to parse.
+ *
+ * @return Node\NodeInterface
+ */
+ private function parseSelectorGroup($stream)
+ {
+ $result = array();
+ while (true) {
+ $result[] = $this->parseSelector($stream);
+ if ($stream->peek() == ',') {
+ $stream->next();
+ } else {
+ break;
+ }
+ }
+
+ if (count($result) == 1) {
+ return $result[0];
+ }
+
+ return new Node\OrNode($result);
+ }
+
+ /**
+ * Parses a selector contained in $stream and returns the Node
+ * object that represents it.
+ *
+ * @throws ParseException When expected selector but got something else
+ *
+ * @param TokenStream $stream The stream containing the selector.
+ *
+ * @return Node\NodeInterface
+ */
+ private function parseSelector($stream)
+ {
+ $result = $this->parseSimpleSelector($stream);
+
+ while (true) {
+ $peek = $stream->peek();
+ if (',' == $peek || null === $peek) {
+ return $result;
+ } elseif (in_array($peek, array('+', '>', '~'))) {
+ // A combinator
+ $combinator = (string) $stream->next();
+ } else {
+ $combinator = ' ';
+ }
+ $consumed = count($stream->getUsed());
+ $nextSelector = $this->parseSimpleSelector($stream);
+ if ($consumed == count($stream->getUsed())) {
+ throw new ParseException(sprintf("Expected selector, got '%s'", $stream->peek()));
+ }
+
+ $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parses a simple selector (the current token) from $stream and returns
+ * the resulting Node object.
+ *
+ * @throws ParseException When expected symbol but got something else
+ *
+ * @param TokenStream $stream The stream containing the selector.
+ *
+ * @return Node\NodeInterface
+ */
+ private function parseSimpleSelector($stream)
+ {
+ $peek = $stream->peek();
+ if ('*' != $peek && !$peek->isType('Symbol')) {
+ $element = $namespace = '*';
+ } else {
+ $next = $stream->next();
+ if ('*' != $next && !$next->isType('Symbol')) {
+ throw new ParseException(sprintf("Expected symbol, got '%s'", $next));
+ }
+
+ if ($stream->peek() == '|') {
+ $namespace = $next;
+ $stream->next();
+ $element = $stream->next();
+ if ('*' != $element && !$next->isType('Symbol')) {
+ throw new ParseException(sprintf("Expected symbol, got '%s'", $next));
+ }
+ } else {
+ $namespace = '*';
+ $element = $next;
+ }
+ }
+
+ $result = new Node\ElementNode($namespace, $element);
+ $hasHash = false;
+ while (true) {
+ $peek = $stream->peek();
+ if ('#' == $peek) {
+ if ($hasHash) {
+ /* You can't have two hashes
+ (FIXME: is there some more general rule I'm missing?) */
+ // @codeCoverageIgnoreStart
+ break;
+ // @codeCoverageIgnoreEnd
+ }
+ $stream->next();
+ $result = new Node\HashNode($result, $stream->next());
+ $hasHash = true;
+
+ continue;
+ } elseif ('.' == $peek) {
+ $stream->next();
+ $result = new Node\ClassNode($result, $stream->next());
+
+ continue;
+ } elseif ('[' == $peek) {
+ $stream->next();
+ $result = $this->parseAttrib($result, $stream);
+ $next = $stream->next();
+ if (']' != $next) {
+ throw new ParseException(sprintf("] expected, got '%s'", $next));
+ }
+
+ continue;
+ } elseif (':' == $peek || '::' == $peek) {
+ $type = $stream->next();
+ $ident = $stream->next();
+ if (!$ident || !$ident->isType('Symbol')) {
+ throw new ParseException(sprintf("Expected symbol, got '%s'", $ident));
+ }
+
+ if ($stream->peek() == '(') {
+ $stream->next();
+ $peek = $stream->peek();
+ if ($peek->isType('String')) {
+ $selector = $stream->next();
+ } elseif ($peek->isType('Symbol') && is_int($peek)) {
+ $selector = intval($stream->next());
+ } else {
+ // FIXME: parseSimpleSelector, or selector, or...?
+ $selector = $this->parseSimpleSelector($stream);
+ }
+ $next = $stream->next();
+ if (')' != $next) {
+ throw new ParseException(sprintf("Expected ')', got '%s' and '%s'", $next, $selector));
+ }
+
+ $result = new Node\FunctionNode($result, $type, $ident, $selector);
+ } else {
+ $result = new Node\PseudoNode($result, $type, $ident);
+ }
+
+ continue;
+ } else {
+ if (' ' == $peek) {
+ $stream->next();
+ }
+
+ break;
+ }
+ // FIXME: not sure what "negation" is
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parses an attribute from a selector contained in $stream and returns
+ * the resulting AttribNode object.
+ *
+ * @throws ParseException When encountered unexpected selector
+ *
+ * @param Node\NodeInterface $selector The selector object whose attribute
+ * is to be parsed.
+ * @param TokenStream $stream The container token stream.
+ *
+ * @return Node\AttribNode
+ */
+ private function parseAttrib($selector, $stream)
+ {
+ $attrib = $stream->next();
+ if ($stream->peek() == '|') {
+ $namespace = $attrib;
+ $stream->next();
+ $attrib = $stream->next();
+ } else {
+ $namespace = '*';
+ }
+
+ if ($stream->peek() == ']') {
+ return new Node\AttribNode($selector, $namespace, $attrib, 'exists', null);
+ }
+
+ $op = $stream->next();
+ if (!in_array($op, array('^=', '$=', '*=', '=', '~=', '|=', '!='))) {
+ throw new ParseException(sprintf("Operator expected, got '%s'", $op));
+ }
+
+ $value = $stream->next();
+ if (!$value->isType('Symbol') && !$value->isType('String')) {
+ throw new ParseException(sprintf("Expected string or symbol, got '%s'", $value));
+ }
+
+ return new Node\AttribNode($selector, $namespace, $attrib, $op, $value);
+ }
+}