/** * format() in font-face needs quoted values for somes browser (FF at least) * * @param $value * @return string */ function quote_font_format($value) { if (strncmp($value, 'format', 6) == 0) { $p = strrpos($value, ")"); $end = substr($value, $p); $format_strings = csstidy::parse_string_list(substr($value, 7, $p - 7)); if (!$format_strings) { $value = ""; } else { $value = "format("; foreach ($format_strings as $format_string) { $value .= '"' . str_replace('"', '\\"', $format_string) . '",'; } $value = substr($value, 0, -1) . $end; } } return $value; }
/** * Parses CSS in $string. The code is saved as array in $this->css * @param string $string the CSS code * @access public * @return bool * @version 1.1 */ 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']; $quoted_string_properties =& $GLOBALS['csstidy']['quoted_string_properties']; $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 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]; } else { if (ctype_space($string[$i]) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) { $this->selector .= $string[$i]; } } } break; /* Case in-property */ /* 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 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) && !($string[$i] == ',' && !ctype_space($string[$i + 1]))) { 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'; $this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties); } 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') { $this->status = 'is'; switch ($this->selector) { case '@charset': /* Add quotes to charset */ $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"'; $this->charset = $this->sub_value_arr[0]; break; case '@namespace': /* Add quotes to namespace */ $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"'; $this->namespace = implode(' ', $this->sub_value_arr); break; case '@import': $this->sub_value = trim($this->sub_value); if (empty($this->sub_value_arr)) { // Quote URLs in imports only if they're not already inside url() and not already quoted. if (substr($this->sub_value, 0, 4) != 'url(') { if (!($this->sub_value[0] == substr($this->sub_value, -1) && in_array($this->sub_value[0], array("'", '"')))) { $this->sub_value = '"' . $this->sub_value . '"'; } } } $this->sub_value_arr[] = $this->sub_value; $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 != '') { if (substr($this->sub_value, 0, 6) == 'format') { $format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1)); if (!$format_strings) { $this->sub_value = ""; } else { $this->sub_value = "format("; foreach ($format_strings as $format_string) { $this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",'; } $this->sub_value = substr($this->sub_value, 0, -1) . ")"; } } if ($this->sub_value != '') { $this->sub_value_arr[] = $this->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->value .= ' ' . 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]) || $string[$i] == ',') { $this->optimise->subvalue(); if ($this->sub_value != '') { $this->sub_value_arr[] = $this->sub_value; $this->sub_value = ''; } } } break; /* Case in string */ /* Case in string */ case 'instr': $_str_char = $this->str_char[count($this->str_char) - 1]; $_cur_string = $this->cur_string[count($this->cur_string) - 1]; $temp_add = $string[$i]; // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but // parentheticals can be nested more than once. if ($_str_char === ")" && ($string[$i] === "(" || $string[$i] === '"' || $string[$i] === '\'') && !csstidy::escaped($string, $i)) { $this->cur_string[] = $string[$i]; $this->str_char[] = $string[$i] == "(" ? ")" : $string[$i]; $this->from[] = 'instr'; $this->quoted_string[] = !($string[$i] === "("); continue; } if ($_str_char !== ")" && ($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'); } $_cur_string .= $temp_add; if ($string[$i] === $_str_char && !csstidy::escaped($string, $i)) { $_quoted_string = array_pop($this->quoted_string); $this->status = array_pop($this->from); if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') { if (!$_quoted_string) { if ($_str_char !== ')') { // Convert properties like // font-family: 'Arial'; // to // font-family: Arial; // or // url("abc") // to // url(abc) $_cur_string = substr($_cur_string, 1, -1); } } else { $_quoted_string = false; } } array_pop($this->cur_string); array_pop($this->str_char); if ($_str_char === ")") { $_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")"; } if ($this->status === 'iv') { if (!$_quoted_string) { if (strpos($_cur_string, ',') !== false) { // we can on only remove space next to ',' $_cur_string = implode(',', array_map('trim', explode(',', $_cur_string))); } // and multiple spaces (too expensive) if (strpos($_cur_string, ' ') !== false) { $_cur_string = preg_replace(",\\s+,", " ", $_cur_string); } } $this->sub_value .= $_cur_string; } elseif ($this->status === 'is') { $this->selector .= $_cur_string; } elseif ($this->status === 'instr') { $this->cur_string[count($this->cur_string) - 1] .= $_cur_string; } } else { $this->cur_string[count($this->cur_string) - 1] = $_cur_string; } break; /* Case in-comment */ /* Case in-comment */ case 'ic': if ($string[$i] === '*' && $string[$i + 1] === '/') { $this->status = array_pop($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)); }