PHP_CodeSniffer
[ class tree: PHP_CodeSniffer ] [ index: PHP_CodeSniffer ] [ all elements ]

Source for file CSS.php

Documentation is available at CSS.php

  1. <?php
  2. /**
  3.  * Tokenizes CSS code.
  4.  *
  5.  * @author    Greg Sherwood <gsherwood@squiz.net>
  6.  * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7.  * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8.  */
  9.  
  10. namespace PHP_CodeSniffer\Tokenizers;
  11.  
  12. use PHP_CodeSniffer\Util;
  13. use PHP_CodeSniffer\Config;
  14. use PHP_CodeSniffer\Exceptions\TokenizerException;
  15.  
  16. class CSS extends PHP
  17. {
  18.  
  19.  
  20.     /**
  21.      * Initialise the tokenizer.
  22.      *
  23.      * Pre-checks the content to see if it looks minified.
  24.      *
  25.      * @param string                  $content The content to tokenize,
  26.      * @param \PHP_CodeSniffer\Config $config  The config data for the run.
  27.      * @param string                  $eolChar The EOL char used in the content.
  28.      *
  29.      * @return void 
  30.      * @throws TokenizerException If the file appears to be minified.
  31.      */
  32.     public function __construct($contentConfig $config$eolChar='\n')
  33.     {
  34.         if ($this->isMinifiedContent($content$eolChar=== true{
  35.             throw new TokenizerException('File appears to be minified and cannot be processed');
  36.         }
  37.  
  38.         return parent::__construct($content$config$eolChar);
  39.  
  40.     }//end __construct()
  41.  
  42.  
  43.     /**
  44.      * Creates an array of tokens when given some CSS code.
  45.      *
  46.      * Uses the PHP tokenizer to do all the tricky work
  47.      *
  48.      * @param string $string The string to tokenize.
  49.      *
  50.      * @return array 
  51.      */
  52.     public function tokenize($string)
  53.     {
  54.         if (PHP_CODESNIFFER_VERBOSITY > 1{
  55.             echo "\t*** START CSS TOKENIZING 1ST PASS ***".PHP_EOL;
  56.         }
  57.  
  58.         // If the content doesn't have an EOL char on the end, add one so
  59.         // the open and close tags we add are parsed correctly.
  60.         $eolAdded = false;
  61.         if (substr($string(strlen($this->eolChar* -1)) !== $this->eolChar{
  62.             $string  .= $this->eolChar;
  63.             $eolAdded = true;
  64.         }
  65.  
  66.         $string str_replace('<?php''^PHPCS_CSS_T_OPEN_TAG^'$string);
  67.         $string str_replace('?>''^PHPCS_CSS_T_CLOSE_TAG^'$string);
  68.         $tokens = parent::tokenize('<?php '.$string.'?>');
  69.  
  70.         $finalTokens    = array();
  71.         $finalTokens[0= array(
  72.                            'code'    => T_OPEN_TAG,
  73.                            'type'    => 'T_OPEN_TAG',
  74.                            'content' => '',
  75.                           );
  76.  
  77.         $newStackPtr      = 1;
  78.         $numTokens        count($tokens);
  79.         $multiLineComment = false;
  80.         for ($stackPtr = 1; $stackPtr $numTokens$stackPtr++{
  81.             $token $tokens[$stackPtr];
  82.  
  83.             // CSS files don't have lists, breaks etc, so convert these to
  84.             // standard strings early so they can be converted into T_STYLE
  85.             // tokens and joined with other strings if needed.
  86.             if ($token['code'=== T_BREAK
  87.                 || $token['code'=== T_LIST
  88.                 || $token['code'=== T_DEFAULT
  89.                 || $token['code'=== T_SWITCH
  90.                 || $token['code'=== T_FOR
  91.                 || $token['code'=== T_FOREACH
  92.                 || $token['code'=== T_WHILE
  93.             {
  94.                 $token['type''T_STRING';
  95.                 $token['code'= T_STRING;
  96.             }
  97.  
  98.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  99.                 $type    $token['type'];
  100.                 $content = Util\Common::prepareForOutput($token['content']);
  101.                 echo "\tProcess token $stackPtr$type => $content".PHP_EOL;
  102.             }
  103.  
  104.             if ($token['code'=== T_BITWISE_XOR
  105.                 && $tokens[($stackPtr + 1)]['content'=== 'PHPCS_CSS_T_OPEN_TAG'
  106.             {
  107.                 $content '<?php';
  108.                 for ($stackPtr ($stackPtr + 3)$stackPtr $numTokens$stackPtr++{
  109.                     if ($tokens[$stackPtr]['code'=== T_BITWISE_XOR
  110.                         && $tokens[($stackPtr + 1)]['content'=== 'PHPCS_CSS_T_CLOSE_TAG'
  111.                     {
  112.                         // Add the end tag and ignore the * we put at the end.
  113.                         $content  .= '?>';
  114.                         $stackPtr += 2;
  115.                         break;
  116.                     else {
  117.                         $content .= $tokens[$stackPtr]['content'];
  118.                     }
  119.                 }
  120.  
  121.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  122.                     echo "\t\t=> Found embedded PHP code: ";
  123.                     $cleanContent = Util\Common::prepareForOutput($content);
  124.                     echo $cleanContent.PHP_EOL;
  125.                 }
  126.  
  127.                 $finalTokens[$newStackPtr= array(
  128.                                               'type'    => 'T_EMBEDDED_PHP',
  129.                                               'code'    => T_EMBEDDED_PHP,
  130.                                               'content' => $content,
  131.                                              );
  132.  
  133.                 $newStackPtr++;
  134.                 continue;
  135.             }//end if
  136.  
  137.             if ($token['code'=== T_GOTO_LABEL{
  138.                 // Convert these back to T_STRING followed by T_COLON so we can
  139.                 // more easily process style definitions.
  140.                 $finalTokens[$newStackPtr= array(
  141.                                               'type'    => 'T_STRING',
  142.                                               'code'    => T_STRING,
  143.                                               'content' => substr($token['content']0-1),
  144.                                              );
  145.                 $newStackPtr++;
  146.                 $finalTokens[$newStackPtr= array(
  147.                                               'type'    => 'T_COLON',
  148.                                               'code'    => T_COLON,
  149.                                               'content' => ':',
  150.                                              );
  151.                 $newStackPtr++;
  152.                 continue;
  153.             }
  154.  
  155.             if ($token['code'=== T_FUNCTION{
  156.                 // There are no functions in CSS, so convert this to a string.
  157.                 $finalTokens[$newStackPtr= array(
  158.                                               'type'    => 'T_STRING',
  159.                                               'code'    => T_STRING,
  160.                                               'content' => $token['content'],
  161.                                              );
  162.  
  163.                 $newStackPtr++;
  164.                 continue;
  165.             }
  166.  
  167.             if ($token['code'=== T_COMMENT
  168.                 && substr($token['content']02=== '/*'
  169.             {
  170.                 // Multi-line comment. Record it so we can ignore other
  171.                 // comment tags until we get out of this one.
  172.                 $multiLineComment = true;
  173.             }
  174.  
  175.             if ($token['code'=== T_COMMENT
  176.                 && $multiLineComment === false
  177.                 && (substr($token['content']02=== '//'
  178.                 || $token['content']{0=== '#')
  179.             {
  180.                 $content ltrim($token['content']'#/');
  181.  
  182.                 // Guard against PHP7+ syntax errors by stripping
  183.                 // leading zeros so the content doesn't look like an invalid int.
  184.                 $leadingZero = false;
  185.                 if ($content{0=== '0'{
  186.                     $content     '1'.$content;
  187.                     $leadingZero = true;
  188.                 }
  189.  
  190.                 $commentTokens = parent::tokenize('<?php '.$content.'?>');
  191.  
  192.                 // The first and last tokens are the open/close tags.
  193.                 array_shift($commentTokens);
  194.                 array_pop($commentTokens);
  195.  
  196.                 if ($leadingZero === true{
  197.                     $commentTokens[0]['content'substr($commentTokens[0]['content']1);
  198.                     $content substr($content1);
  199.                 }
  200.  
  201.                 if ($token['content']{0=== '#'{
  202.                     // The # character is not a comment in CSS files, so
  203.                     // determine what it means in this context.
  204.                     $firstContent $commentTokens[0]['content'];
  205.  
  206.                     // If the first content is just a number, it is probably a
  207.                     // colour like 8FB7DB, which PHP splits into 8 and FB7DB.
  208.                     if (($commentTokens[0]['code'=== T_LNUMBER
  209.                         || $commentTokens[0]['code'=== T_DNUMBER)
  210.                         && $commentTokens[1]['code'=== T_STRING
  211.                     {
  212.                         $firstContent .= $commentTokens[1]['content'];
  213.                         array_shift($commentTokens);
  214.                     }
  215.  
  216.                     // If the first content looks like a colour and not a class
  217.                     // definition, join the tokens together.
  218.                     if (preg_match('/^[ABCDEF0-9]+$/i'$firstContent=== 1
  219.                         && $commentTokens[1]['content'!== '-'
  220.                     {
  221.                         array_shift($commentTokens);
  222.                         // Work out what we trimmed off above and remember to re-add it.
  223.                         $trimmed substr($token['content']0(strlen($token['content']strlen($content)));
  224.                         $finalTokens[$newStackPtr= array(
  225.                                                       'type'    => 'T_COLOUR',
  226.                                                       'code'    => T_COLOUR,
  227.                                                       'content' => $trimmed.$firstContent,
  228.                                                      );
  229.                     else {
  230.                         $finalTokens[$newStackPtr= array(
  231.                                                       'type'    => 'T_HASH',
  232.                                                       'code'    => T_HASH,
  233.                                                       'content' => '#',
  234.                                                      );
  235.                     }
  236.                 else {
  237.                     $finalTokens[$newStackPtr= array(
  238.                                                   'type'    => 'T_STRING',
  239.                                                   'code'    => T_STRING,
  240.                                                   'content' => '//',
  241.                                                  );
  242.                 }//end if
  243.  
  244.                 $newStackPtr++;
  245.  
  246.                 array_splice($tokens$stackPtr1$commentTokens);
  247.                 $numTokens count($tokens);
  248.                 $stackPtr--;
  249.                 continue;
  250.             }//end if
  251.  
  252.             if ($token['code'=== T_COMMENT
  253.                 && substr($token['content']-2=== '*/'
  254.             {
  255.                 // Multi-line comment is done.
  256.                 $multiLineComment = false;
  257.             }
  258.  
  259.             $finalTokens[$newStackPtr$token;
  260.             $newStackPtr++;
  261.         }//end for
  262.  
  263.         if (PHP_CODESNIFFER_VERBOSITY > 1{
  264.             echo "\t*** END CSS TOKENIZING 1ST PASS ***".PHP_EOL;
  265.             echo "\t*** START CSS TOKENIZING 2ND PASS ***".PHP_EOL;
  266.         }
  267.  
  268.         // A flag to indicate if we are inside a style definition,
  269.         // which is defined using curly braces.
  270.         $inStyleDef = false;
  271.  
  272.         // A flag to indicate if an At-rule like "@media" is used, which will result
  273.         // in nested curly brackets.
  274.         $asperandStart = false;
  275.  
  276.         $numTokens count($finalTokens);
  277.         for ($stackPtr = 0; $stackPtr $numTokens$stackPtr++{
  278.             $token $finalTokens[$stackPtr];
  279.  
  280.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  281.                 $type    $token['type'];
  282.                 $content = Util\Common::prepareForOutput($token['content']);
  283.                 echo "\tProcess token $stackPtr$type => $content".PHP_EOL;
  284.             }
  285.  
  286.             switch ($token['code']{
  287.             case T_OPEN_CURLY_BRACKET:
  288.                 // Opening curly brackets for an At-rule do not start a style
  289.                 // definition. We also reset the asperand flag here because the next
  290.                 // opening curly bracket could be indeed the start of a style
  291.                 // definition.
  292.                 if ($asperandStart === true{
  293.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  294.                         if ($inStyleDef === true{
  295.                             echo "\t\t* style definition closed *".PHP_EOL;
  296.                         }
  297.  
  298.                         if ($asperandStart === true{
  299.                             echo "\t\t* at-rule definition closed *".PHP_EOL;
  300.                         }
  301.                     }
  302.  
  303.                     $inStyleDef    = false;
  304.                     $asperandStart = false;
  305.                 else {
  306.                     $inStyleDef = true;
  307.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  308.                         echo "\t\t* style definition opened *".PHP_EOL;
  309.                     }
  310.                 }
  311.                 break;
  312.             case T_CLOSE_CURLY_BRACKET:
  313.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  314.                     if ($inStyleDef === true{
  315.                         echo "\t\t* style definition closed *".PHP_EOL;
  316.                     }
  317.  
  318.                     if ($asperandStart === true{
  319.                         echo "\t\t* at-rule definition closed *".PHP_EOL;
  320.                     }
  321.                 }
  322.  
  323.                 $inStyleDef    = false;
  324.                 $asperandStart = false;
  325.                 break;
  326.             case T_MINUS:
  327.                 // Minus signs are often used instead of spaces inside
  328.                 // class names, IDs and styles.
  329.                 if ($finalTokens[($stackPtr + 1)]['code'=== T_STRING{
  330.                     if ($finalTokens[($stackPtr - 1)]['code'=== T_STRING{
  331.                         $newContent $finalTokens[($stackPtr - 1)]['content'].'-'.$finalTokens[($stackPtr + 1)]['content'];
  332.  
  333.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  334.                             echo "\t\t* token is a string joiner; ignoring this and previous token".PHP_EOL;
  335.                             $old = Util\Common::prepareForOutput($finalTokens[($stackPtr + 1)]['content']);
  336.                             $new = Util\Common::prepareForOutput($newContent);
  337.                             echo "\t\t=> token ".($stackPtr + 1)." content changed from \"$old\" to \"$new\"".PHP_EOL;
  338.                         }
  339.  
  340.                         $finalTokens[($stackPtr + 1)]['content'$newContent;
  341.                         unset($finalTokens[$stackPtr]);
  342.                         unset($finalTokens[($stackPtr - 1)]);
  343.                     else {
  344.                         $newContent '-'.$finalTokens[($stackPtr + 1)]['content'];
  345.  
  346.                         $finalTokens[($stackPtr + 1)]['content'$newContent;
  347.                         unset($finalTokens[$stackPtr]);
  348.                     }
  349.                 else if ($finalTokens[($stackPtr + 1)]['code'=== T_LNUMBER{
  350.                     // They can also be used to provide negative numbers.
  351.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  352.                         echo "\t\t* token is part of a negative number; adding content to next token and ignoring *".PHP_EOL;
  353.                         $content = Util\Common::prepareForOutput($finalTokens[($stackPtr + 1)]['content']);
  354.                         echo "\t\t=> token ".($stackPtr + 1)." content changed from \"$content\" to \"-$content\"".PHP_EOL;
  355.                     }
  356.  
  357.                     $finalTokens[($stackPtr + 1)]['content''-'.$finalTokens[($stackPtr + 1)]['content'];
  358.                     unset($finalTokens[$stackPtr]);
  359.                 }//end if
  360.  
  361.                 break;
  362.             case T_COLON:
  363.                 // Only interested in colons that are defining styles.
  364.                 if ($inStyleDef === false{
  365.                     break;
  366.                 }
  367.  
  368.                 for ($x ($stackPtr - 1)$x >= 0; $x--{
  369.                     if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]=== false{
  370.                         break;
  371.                     }
  372.                 }
  373.  
  374.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  375.                     $type $finalTokens[$x]['type'];
  376.                     echo "\t\t=> token $x changed from $type to T_STYLE".PHP_EOL;
  377.                 }
  378.  
  379.                 $finalTokens[$x]['type''T_STYLE';
  380.                 $finalTokens[$x]['code'T_STYLE;
  381.                 break;
  382.             case T_STRING:
  383.                 if (strtolower($token['content']=== 'url'{
  384.                     // Find the next content.
  385.                     for ($x ($stackPtr + 1)$x $numTokens$x++{
  386.                         if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]=== false{
  387.                             break;
  388.                         }
  389.                     }
  390.  
  391.                     // Needs to be in the format "url(" for it to be a URL.
  392.                     if ($finalTokens[$x]['code'!== T_OPEN_PARENTHESIS{
  393.                         continue;
  394.                     }
  395.  
  396.                     // Make sure the content isn't empty.
  397.                     for ($y ($x + 1)$y $numTokens$y++{
  398.                         if (isset(Util\Tokens::$emptyTokens[$finalTokens[$y]['code']]=== false{
  399.                             break;
  400.                         }
  401.                     }
  402.  
  403.                     if ($finalTokens[$y]['code'=== T_CLOSE_PARENTHESIS{
  404.                         continue;
  405.                     }
  406.  
  407.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  408.                         for ($i ($stackPtr + 1)$i <= $y$i++{
  409.                             $type    $finalTokens[$i]['type'];
  410.                             $content = Util\Common::prepareForOutput($finalTokens[$i]['content']);
  411.                             echo "\tProcess token $i$type => $content".PHP_EOL;
  412.                         }
  413.  
  414.                         echo "\t\t* token starts a URL *".PHP_EOL;
  415.                     }
  416.  
  417.                     // Join all the content together inside the url() statement.
  418.                     $newContent '';
  419.                     for ($i ($x + 2)$i $numTokens$i++{
  420.                         if ($finalTokens[$i]['code'=== T_CLOSE_PARENTHESIS{
  421.                             break;
  422.                         }
  423.  
  424.                         $newContent .= $finalTokens[$i]['content'];
  425.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  426.                             $content = Util\Common::prepareForOutput($finalTokens[$i]['content']);
  427.                             echo "\t\t=> token $i added to URL string and ignored: $content".PHP_EOL;
  428.                         }
  429.  
  430.                         unset($finalTokens[$i]);
  431.                     }
  432.  
  433.                     $stackPtr $i;
  434.  
  435.                     // If the content inside the "url()" is in double quotes
  436.                     // there will only be one token and so we don't have to do
  437.                     // anything except change its type. If it is not empty,
  438.                     // we need to do some token merging.
  439.                     $finalTokens[($x + 1)]['type''T_URL';
  440.                     $finalTokens[($x + 1)]['code'T_URL;
  441.  
  442.                     if ($newContent !== ''{
  443.                         $finalTokens[($x + 1)]['content'.= $newContent;
  444.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  445.                             $content = Util\Common::prepareForOutput($finalTokens[($x + 1)]['content']);
  446.                             echo "\t\t=> token content changed to: $content".PHP_EOL;
  447.                         }
  448.                     }
  449.                 }//end if
  450.  
  451.                 break;
  452.             case T_ASPERAND:
  453.                 $asperandStart = true;
  454.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  455.                     echo "\t\t* at-rule definition opened *".PHP_EOL;
  456.                 }
  457.                 break;
  458.             default:
  459.                 // Nothing special to be done with this token.
  460.                 break;
  461.             }//end switch
  462.         }//end for
  463.  
  464.         // Reset the array keys to avoid gaps.
  465.         $finalTokens array_values($finalTokens);
  466.         $numTokens   count($finalTokens);
  467.  
  468.         // Blank out the content of the end tag.
  469.         $finalTokens[($numTokens - 1)]['content''';
  470.  
  471.         if ($eolAdded === true{
  472.             // Strip off the extra EOL char we added for tokenizing.
  473.             $finalTokens[($numTokens - 2)]['content'substr(
  474.                 $finalTokens[($numTokens - 2)]['content'],
  475.                 0,
  476.                 (strlen($this->eolChar* -1)
  477.             );
  478.  
  479.             if ($finalTokens[($numTokens - 2)]['content'=== ''{
  480.                 unset($finalTokens[($numTokens - 2)]);
  481.                 $finalTokens array_values($finalTokens);
  482.                 $numTokens   count($finalTokens);
  483.             }
  484.         }
  485.  
  486.         if (PHP_CODESNIFFER_VERBOSITY > 1{
  487.             echo "\t*** END CSS TOKENIZING 2ND PASS ***".PHP_EOL;
  488.         }
  489.  
  490.         return $finalTokens;
  491.  
  492.     }//end tokenize()
  493.  
  494.  
  495.     /**
  496.      * Performs additional processing after main tokenizing.
  497.      *
  498.      * @return void 
  499.      */
  500.     public function processAdditional()
  501.     {
  502.         /*
  503.             We override this method because we don't want the PHP version to
  504.             run during CSS processing because it is wasted processing time.
  505.         */
  506.  
  507.     }//end processAdditional()
  508.  
  509.  
  510. }//end class

Documentation generated on Mon, 11 Mar 2019 14:51:11 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.