You are here

class CssParser in QueryPath 6

Same name and namespace in other branches
  1. 7.3 QueryPath/CssParser.php \CssParser
  2. 7.2 QueryPath/CssParser.php \CssParser

Hierarchy

Expanded class hierarchy of CssParser

File

QueryPath/CssParser.php, line 108

View source
class CssParser {
  protected $scanner = NULL;
  protected $buffer = '';
  protected $handler = NULL;
  protected $strict = FALSE;
  protected $DEBUG = FALSE;
  public function __construct($string, CssEventHandler $handler) {
    $this->originalString = $string;
    $is = new CssInputStream($string);
    $this->scanner = new CssScanner($is);
    $this->handler = $handler;
  }
  public function parse() {
    $this->scanner
      ->nextToken();
    while ($this->scanner->token !== FALSE) {
      $position = $this->scanner
        ->position();
      if ($this->DEBUG) {
        print "PARSE " . $this->scanner->token . "\n";
      }
      $this
        ->selector();
      $finalPosition = $this->scanner
        ->position();
      if ($this->scanner->token !== FALSE && $finalPosition == $position) {
        throw new CssParseException('CSS selector is not well formed.');
      }
    }
  }
  private function selector() {
    if ($this->DEBUG) {
      print "SELECTOR{$this->scanner->position()}\n";
    }
    $this
      ->consumeWhitespace();
    $this
      ->simpleSelectors();
    $this
      ->combinator();
  }
  private function consumeWhitespace() {
    if ($this->DEBUG) {
      print "CONSUME WHITESPACE\n";
    }
    $white = 0;
    while ($this->scanner->token == CssToken::white) {
      $this->scanner
        ->nextToken();
      ++$white;
    }
    return $white;
  }
  private function combinator() {
    if ($this->DEBUG) {
      print "COMBINATOR\n";
    }
    $inCombinator = FALSE;
    $white = $this
      ->consumeWhitespace();
    $t = $this->scanner->token;
    if ($t == CssToken::rangle) {
      $this->handler
        ->directDescendant();
      $this->scanner
        ->nextToken();
      $inCombinator = TRUE;
    }
    elseif ($t == CssToken::plus) {
      $this->handler
        ->adjacent();
      $this->scanner
        ->nextToken();
      $inCombinator = TRUE;
    }
    elseif ($t == CssToken::comma) {
      $this->handler
        ->anotherSelector();
      $this->scanner
        ->nextToken();
      $inCombinator = TRUE;
    }
    elseif ($t == CssToken::tilde) {
      $this->handler
        ->sibling();
      $this->scanner
        ->nextToken();
      $inCombinator = TRUE;
    }
    if ($inCombinator) {
      $white = 0;
      if ($this->DEBUG) {
        print "COMBINATOR: " . CssToken::name($t) . "\n";
      }
      $this
        ->consumeWhitespace();
      if ($this
        ->isCombinator($this->scanner->token)) {
        throw new CssParseException("Illegal combinator: Cannot have two combinators in sequence.");
      }
    }
    elseif ($white > 0) {
      if ($this->DEBUG) {
        print "COMBINATOR: any descendant\n";
      }
      $inCombinator = TRUE;
      $this->handler
        ->anyDescendant();
    }
    else {
      if ($this->DEBUG) {
        print "COMBINATOR: no combinator found.\n";
      }
    }
  }
  private function isCombinator($tok) {
    $combinators = array(
      CssToken::plus,
      CssToken::rangle,
      CssToken::comma,
      CssToken::tilde,
    );
    return in_array($tok, $combinators);
  }
  private function simpleSelectors() {
    if ($this->DEBUG) {
      print "SIMPLE SELECTOR\n";
    }
    $this
      ->allElements();
    $this
      ->elementName();
    $this
      ->elementClass();
    $this
      ->elementID();
    $this
      ->pseudoClass();
    $this
      ->attribute();
  }
  private function elementID() {
    if ($this->DEBUG) {
      print "ELEMENT ID\n";
    }
    if ($this->scanner->token == CssToken::octo) {
      $this->scanner
        ->nextToken();
      if ($this->scanner->token !== CssToken::char) {
        throw new CssParseException("Expected string after #");
      }
      $id = $this->scanner
        ->getNameString();
      $this->handler
        ->elementID($id);
    }
  }
  private function elementClass() {
    if ($this->DEBUG) {
      print "ELEMENT CLASS\n";
    }
    if ($this->scanner->token == CssToken::dot) {
      $this->scanner
        ->nextToken();
      $this
        ->consumeWhitespace();
      $cssClass = $this->scanner
        ->getNameString();
      $this->handler
        ->elementClass($cssClass);
    }
  }
  private function pseudoClass($restricted = FALSE) {
    if ($this->DEBUG) {
      print "PSEUDO-CLASS\n";
    }
    if ($this->scanner->token == CssToken::colon) {
      $isPseudoElement = FALSE;
      if ($this->scanner
        ->nextToken() === CssToken::colon) {
        $isPseudoElement = TRUE;
        $this->scanner
          ->nextToken();
      }
      $name = $this->scanner
        ->getNameString();
      if ($restricted && $name == 'not') {
        throw new CssParseException("The 'not' pseudo-class is illegal in this context.");
      }
      $value = NULL;
      if ($this->scanner->token == CssToken::lparen) {
        if ($isPseudoElement) {
          throw new CssParseException("Illegal left paren. Pseudo-Element cannot have arguments.");
        }
        $value = $this
          ->pseudoClassValue();
      }
      if ($isPseudoElement) {
        if ($restricted) {
          throw new CssParseException("Pseudo-Elements are illegal in this context.");
        }
        $this->handler
          ->pseudoElement($name);
        $this
          ->consumeWhitespace();
        if ($this->scanner->token !== FALSE && $this->scanner->token !== CssToken::comma) {
          throw new CssParseException("A Pseudo-Element must be the last item in a selector.");
        }
      }
      else {
        $this->handler
          ->pseudoClass($name, $value);
      }
    }
  }
  private function pseudoClassValue() {
    if ($this->scanner->token == CssToken::lparen) {
      $buf = '';
      $buf .= $this->scanner
        ->getQuotedString();
      return $buf;
    }
  }
  private function elementName() {
    if ($this->DEBUG) {
      print "ELEMENT NAME\n";
    }
    if ($this->scanner->token === CssToken::pipe) {
      $this->scanner
        ->nextToken();
      $this
        ->consumeWhitespace();
      $elementName = $this->scanner
        ->getNameString();
      $this->handler
        ->element($elementName);
    }
    elseif ($this->scanner->token === CssToken::char) {
      $elementName = $this->scanner
        ->getNameString();
      if ($this->scanner->token == CssToken::pipe) {
        $elementNS = $elementName;
        $this->scanner
          ->nextToken();
        $this
          ->consumeWhitespace();
        if ($this->scanner->token === CssToken::star) {
          $this->handler
            ->anyElementInNS($elementNS);
          $this->scanner
            ->nextToken();
        }
        elseif ($this->scanner->token !== CssToken::char) {
          $this
            ->throwError(CssToken::char, $this->scanner->token);
        }
        else {
          $elementName = $this->scanner
            ->getNameString();
          $this->handler
            ->elementNS($elementName, $elementNS);
        }
      }
      else {
        $this->handler
          ->element($elementName);
      }
    }
  }
  private function allElements() {
    if ($this->scanner->token === CssToken::star) {
      $this->scanner
        ->nextToken();
      if ($this->scanner->token === CssToken::pipe) {
        $this->scanner
          ->nextToken();
        if ($this->scanner->token === CssToken::star) {
          $this->scanner
            ->nextToken();
          $this->handler
            ->anyElementInNS('*');
        }
        else {
          $name = $this->scanner
            ->getNameString();
          $this->handler
            ->elementNS($name, '*');
        }
      }
      else {
        $this->handler
          ->anyElement();
      }
    }
  }
  private function attribute() {
    if ($this->scanner->token == CssToken::lsquare) {
      $attrVal = $op = $ns = NULL;
      $this->scanner
        ->nextToken();
      $this
        ->consumeWhitespace();
      if ($this->scanner->token === CssToken::at) {
        if ($this->strict) {
          throw new CssParseException('The @ is illegal in attributes.');
        }
        else {
          $this->scanner
            ->nextToken();
          $this
            ->consumeWhitespace();
        }
      }
      if ($this->scanner->token === CssToken::star) {
        $ns = '*';
        $this->scanner
          ->nextToken();
      }
      if ($this->scanner->token === CssToken::pipe) {
        $this->scanner
          ->nextToken();
        $this
          ->consumeWhitespace();
      }
      $attrName = $this->scanner
        ->getNameString();
      $this
        ->consumeWhitespace();
      if ($this->scanner->token === CssToken::pipe && $this->scanner
        ->peek() !== '=') {
        $ns = $attrName;
        $this->scanner
          ->nextToken();
        $attrName = $this->scanner
          ->getNameString();
        $this
          ->consumeWhitespace();
      }
      switch ($this->scanner->token) {
        case CssToken::eq:
          $this
            ->consumeWhitespace();
          $op = CssEventHandler::isExactly;
          break;
        case CssToken::tilde:
          if ($this->scanner
            ->nextToken() !== CssToken::eq) {
            $this
              ->throwError(CssToken::eq, $this->scanner->token);
          }
          $op = CssEventHandler::containsWithSpace;
          break;
        case CssToken::pipe:
          if ($this->scanner
            ->nextToken() !== CssToken::eq) {
            $this
              ->throwError(CssToken::eq, $this->scanner->token);
          }
          $op = CssEventHandler::containsWithHyphen;
          break;
        case CssToken::star:
          if ($this->scanner
            ->nextToken() !== CssToken::eq) {
            $this
              ->throwError(CssToken::eq, $this->scanner->token);
          }
          $op = CssEventHandler::containsInString;
          break;
        case CssToken::dollar:
          if ($this->scanner
            ->nextToken() !== CssToken::eq) {
            $this
              ->throwError(CssToken::eq, $this->scanner->token);
          }
          $op = CssEventHandler::endsWith;
          break;
        case CssToken::carat:
          if ($this->scanner
            ->nextToken() !== CssToken::eq) {
            $this
              ->throwError(CssToken::eq, $this->scanner->token);
          }
          $op = CssEventHandler::beginsWith;
          break;
      }
      if (isset($op)) {
        $this->scanner
          ->nextToken();
        $this
          ->consumeWhitespace();
        if ($this->scanner->token === CssToken::quote || $this->scanner->token === CssToken::squote) {
          $attrVal = $this->scanner
            ->getQuotedString();
        }
        else {
          $attrVal = $this->scanner
            ->getNameString();
        }
        if ($this->DEBUG) {
          print "ATTR: {$attrVal} AND OP: {$op}\n";
        }
      }
      $this
        ->consumeWhitespace();
      if ($this->scanner->token != CssToken::rsquare) {
        $this
          ->throwError(CssToken::rsquare, $this->scanner->token);
      }
      if (isset($ns)) {
        $this->handler
          ->attributeNS($attrName, $ns, $attrVal, $op);
      }
      elseif (isset($attrVal)) {
        $this->handler
          ->attribute($attrName, $attrVal, $op);
      }
      else {
        $this->handler
          ->attribute($attrName);
      }
      $this->scanner
        ->nextToken();
    }
  }
  private function throwError($expected, $got) {
    $filter = sprintf('Expected %s, got %s', CssToken::name($expected), CssToken::name($got));
    throw new CssParseException($filter);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CssParser::$buffer protected property
CssParser::$DEBUG protected property
CssParser::$handler protected property
CssParser::$scanner protected property
CssParser::$strict protected property
CssParser::allElements private function
CssParser::attribute private function
CssParser::combinator private function
CssParser::consumeWhitespace private function
CssParser::elementClass private function
CssParser::elementID private function
CssParser::elementName private function
CssParser::isCombinator private function
CssParser::parse public function
CssParser::pseudoClass private function
CssParser::pseudoClassValue private function
CssParser::selector private function
CssParser::simpleSelectors private function
CssParser::throwError private function
CssParser::__construct public function