8
* @package PHP_CodeSniffer
9
* @author Greg Sherwood <gsherwood@squiz.net>
10
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
11
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12
* @link http://pear.php.net/package/PHP_CodeSniffer
15
if (class_exists('PHP_CodeSniffer_Tokenizers_PHP', true) === false) {
16
throw new Exception('Class PHP_CodeSniffer_Tokenizers_PHP not found');
23
* @package PHP_CodeSniffer
24
* @author Greg Sherwood <gsherwood@squiz.net>
25
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
26
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
27
* @version Release: 1.5.4
28
* @link http://pear.php.net/package/PHP_CodeSniffer
30
class PHP_CodeSniffer_Tokenizers_CSS extends PHP_CodeSniffer_Tokenizers_PHP
35
* Creates an array of tokens when given some CSS code.
37
* Uses the PHP tokenizer to do all the tricky work
39
* @param string $string The string to tokenize.
40
* @param string $eolChar The EOL character to use for splitting strings.
44
public function tokenizeString($string, $eolChar='\n')
46
if (PHP_CODESNIFFER_VERBOSITY > 1) {
47
echo "\t*** START CSS TOKENIZING ***".PHP_EOL;
50
// If the content doesn't have an EOl char on the end, add one so
51
// the open and close tags we add are parsed correctly.
52
if (substr($string, 0, (strlen($eolChar) * -1)) !== $eolChar) {
56
$tokens = parent::tokenizeString('<?php '.$string.'?>', $eolChar);
57
$finalTokens = array();
60
$numTokens = count($tokens);
61
$multiLineComment = false;
62
for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
63
$token = $tokens[$stackPtr];
65
// CSS files don't have lists or break tags, so convert these to
66
// standard strings early so they can be converted into T_STYLE
67
// tokens and joined with other strings if needed.
68
if ($token['code'] === T_BREAK || $token['code'] === T_LIST) {
69
$token['type'] = 'T_STRING';
70
$token['code'] = T_STRING;
73
if (PHP_CODESNIFFER_VERBOSITY > 1) {
74
$type = $token['type'];
75
$content = str_replace($eolChar, '\n', $token['content']);
76
echo "\tProcess token $stackPtr: $type => $content".PHP_EOL;
79
// Sometimes, there are PHP tags embedded in the code, which causes issues
80
// with how PHP tokenizeses the string. After the first closing tag is found,
81
// everything outside PHP tags is set as inline HTML tokens (1 for each line).
82
// So we need to go through and find these tokens so we can re-tokenize them.
83
if ($token['code'] === T_CLOSE_TAG && $stackPtr !== ($numTokens - 1)) {
85
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
86
if ($tokens[$x]['code'] === T_INLINE_HTML) {
87
$content .= $tokens[$x]['content'];
94
if ($x < ($numTokens - 1)) {
95
// This is not the last closing tag in the file, so we
96
// have to add another closing tag here. If it is the last closing
97
// tag, this additional one would have been added during the
98
// original tokenize call.
102
if (PHP_CODESNIFFER_VERBOSITY > 1) {
103
echo "\t\t=> Found premature closing tag at $stackPtr".PHP_EOL;
104
$cleanContent = str_replace($eolChar, '\n', $content);
105
echo "\t\tcontent: $cleanContent".PHP_EOL;
106
$oldNumTokens = $numTokens;
109
// Tokenize the string and remove the extra PHP tags we don't need.
110
$moreTokens = parent::tokenizeString($content, $eolChar);
111
array_shift($moreTokens);
112
array_pop($moreTokens);
113
array_pop($moreTokens);
115
// Rebuild the tokens array.
116
array_splice($tokens, ($stackPtr + 1), ($x - $stackPtr), $moreTokens);
117
$numTokens = count($tokens);
118
if (PHP_CODESNIFFER_VERBOSITY > 1) {
119
$count = count($moreTokens);
120
$diff = ($x - $stackPtr);
121
echo "\t\t* added $count tokens, replaced $diff; size changed from $oldNumTokens to $numTokens *".PHP_EOL;
125
if ($token['code'] === T_GOTO_LABEL) {
126
// Convert these back to T_STRING folowed by T_COLON so we can
127
// more easily process style definitions.
128
$finalTokens[$newStackPtr] = array(
129
'type' => 'T_STRING',
131
'content' => substr($token['content'], 0, -1),
134
$finalTokens[$newStackPtr] = array(
143
if ($token['code'] === T_FUNCTION) {
144
// There are no functions in CSS, so convert this to a string.
145
$finalTokens[$newStackPtr] = array(
146
'type' => 'T_STRING',
148
'content' => $token['content'],
155
if ($token['code'] === T_COMMENT
156
&& substr($token['content'], 0, 2) === '/*'
158
// Multi-line comment. Record it so we can ignore other
159
// comment tags until we get out of this one.
160
$multiLineComment = true;
163
if ($token['code'] === T_COMMENT
164
&& $multiLineComment === false
165
&& (substr($token['content'], 0, 2) === '//'
166
|| $token['content']{0} === '#')
168
$content = ltrim($token['content'], '#/');
170
= parent::tokenizeString('<?php '.$content.'?>', $eolChar);
172
// The first and last tokens are the open/close tags.
173
array_shift($commentTokens);
174
array_pop($commentTokens);
176
if ($token['content']{0} === '#') {
177
// The # character is not a comment in CSS files, so
178
// determine what it means in this context.
179
$firstContent = $commentTokens[0]['content'];
181
// If the first content is just a number, it is probably a
182
// colour like 8FB7DB, which PHP splits into 8 and FB7DB.
183
if (($commentTokens[0]['code'] === T_LNUMBER
184
|| $commentTokens[0]['code'] === T_DNUMBER)
185
&& $commentTokens[1]['code'] === T_STRING
187
$firstContent .= $commentTokens[1]['content'];
188
array_shift($commentTokens);
191
// If the first content looks like a colour and not a class
192
// definition, join the tokens together.
193
if (preg_match('/^[ABCDEF0-9]+$/i', $firstContent) === 1
194
&& $commentTokens[1]['content'] !== '-'
196
array_shift($commentTokens);
197
// Work out what we trimmed off above and remember to re-add it.
198
$trimmed = substr($token['content'], 0, (strlen($token['content']) - strlen($content)));
199
$finalTokens[$newStackPtr] = array(
200
'type' => 'T_COLOUR',
202
'content' => $trimmed.$firstContent,
205
$finalTokens[$newStackPtr] = array(
212
$finalTokens[$newStackPtr] = array(
213
'type' => 'T_STRING',
221
foreach ($commentTokens as $tokenData) {
222
if ($tokenData['code'] === T_COMMENT
223
&& (substr($tokenData['content'], 0, 2) === '//'
224
|| $tokenData['content']{0} === '#')
226
// This is a comment in a comment, so it needs
227
// to go through the whole process again.
228
$tokens[$stackPtr]['content'] = $tokenData['content'];
233
$finalTokens[$newStackPtr] = $tokenData;
240
if ($token['code'] === T_COMMENT
241
&& substr($token['content'], -2) === '*/'
243
// Multi-line comment is done.
244
$multiLineComment = false;
247
$finalTokens[$newStackPtr] = $token;
251
// A flag to indicate if we are inside a style definition,
252
// which is defined using curly braces. I'm assuming you can't
253
// have nested curly brackets.
256
$numTokens = count($finalTokens);
257
for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
258
$token = $finalTokens[$stackPtr];
260
switch ($token['code']) {
261
case T_OPEN_CURLY_BRACKET:
264
case T_CLOSE_CURLY_BRACKET:
268
// Minus signs are often used instead of spaces inside
269
// class names, IDs and styles.
270
if ($finalTokens[($stackPtr + 1)]['code'] === T_STRING) {
271
if ($finalTokens[($stackPtr - 1)]['code'] === T_STRING) {
272
$newContent = $finalTokens[($stackPtr - 1)]['content'].'-'.$finalTokens[($stackPtr + 1)]['content'];
274
$finalTokens[($stackPtr - 1)]['content'] = $newContent;
275
unset($finalTokens[$stackPtr]);
276
unset($finalTokens[($stackPtr + 1)]);
279
$newContent = '-'.$finalTokens[($stackPtr + 1)]['content'];
281
$finalTokens[($stackPtr + 1)]['content'] = $newContent;
282
unset($finalTokens[$stackPtr]);
286
$finalTokens = array_values($finalTokens);
287
$numTokens = count($finalTokens);
288
} else if ($finalTokens[($stackPtr + 1)]['code'] === T_LNUMBER) {
289
// They can also be used to provide negative numbers.
290
$finalTokens[($stackPtr + 1)]['content']
291
= '-'.$finalTokens[($stackPtr + 1)]['content'];
292
unset($finalTokens[$stackPtr]);
294
$finalTokens = array_values($finalTokens);
295
$numTokens = count($finalTokens);
300
// Only interested in colons that are defining styles.
301
if ($inStyleDef === false) {
305
for ($x = ($stackPtr - 1); $x >= 0; $x--) {
306
if (in_array($finalTokens[$x]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
311
$finalTokens[$x]['type'] = 'T_STYLE';
312
$finalTokens[$x]['code'] = T_STYLE;
315
if (strtolower($token['content']) === 'url') {
316
// Find the next content.
317
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
318
if (in_array($finalTokens[$x]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
323
// Needs to be in the format "url(" for it to be a URL.
324
if ($finalTokens[$x]['code'] !== T_OPEN_PARENTHESIS) {
328
// Make sure the content isn't empty.
329
for ($y = ($x + 1); $y < $numTokens; $y++) {
330
if (in_array($finalTokens[$y]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
335
if ($finalTokens[$y]['code'] === T_CLOSE_PARENTHESIS) {
339
// Join all the content together inside the url() statement.
341
for ($i = ($x + 2); $i < $numTokens; $i++) {
342
if ($finalTokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
346
$newContent .= $finalTokens[$i]['content'];
347
unset($finalTokens[$i]);
350
// If the content inside the "url()" is in double quotes
351
// there will only be one token and so we don't have to do
352
// anything except change its type. If it is not empty,
353
// we need to do some token merging.
354
$finalTokens[($x + 1)]['type'] = 'T_URL';
355
$finalTokens[($x + 1)]['code'] = T_URL;
357
if ($newContent !== '') {
358
$finalTokens[($x + 1)]['content'] .= $newContent;
360
$finalTokens = array_values($finalTokens);
361
$numTokens = count($finalTokens);
367
// Nothing special to be done with this token.
372
if (PHP_CODESNIFFER_VERBOSITY > 1) {
373
echo "\t*** END CSS TOKENIZING ***".PHP_EOL;
378
}//end tokenizeString()
382
* Performs additional processing after main tokenizing.
384
* @param array &$tokens The array of tokens to process.
385
* @param string $eolChar The EOL character to use for splitting strings.
389
public function processAdditional(&$tokens, $eolChar)
391
// We override this method because we don't want the PHP version to
392
// run during CSS processing because it is wasted processing time.
394
}//end processAdditional()