vendor/symfony/src/Symfony/Component/CssSelector/CssSelector.php
author ymh <ymh.work@gmail.com>
Sat, 24 Sep 2011 15:40:41 +0200
changeset 0 7f95f8617b0b
permissions -rwxr-xr-x
first commit
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
<?php
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
/*
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
 * This file is part of the Symfony package.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
 * (c) Fabien Potencier <fabien@symfony.com>
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
 * For the full copyright and license information, please view the LICENSE
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
 * file that was distributed with this source code.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
 */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
namespace Symfony\Component\CssSelector;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
use Symfony\Component\CssSelector\Exception\ParseException;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
/**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
 * CssSelector is the main entry point of the component and can convert CSS
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
 * selectors to XPath expressions.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
 * $xpath = CssSelector::toXpath('h1.foo');
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
 * This component is a port of the Python lxml library,
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
 * which is copyright Infrae and distributed under the BSD license.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
 * @author Fabien Potencier <fabien@symfony.com>
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
 *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
 * @api
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
 */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
class CssSelector
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
{
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
     * Translates a CSS expression to its XPath equivalent.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
     * Optionally, a prefix can be added to the resulting XPath
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
     * expression with the $prefix parameter.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
     * @param  mixed  $cssExpr The CSS expression.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
     * @param  string $prefix  An optional prefix for the XPath expression.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
     * @return string
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
     * @throws ParseException When got None for xpath expression
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
     * @api
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
    static public function toXPath($cssExpr, $prefix = 'descendant-or-self::')
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
        if (is_string($cssExpr)) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
            if (preg_match('#^\w+\s*$#u', $cssExpr, $match)) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
                return $prefix.trim($match[0]);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
            if (preg_match('~^(\w*)#(\w+)\s*$~u', $cssExpr, $match)) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
                return sprintf("%s%s[@id = '%s']", $prefix, $match[1] ? $match[1] : '*', $match[2]);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
            if (preg_match('#^(\w*)\.(\w+)\s*$#u', $cssExpr, $match)) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
                return sprintf("%s%s[contains(concat(' ', normalize-space(@class), ' '), ' %s ')]", $prefix, $match[1] ? $match[1] : '*', $match[2]);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
            $parser = new self();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
            $cssExpr = $parser->parse($cssExpr);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
        $expr = $cssExpr->toXpath();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
        // @codeCoverageIgnoreStart
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
        if (!$expr) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
            throw new ParseException(sprintf('Got None for xpath expression from %s.', $cssExpr));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
        // @codeCoverageIgnoreEnd
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
        if ($prefix) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
            $expr->addPrefix($prefix);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
        return (string) $expr;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
     * Parses an expression and returns the Node object that represents
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
     * the parsed expression.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
     * @throws \Exception When tokenizer throws it while parsing
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
     * @param  string $string The expression to parse
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
     * @return Node\NodeInterface
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
    public function parse($string)
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
        $tokenizer = new Tokenizer();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
        $stream = new TokenStream($tokenizer->tokenize($string), $string);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
        try {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
            return $this->parseSelectorGroup($stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
        } catch (\Exception $e) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
            $class = get_class($e);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
            throw new $class(sprintf('%s at %s -> %s', $e->getMessage(), implode($stream->getUsed(), ''), $stream->peek()), 0, $e);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
     * Parses a selector group contained in $stream and returns
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
     * the Node object that represents the expression.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
     * @param  TokenStream $stream The stream to parse.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
     * @return Node\NodeInterface
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
    private function parseSelectorGroup($stream)
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
        $result = array();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
        while (true) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
            $result[] = $this->parseSelector($stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
            if ($stream->peek() == ',') {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
                $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
            } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
                break;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
        if (count($result) == 1) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
            return $result[0];
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
        return new Node\OrNode($result);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
     * Parses a selector contained in $stream and returns the Node
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
     * object that represents it.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
     * @throws ParseException When expected selector but got something else
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
     * @param  TokenStream $stream The stream containing the selector.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   138
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   139
     * @return Node\NodeInterface
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   140
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   141
    private function parseSelector($stream)
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   142
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   143
        $result = $this->parseSimpleSelector($stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   144
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   145
        while (true) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   146
            $peek = $stream->peek();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   147
            if (',' == $peek || null === $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   148
                return $result;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   149
            } elseif (in_array($peek, array('+', '>', '~'))) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   150
                // A combinator
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   151
                $combinator = (string) $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   152
            } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   153
                $combinator = ' ';
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   154
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   155
            $consumed = count($stream->getUsed());
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   156
            $nextSelector = $this->parseSimpleSelector($stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   157
            if ($consumed == count($stream->getUsed())) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   158
                throw new ParseException(sprintf("Expected selector, got '%s'", $stream->peek()));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   159
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   160
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   161
            $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   162
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   163
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   164
        return $result;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   165
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   166
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   167
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   168
     * Parses a simple selector (the current token) from $stream and returns
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   169
     * the resulting Node object.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   170
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   171
     * @throws ParseException When expected symbol but got something else
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   172
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   173
     * @param  TokenStream $stream The stream containing the selector.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   174
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   175
     * @return Node\NodeInterface
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   176
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   177
    private function parseSimpleSelector($stream)
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   178
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   179
        $peek = $stream->peek();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   180
        if ('*' != $peek && !$peek->isType('Symbol')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   181
            $element = $namespace = '*';
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   182
        } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   183
            $next = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   184
            if ('*' != $next && !$next->isType('Symbol')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   185
                throw new ParseException(sprintf("Expected symbol, got '%s'", $next));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   186
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   187
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   188
            if ($stream->peek() == '|') {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   189
                $namespace = $next;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   190
                $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   191
                $element = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   192
                if ('*' != $element && !$next->isType('Symbol')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   193
                    throw new ParseException(sprintf("Expected symbol, got '%s'", $next));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   194
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   195
            } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   196
                $namespace = '*';
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   197
                $element = $next;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   198
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   199
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   200
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   201
        $result = new Node\ElementNode($namespace, $element);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   202
        $hasHash = false;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   203
        while (true) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   204
            $peek = $stream->peek();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   205
            if ('#' == $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   206
                if ($hasHash) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   207
                    /* You can't have two hashes
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   208
                        (FIXME: is there some more general rule I'm missing?) */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   209
                    // @codeCoverageIgnoreStart
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   210
                    break;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   211
                    // @codeCoverageIgnoreEnd
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   212
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   213
                $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   214
                $result = new Node\HashNode($result, $stream->next());
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   215
                $hasHash = true;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   216
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   217
                continue;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   218
            } elseif ('.' == $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   219
                $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   220
                $result = new Node\ClassNode($result, $stream->next());
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   221
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   222
                continue;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   223
            } elseif ('[' == $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   224
                $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   225
                $result = $this->parseAttrib($result, $stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   226
                $next = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   227
                if (']' != $next) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   228
                    throw new ParseException(sprintf("] expected, got '%s'", $next));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   229
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   230
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   231
                continue;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   232
            } elseif (':' == $peek || '::' == $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   233
                $type = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   234
                $ident = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   235
                if (!$ident || !$ident->isType('Symbol')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   236
                    throw new ParseException(sprintf("Expected symbol, got '%s'", $ident));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   237
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   238
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   239
                if ($stream->peek() == '(') {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   240
                    $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   241
                    $peek = $stream->peek();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   242
                    if ($peek->isType('String')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   243
                        $selector = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   244
                    } elseif ($peek->isType('Symbol') && is_int($peek)) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   245
                        $selector = intval($stream->next());
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   246
                    } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   247
                        // FIXME: parseSimpleSelector, or selector, or...?
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   248
                        $selector = $this->parseSimpleSelector($stream);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   249
                    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   250
                    $next = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   251
                    if (')' != $next) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   252
                        throw new ParseException(sprintf("Expected ')', got '%s' and '%s'", $next, $selector));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   253
                    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   254
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   255
                    $result = new Node\FunctionNode($result, $type, $ident, $selector);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   256
                } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   257
                    $result = new Node\PseudoNode($result, $type, $ident);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   258
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   259
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   260
                continue;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   261
            } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   262
                if (' ' == $peek) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   263
                    $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   264
                }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   265
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   266
                break;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   267
            }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   268
            // FIXME: not sure what "negation" is
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   269
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   270
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   271
        return $result;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   272
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   273
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   274
    /**
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   275
     * Parses an attribute from a selector contained in $stream and returns
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   276
     * the resulting AttribNode object.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   277
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   278
     * @throws ParseException When encountered unexpected selector
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   279
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   280
     * @param  Node\NodeInterface $selector The selector object whose attribute
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   281
     *                                      is to be parsed.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   282
     * @param  TokenStream        $stream    The container token stream.
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   283
     *
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   284
     * @return Node\AttribNode
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   285
     */
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   286
    private function parseAttrib($selector, $stream)
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   287
    {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   288
        $attrib = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   289
        if ($stream->peek() == '|') {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   290
            $namespace = $attrib;
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   291
            $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   292
            $attrib = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   293
        } else {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   294
            $namespace = '*';
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   295
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   296
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   297
        if ($stream->peek() == ']') {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   298
            return new Node\AttribNode($selector, $namespace, $attrib, 'exists', null);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   299
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   300
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   301
        $op = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   302
        if (!in_array($op, array('^=', '$=', '*=', '=', '~=', '|=', '!='))) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   303
            throw new ParseException(sprintf("Operator expected, got '%s'", $op));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   304
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   305
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   306
        $value = $stream->next();
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   307
        if (!$value->isType('Symbol') && !$value->isType('String')) {
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   308
            throw new ParseException(sprintf("Expected string or symbol, got '%s'", $value));
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   309
        }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   310
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   311
        return new Node\AttribNode($selector, $namespace, $attrib, $op, $value);
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   312
    }
7f95f8617b0b first commit
ymh <ymh.work@gmail.com>
parents:
diff changeset
   313
}