1<?php
2/**
3 * An element (token) generated by the scan
4 *
5 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
6 * @copyright Copyright 2010-2014 PhpCss Team
7 */
8
9namespace PhpCss\Scanner {
10
11  use BadMethodCallException;
12  use InvalidArgumentException;
13
14  /**
15   * An element (token) generated by the scan
16   *
17   * @property-read integer $type
18   * @property-read string $content
19   * @property-read integer $length
20   * @property-read integer $position
21   */
22  class Token {
23
24    // special any token (a joker)
25    public const ANY = 255;
26
27    //whitespace
28    public const WHITESPACE = 1;
29    public const NUMBER = 2;
30    public const IDENTIFIER = 3;
31
32    //simple selectors
33    public const CLASS_SELECTOR = 11;
34    public const ID_SELECTOR = 12;
35    public const PSEUDO_CLASS = 13;
36    public const PSEUDO_CLASS_POSITION = 14;
37    public const PSEUDO_ELEMENT = 15;
38
39    // attribute selectors - [...]
40    public const ATTRIBUTE_SELECTOR_START = 20;
41    public const ATTRIBUTE_SELECTOR_END = 21;
42    public const ATTRIBUTE_OPERATOR = 22;
43
44    // parentheses - (...)
45    public const PARENTHESES_START = 31;
46    public const PARENTHESES_END = 32;
47
48    //selector separator
49    public const COMBINATOR = 41;
50    public const SEPARATOR = 42;
51
52    //single quoted strings
53    public const SINGLEQUOTE_STRING_START = 100;
54    public const SINGLEQUOTE_STRING_END = 101;
55    // double quoted strings
56    public const DOUBLEQUOTE_STRING_START = 102;
57    public const DOUBLEQUOTE_STRING_END = 103;
58    // string general
59    public const STRING_CHARACTERS = 110;
60    public const STRING_ESCAPED_CHARACTER = 111;
61
62    private static $_names = [
63      self::ANY => 'ANY',
64      self::WHITESPACE => 'WHITESPACE',
65      self::NUMBER => 'NUMBER',
66      self::IDENTIFIER => 'IDENTIFIER',
67      self::CLASS_SELECTOR => 'SIMPLESELECTOR_CLASS',
68      self::ID_SELECTOR => 'SIMPLE_SELECTOR_ID',
69      self::PSEUDO_CLASS => 'PSEUDOCLASS',
70      self::PSEUDO_CLASS_POSITION => 'PSEUDOCLASS_POSITION',
71      self::PSEUDO_ELEMENT => 'PSEUDOELEMENT',
72      self::ATTRIBUTE_SELECTOR_START => 'SIMPLESELECTOR_ATTRIBUTE_START',
73      self::ATTRIBUTE_SELECTOR_END => 'SIMPLESELECTOR_ATTRIBUTE_END',
74      self::ATTRIBUTE_OPERATOR => 'SIMPLESELECTOR_ATTRIBUTE_OPERATOR',
75      self::PARENTHESES_START => 'PARENTHESES_START',
76      self::PARENTHESES_END => 'PARENTHESES_END',
77      self::COMBINATOR => 'SELECTOR_COMBINATOR',
78      self::SEPARATOR => 'SELECTOR_SEPARATOR',
79      self::SINGLEQUOTE_STRING_START => 'STRING_SINGLE_QUOTE_START',
80      self::SINGLEQUOTE_STRING_END => 'STRING_SINGLE_QUOTE_END',
81      self::DOUBLEQUOTE_STRING_START => 'STRING_DOUBLE_QUOTE_START',
82      self::DOUBLEQUOTE_STRING_END => 'STRING_DOUBLE_QUOTE_END',
83      self::STRING_CHARACTERS => 'STRING_CHARACTERS',
84      self::STRING_ESCAPED_CHARACTER => 'STRING_ESCAPED_CHARACTER',
85    ];
86
87    /**
88     * Token type
89     * @var integer
90     */
91    private $_type;
92    /**
93     * Token string content
94     * @var string
95     */
96    private $_content;
97    /**
98     * Token string content length
99     * @var integer
100     */
101    private $_length;
102    /**
103     * Byte position the token was found at
104     * @var integer
105     */
106    private $_position;
107
108    /**
109     * Construct and initialize token
110     *
111     * @param integer $type
112     * @param string $content
113     * @param integer $position
114     */
115    public function __construct(int $type = 0, string $content = '', int $position = -1) {
116      $this->_type = $type;
117      $this->_content = $content;
118      $this->_length = strlen($content);
119      $this->_position = $position;
120    }
121
122    /**
123     * Get token attribute
124     *
125     * @param string $name
126     * @return int|string
127     * @throws InvalidArgumentException
128     */
129    public function __get(string $name) {
130      switch ($name) {
131      case 'type' :
132        return $this->_type;
133      case 'content' :
134        return $this->_content;
135      case 'length' :
136        return $this->_length;
137      case 'position' :
138        return $this->_position;
139      }
140      throw new InvalidArgumentException();
141    }
142
143    /**
144     * Has token property
145     *
146     * @param string $name
147     * @return bool
148     */
149    public function __isset(string $name): bool {
150      switch ($name) {
151      case 'type' :
152      case 'content' :
153      case 'length' :
154      case 'position' :
155        return TRUE;
156      }
157      return FALSE;
158    }
159
160    /**
161     * Do not allow to set attributes
162     *
163     * @param string $name
164     * @param mixed $value
165     * @return void
166     * @throws BadMethodCallException
167     */
168    public function __set(string $name, $value): void {
169      throw new BadMethodCallException();
170    }
171
172    /**
173     * Convert token object to string
174     * @return string
175     */
176    public function __toString() {
177      return 'TOKEN::'.self::typeToString($this->type).
178        ' @'.$this->position.' '.$this->quoteContent($this->content);
179    }
180
181    /**
182     * Return string representation of token type
183     *
184     * @param integer $type
185     * @return string
186     */
187    public static function typeToString(int $type): string {
188      return self::$_names[$type];
189    }
190
191    /**
192     * Escape content for double quoted, single line string representation
193     *
194     * @param string $content
195     * @return string
196     */
197    protected function quoteContent(string $content): string {
198      return "'".str_replace(
199          ['\\', "\r", "\n", "'"],
200          ['\\\\', '\\r', '\\n', "\\'"],
201          $content
202        )."'";
203    }
204  }
205}
206