You are here

function csstidy::parse in Advanced CSS/JS Aggregation 6

Parses CSS in $string. The code is saved as array in $this->css

@access public

@version 1.1

Parameters

string $string the CSS code:

Return value

bool

1 call to csstidy::parse()
csstidy::parse_from_url in advagg_css_compress/csstidy/class.csstidy.inc
Starts parsing from URL

File

advagg_css_compress/csstidy/class.csstidy.inc, line 550

Class

csstidy
CSS Parser class

Code

function parse($string) {

  // Temporarily set locale to en_US in order to handle floats properly
  $old = @setlocale(LC_ALL, 0);
  @setlocale(LC_ALL, 'C');

  // PHP bug? Settings need to be refreshed in PHP4
  $this->print = new csstidy_print($this);
  $this->optimise = new csstidy_optimise($this);
  $all_properties =& $GLOBALS['csstidy']['all_properties'];
  $at_rules =& $GLOBALS['csstidy']['at_rules'];
  $this->css = array();
  $this->print->input_css = $string;
  $string = str_replace("\r\n", "\n", $string) . ' ';
  $cur_comment = '';
  for ($i = 0, $size = strlen($string); $i < $size; $i++) {
    if ($string[$i] === "\n" || $string[$i] === "\r") {
      ++$this->line;
    }
    switch ($this->status) {

      /* Case in at-block */
      case 'at':
        if (csstidy::is_token($string, $i)) {
          if ($string[$i] === '/' && @$string[$i + 1] === '*') {
            $this->status = 'ic';
            ++$i;
            $this->from = 'at';
          }
          elseif ($string[$i] === '{') {
            $this->status = 'is';
            $this->at = $this
              ->css_new_media_section($this->at);
            $this
              ->_add_token(AT_START, $this->at);
          }
          elseif ($string[$i] === ',') {
            $this->at = trim($this->at) . ',';
          }
          elseif ($string[$i] === '\\') {
            $this->at .= $this
              ->_unicode($string, $i);
          }
          elseif (in_array($string[$i], array(
            '(',
            ')',
            ':',
          ))) {
            $this->at .= $string[$i];
          }
        }
        else {
          $lastpos = strlen($this->at) - 1;
          if (!((ctype_space($this->at[$lastpos]) || csstidy::is_token($this->at, $lastpos) && $this->at[$lastpos] === ',') && ctype_space($string[$i]))) {
            $this->at .= $string[$i];
          }
        }
        break;

      /* Case in-selector */
      case 'is':
        if (csstidy::is_token($string, $i)) {
          if ($string[$i] === '/' && @$string[$i + 1] === '*' && trim($this->selector) == '') {
            $this->status = 'ic';
            ++$i;
            $this->from = 'is';
          }
          elseif ($string[$i] === '@' && trim($this->selector) == '') {

            // Check for at-rule
            $this->invalid_at = true;
            foreach ($at_rules as $name => $type) {
              if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) {
                $type === 'at' ? $this->at = '@' . $name : ($this->selector = '@' . $name);
                $this->status = $type;
                $i += strlen($name);
                $this->invalid_at = false;
              }
            }
            if ($this->invalid_at) {
              $this->selector = '@';
              $invalid_at_name = '';
              for ($j = $i + 1; $j < $size; ++$j) {
                if (!ctype_alpha($string[$j])) {
                  break;
                }
                $invalid_at_name .= $string[$j];
              }
              $this
                ->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning');
            }
          }
          elseif ($string[$i] === '"' || $string[$i] === "'") {
            $this->cur_string = $string[$i];
            $this->status = 'instr';
            $this->str_char = $string[$i];
            $this->from = 'is';

            /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
            $this->quoted_string = $string[$i - 1] == '=';
          }
          elseif ($this->invalid_at && $string[$i] === ';') {
            $this->invalid_at = false;
            $this->status = 'is';
          }
          elseif ($string[$i] === '{') {
            $this->status = 'ip';
            if ($this->at == '') {
              $this->at = $this
                ->css_new_media_section(DEFAULT_AT);
            }
            $this->selector = $this
              ->css_new_selector($this->at, $this->selector);
            $this
              ->_add_token(SEL_START, $this->selector);
            $this->added = false;
          }
          elseif ($string[$i] === '}') {
            $this
              ->_add_token(AT_END, $this->at);
            $this->at = '';
            $this->selector = '';
            $this->sel_separate = array();
          }
          elseif ($string[$i] === ',') {
            $this->selector = trim($this->selector) . ',';
            $this->sel_separate[] = strlen($this->selector);
          }
          elseif ($string[$i] === '\\') {
            $this->selector .= $this
              ->_unicode($string, $i);
          }
          elseif ($string[$i] === '*' && @in_array($string[$i + 1], array(
            '.',
            '#',
            '[',
            ':',
          ))) {

            // remove unnecessary universal selector, FS#147
          }
          else {
            $this->selector .= $string[$i];
          }
        }
        else {
          $lastpos = strlen($this->selector) - 1;
          if ($lastpos == -1 || !((ctype_space($this->selector[$lastpos]) || csstidy::is_token($this->selector, $lastpos) && $this->selector[$lastpos] === ',') && ctype_space($string[$i]))) {
            $this->selector .= $string[$i];
          }
        }
        break;

      /* Case in-property */
      case 'ip':
        if (csstidy::is_token($string, $i)) {
          if (($string[$i] === ':' || $string[$i] === '=') && $this->property != '') {
            $this->status = 'iv';
            if (!$this
              ->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) {
              $this->property = $this
                ->css_new_property($this->at, $this->selector, $this->property);
              $this
                ->_add_token(PROPERTY, $this->property);
            }
          }
          elseif ($string[$i] === '/' && @$string[$i + 1] === '*' && $this->property == '') {
            $this->status = 'ic';
            ++$i;
            $this->from = 'ip';
          }
          elseif ($string[$i] === '}') {
            $this
              ->explode_selectors();
            $this->status = 'is';
            $this->invalid_at = false;
            $this
              ->_add_token(SEL_END, $this->selector);
            $this->selector = '';
            $this->property = '';
          }
          elseif ($string[$i] === ';') {
            $this->property = '';
          }
          elseif ($string[$i] === '\\') {
            $this->property .= $this
              ->_unicode($string, $i);
          }
          elseif ($this->property == '' and !ctype_space($string[$i])) {
            $this->property .= $string[$i];
          }
        }
        elseif (!ctype_space($string[$i])) {
          $this->property .= $string[$i];
        }
        break;

      /* Case in-value */
      case 'iv':
        $pn = ($string[$i] === "\n" || $string[$i] === "\r") && $this
          ->property_is_next($string, $i + 1) || $i == strlen($string) - 1;
        if (csstidy::is_token($string, $i) || $pn) {
          if ($string[$i] === '/' && @$string[$i + 1] === '*') {
            $this->status = 'ic';
            ++$i;
            $this->from = 'iv';
          }
          elseif ($string[$i] === '"' || $string[$i] === "'" || $string[$i] === '(') {
            $this->cur_string = $string[$i];
            $this->str_char = $string[$i] === '(' ? ')' : $string[$i];
            $this->status = 'instr';
            $this->from = 'iv';
          }
          elseif ($string[$i] === ',') {
            $this->sub_value = trim($this->sub_value) . ',';
          }
          elseif ($string[$i] === '\\') {
            $this->sub_value .= $this
              ->_unicode($string, $i);
          }
          elseif ($string[$i] === ';' || $pn) {
            if ($this->selector[0] === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') {

              /* Add quotes to charset, import, namespace */
              $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
              $this->status = 'is';
              switch ($this->selector) {
                case '@charset':
                  $this->charset = $this->sub_value_arr[0];
                  break;
                case '@namespace':
                  $this->namespace = implode(' ', $this->sub_value_arr);
                  break;
                case '@import':
                  $this->import[] = implode(' ', $this->sub_value_arr);
                  break;
              }
              $this->sub_value_arr = array();
              $this->sub_value = '';
              $this->selector = '';
              $this->sel_separate = array();
            }
            else {
              $this->status = 'ip';
            }
          }
          elseif ($string[$i] !== '}') {
            $this->sub_value .= $string[$i];
          }
          if (($string[$i] === '}' || $string[$i] === ';' || $pn) && !empty($this->selector)) {
            if ($this->at == '') {
              $this->at = $this
                ->css_new_media_section(DEFAULT_AT);
            }

            // case settings
            if ($this
              ->get_cfg('lowercase_s')) {
              $this->selector = strtolower($this->selector);
            }
            $this->property = strtolower($this->property);
            $this->optimise
              ->subvalue();
            if ($this->sub_value != '') {

              /* original, disabled for fix below
                 if (substr($this->sub_value, 0, 6) == 'format') {
                 $this->sub_value = str_replace(array('format(', ')'), array('format("', '")'), $this->sub_value);
                 }//*/
              $this->sub_value_arr[] = $this->sub_value;

              // [FIX] @font-face with multiple fonts and CSSTidy
              // (http://www.pixelastic.com/blog/86:csstidy-and-the-woff-fonts)
              foreach ($this->sub_value_arr as $sub_value) {
                if (substr($sub_value, 0, 6) == 'format') {
                  $sub_value = str_replace(array(
                    'format(',
                    ')',
                  ), array(
                    'format("',
                    '")',
                  ), $sub_value);
                }
              }
              $this->sub_value = '';
            }
            $this->value = array_shift($this->sub_value_arr);
            while (count($this->sub_value_arr)) {
              $this->value .= (substr($this->value, -1, 1) == ',' ? '' : ' ') . array_shift($this->sub_value_arr);
            }
            $this->optimise
              ->value();
            $valid = csstidy::property_is_valid($this->property);
            if ((!$this->invalid_at || $this
              ->get_cfg('preserve_css')) && (!$this
              ->get_cfg('discard_invalid_properties') || $valid)) {
              $this
                ->css_add_property($this->at, $this->selector, $this->property, $this->value);
              $this
                ->_add_token(VALUE, $this->value);
              $this->optimise
                ->shorthands();
            }
            if (!$valid) {
              if ($this
                ->get_cfg('discard_invalid_properties')) {
                $this
                  ->log('Removed invalid property: ' . $this->property, 'Warning');
              }
              else {
                $this
                  ->log('Invalid property in ' . strtoupper($this
                  ->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
              }
            }
            $this->property = '';
            $this->sub_value_arr = array();
            $this->value = '';
          }
          if ($string[$i] === '}') {
            $this
              ->explode_selectors();
            $this
              ->_add_token(SEL_END, $this->selector);
            $this->status = 'is';
            $this->invalid_at = false;
            $this->selector = '';
          }
        }
        elseif (!$pn) {
          $this->sub_value .= $string[$i];
          if (ctype_space($string[$i])) {
            $this->optimise
              ->subvalue();
            if ($this->sub_value != '') {
              $this->sub_value_arr[] = $this->sub_value;
              $this->sub_value = '';
            }
          }
        }
        break;

      /* Case in string */
      case 'instr':
        if ($this->str_char === ')' && ($string[$i] === '"' || $string[$i] === '\'') && !$this->str_in_str && !csstidy::escaped($string, $i)) {
          $this->str_in_str = true;
        }
        elseif ($this->str_char === ')' && ($string[$i] === '"' || $string[$i] === '\'') && $this->str_in_str && !csstidy::escaped($string, $i)) {
          $this->str_in_str = false;
        }
        $temp_add = $string[$i];

        // ...and no not-escaped backslash at the previous position
        if (($string[$i] === "\n" || $string[$i] === "\r") && !($string[$i - 1] === '\\' && !csstidy::escaped($string, $i - 1))) {
          $temp_add = "\\A ";
          $this
            ->log('Fixed incorrect newline in string', 'Warning');
        }

        // this optimisation remove space in css3 properties (see vendor-prefixed/webkit-gradient.csst)

        #if (!($this->str_char === ')' && in_array($string{$i}, $GLOBALS['csstidy']['whitespace']) && !$this->str_in_str)) {
        $this->cur_string .= $temp_add;

        #}
        if ($string[$i] == $this->str_char && !csstidy::escaped($string, $i) && !$this->str_in_str) {
          $this->status = $this->from;
          if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $this->cur_string) && $this->property !== 'content') {
            if (!$this->quoted_string) {
              if ($this->str_char === '"' || $this->str_char === '\'') {

                // Temporarily disable this optimization to avoid problems with @charset rule, quote properties, and some attribute selectors...
                // Attribute selectors fixed, added quotes to @chartset, no problems with properties detected. Enabled
                $this->cur_string = substr($this->cur_string, 1, -1);
              }
              else {
                if (strlen($this->cur_string) > 3 && ($this->cur_string[1] === '"' || $this->cur_string[1] === '\'')) {
                  $this->cur_string = $this->cur_string[0] . substr($this->cur_string, 2, -2) . substr($this->cur_string, -1);
                }
              }
            }
            else {
              $this->quoted_string = false;
            }
          }
          if ($this->from === 'iv') {
            if (!$this->quoted_string) {
              if (strpos($this->cur_string, ',') !== false) {

                // we can on only remove space next to ','
                $this->cur_string = implode(',', array_map('trim', explode(',', $this->cur_string)));
              }

              // and multiple spaces (too expensive)
              if (strpos($this->cur_string, '  ') !== false) {
                $this->cur_string = preg_replace(",\\s+,", " ", $this->cur_string);
              }
            }
            $this->sub_value .= $this->cur_string;
          }
          elseif ($this->from === 'is') {
            $this->selector .= $this->cur_string;
          }
        }
        break;

      /* Case in-comment */
      case 'ic':
        if ($string[$i] === '*' && $string[$i + 1] === '/') {
          $this->status = $this->from;
          $i++;
          $this
            ->_add_token(COMMENT, $cur_comment);
          $cur_comment = '';
        }
        else {
          $cur_comment .= $string[$i];
        }
        break;
    }
  }
  $this->optimise
    ->postparse();
  $this->print
    ->_reset();
  @setlocale(LC_ALL, $old);

  // Set locale back to original setting
  return;
  empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace);
}