MDL-33468 css_optimiser: Numerous fixes to the CSS optimiser and its tests

This commit is contained in:
Sam Hemelryk
2012-06-12 14:22:55 +12:00
parent f8dfdb524b
commit f2e8d3798c
2 changed files with 219 additions and 18 deletions
+115 -16
View File
@@ -352,7 +352,7 @@ function css_is_width($value) {
if (in_array(strtolower($value), array('auto', 'inherit'))) {
return true;
}
if (preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|%|in|cm|mm|ex|pc)?$#i', $value)) {
if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
return true;
}
return false;
@@ -501,10 +501,17 @@ class css_optimiser {
public function process($css) {
global $CFG;
$css = trim($css);
$this->reset_stats();
$this->timestart = microtime(true);
$this->rawstrlen = strlen($css);
if ($this->rawstrlen === 0) {
$this->errors[] = 'Skipping file as it has no content.';
return '';
}
// First up we need to remove all line breaks - this allows us to instantly
// reduce our processing requirements and as we will process everything
// into a new structure there's really nothing lost.
@@ -749,7 +756,7 @@ class css_optimiser {
$this->optimisedrules += $media->count_rules();
$this->optimisedselectors += $media->count_selectors();
if ($media->has_errors()) {
$this->errors[] = $media->get_errors();
$this->errors += $media->get_errors();
}
$css .= $media->out();
}
@@ -774,11 +781,20 @@ class css_optimiser {
'rawrules' => $this->rawrules,
'optimisedstrlen' => $this->optimisedstrlen,
'optimisedrules' => $this->optimisedrules,
'optimisedselectors' => $this->optimisedselectors,
'improvementstrlen' => round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%',
'improvementrules' => round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%',
'improvementselectors' => round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%',
'optimisedselectors' => $this->optimisedselectors,
'improvementstrlen' => '-',
'improvementrules' => '-',
'improvementselectors' => '-',
);
if ($this->rawstrlen > 0) {
$stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
}
if ($this->rawrules > 0) {
$stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
}
if ($this->rawselectors > 0) {
$stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
}
return $stats;
}
@@ -794,10 +810,16 @@ class css_optimiser {
/**
* Returns an array of errors that have occured
*
* @param bool $clear If set to true the errors will be cleared after being returned.
* @return array
*/
public function get_errors() {
return $this->errors;
public function get_errors($clear = false) {
$errors = $this->errors;
if ($clear) {
// Reset the error array
$this->errors = array();
}
return $errors;
}
/**
@@ -821,15 +843,22 @@ class css_optimiser {
* @return string
*/
public function output_stats_css() {
$stats = $this->get_stats();
$strlenimprovement = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1);
$ruleimprovement = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1);
$selectorimprovement = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1);
$timetaken = round($this->timecomplete - $this->timestart, 4);
$computedcss = "/****************************************\n";
$computedcss .= " *------- CSS Optimisation stats --------\n";
if ($this->rawstrlen === 0) {
$computedcss .= " File not processed as it has no content /\n\n";
$computedcss .= " ****************************************/\n\n";
return $computedcss;
} else if ($this->rawrules === 0) {
$computedcss .= " File contained no rules to be processed /\n\n";
$computedcss .= " ****************************************/\n\n";
return $computedcss;
}
$stats = $this->get_stats();
$computedcss .= " * ".date('r')."\n";
$computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
$computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
@@ -1169,6 +1198,7 @@ abstract class css_writer {
* @return string
*/
public static function style($name, $value, $important = false) {
$value = trim($value);
if ($important && strpos($value, '!important') === false) {
$value .= ' !important';
}
@@ -1657,7 +1687,7 @@ class css_media {
$errors[] = $rule->get_error_string();
}
}
return join("\n", $errors);
return $errors;
}
}
@@ -1745,6 +1775,7 @@ abstract class css_style {
$important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
if ($important) {
$value = substr($value, 0, -(strlen($matches[1])));
$value = rtrim($value);
}
if (!$this->important || $important) {
$this->value = $this->clean_value($value);
@@ -2233,7 +2264,7 @@ class css_style_border extends css_style {
$return = array();
if (count($bits) > 0) {
$width = array_shift($bits);
if (!css_is_width($width)) {
if (!css_style_borderwidth::is_border_width($width)) {
$width = '0';
}
$return[] = new css_style_borderwidth('border-top-width', $width);
@@ -2801,6 +2832,35 @@ class css_style_borderwidth extends css_style_width {
public function consolidate_to() {
return 'border';
}
/**
* Checks if the width is valid
* @return bool
*/
public function is_valid() {
return self::is_border_width($this->value);
}
/**
* Cleans the provided value
*
* @param mixed $value Cleans the provided value optimising it if possible
* @return string
*/
protected function clean_value($value) {
$isvalid = self::is_border_width($value);
if (!$isvalid) {
$this->set_error('Invalid width specified for '.$this->name);
} else if (preg_match('#^0\D+$#', $value)) {
return '0';
}
return trim($value);
}
public static function is_border_width($value) {
$altwidthvalues = array('thin', 'medium', 'thick');
return css_is_width($value) || in_array($value, $altwidthvalues);
}
}
/**
@@ -3679,3 +3739,42 @@ class css_style_paddingleft extends css_style_padding {
return 'padding';
}
}
class css_style_cursor extends css_style_generic {
public static function init($value) {
return new css_style_cursor('cursor', $value);
}
protected function clean_value($value) {
$allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
$this->set_error('Invalid or unexpected cursor value specified: '.$value);
}
return trim($value);
}
}
class css_style_verticalalign extends css_style_generic {
public static function init($value) {
return new css_style_verticalalign('vertical-align', $value);
}
protected function clean_value($value) {
$allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
if (!css_is_width($value) && !in_array($value, $allowed)) {
$this->set_error('Invalid vertical-align value specified: '.$value);
}
return trim($value);
}
}
class css_style_float extends css_style_generic {
public static function init($value) {
return new css_style_float('float', $value);
}
protected function clean_value($value) {
$allowed = array('left', 'right', 'none', 'inherit');
if (!css_is_width($value) && !in_array($value, $allowed)) {
$this->set_error('Invalid float value specified: '.$value);
}
return trim($value);
}
}