diff options
-rw-r--r-- | system/core/Output.php | 203 |
1 files changed, 71 insertions, 132 deletions
diff --git a/system/core/Output.php b/system/core/Output.php index 719c43256..05bc48ed4 100644 --- a/system/core/Output.php +++ b/system/core/Output.php @@ -740,13 +740,13 @@ class CI_Output { preg_match_all('{<style.+</style>}msU', $output, $style_clean); foreach ($style_clean[0] as $s) { - $output = str_replace($s, $this->_minify_script_style($s, TRUE), $output); + $output = str_replace($s, $this->_minify_js_css($s, 'css', TRUE), $output); } // Minify the javascript in <script> tags. foreach ($javascript_clean[0] as $s) { - $javascript_mini[] = $this->_minify_script_style($s, TRUE); + $javascript_mini[] = $this->_minify_js_css($s, 'js', TRUE); } // Replace multiple spaces with a single space. @@ -792,13 +792,14 @@ class CI_Output { break; case 'text/css': + + return $this->_minify_js_css($output, 'css'); + case 'text/javascript': case 'application/javascript': case 'application/x-javascript': - $output = $this->_minify_script_style($output); - - break; + return $this->_minify_js_css($output, 'js'); default: break; } @@ -809,163 +810,101 @@ class CI_Output { // -------------------------------------------------------------------- /** - * Minify Style and Script - * - * Reduce excessive size of CSS/JavaScript content. To remove spaces this - * script walks the string as an array and determines if the pointer is inside - * a string created by single quotes or double quotes. spaces inside those - * strings are not stripped. Opening and closing tags are severed from - * the string initially and saved without stripping whitespace to preserve - * the tags and any associated properties if tags are present - * - * Minification logic/workflow is similar to methods used by Douglas Crockford - * in JSMIN. http://www.crockford.com/javascript/jsmin.html + * Minify JavaScript and CSS code * - * KNOWN ISSUE: ending a line with a closing parenthesis ')' and no semicolon - * where there should be one will break the Javascript. New lines after a - * closing parenthesis are not recognized by the script. For best results - * be sure to terminate lines with a semicolon when appropriate. + * Strips comments and excessive whitespace characters * - * @param string $output Output to minify - * @param bool $has_tags Specify if the output has style or script tags - * @return string Minified output + * @param string $output + * @param string $type 'js' or 'css' + * @param bool $tags Whether $output contains the 'script' or 'style' tag + * @return string */ - protected function _minify_script_style($output, $has_tags = FALSE) + protected function _minify_js_css($output, $type, $tags = FALSE) { - // We only need this if there are tags in the file - if ($has_tags === TRUE) + if ($tags === TRUE) { - // Remove opening tag and save for later - $pos = strpos($output, '>') + 1; - $open_tag = substr($output, 0, $pos); - $output = substr_replace($output, '', 0, $pos); + $tags = array('close' => strrchr($output, '<')); + + $open_length = strpos($output, '>') + 1; + $tags['open'] = substr($output, 0, $open_length); - // Remove closing tag and save it for later - $pos = strrpos($output, '</'); - $closing_tag = substr($output, $pos); - $output = substr_replace($output, '', $pos); + $output = substr($output, $open_length, -strlen($tags['close'])); + + // Strip spaces from the tags + $tags = preg_replace('#\s{2,}#', ' ', $tags); } - // Remove CSS comments - $output = preg_replace('@/\*([^/][^*]*\*)*/(?!.+?["\'])@i', '', $output); + $output = trim($output); - // Remove Javascript inline comments - if ($has_tags === TRUE && strpos(strtolower($open_tag), 'script') !== FALSE) + if ($type === 'js') { - $lines = preg_split('/\r?\n|\n?\r/', $output); - foreach ($lines as &$line) + // Catch all string literals and comment blocks + if (preg_match_all('#((?:((?<!\\\)\'|")|/\*).*(?(2)(?<!\\\)\2|\*/))#msuUS', $output, $match, PREG_OFFSET_CAPTURE)) { - $in_string = $in_dstring = FALSE; - for ($i = 0, $len = strlen($line); $i < $len; $i++) + $js_literals = $js_code = array(); + for ($match = $match[0], $c = count($match), $i = $pos = $offset = 0; $i < $c; $i++) { - if ( ! $in_string && ! $in_dstring && substr($line, $i, 2) === '//') - { - $line = substr($line, 0, $i); - break; - } + $js_code[$pos++] = trim(substr($output, $offset, $match[$i][1] - $offset)); + $offset = $match[$i][1] + strlen($match[$i][0]); - if ($line[$i] === "'" && ! $in_dstring) - { - $in_string = ! $in_string; - } - elseif ($line[$i] === '"' && ! $in_string) + // Save only if we haven't matched a comment block + if ($match[$i][0][0] !== '/') { - $in_dstring = ! $in_dstring; + $js_literals[$pos++] = array_shift($match[$i]); } } + $js_code[$pos] = substr($output, $offset); + + // $match might be quite large, so free it up together with other vars that we no longer need + unset($match, $offset, $pos); + } + else + { + $js_code = array($output); + $js_literals = array(); } - $output = implode("\n", $lines); + $varname = 'js_code'; } - - // Remove spaces around curly brackets, colons, - // semi-colons, parenthesis, commas - $chunks = preg_split('/([\'|"]).+(?![^\\\]\\1)\\1/iU', $output, -1, PREG_SPLIT_OFFSET_CAPTURE); - for ($i = count($chunks) - 1; $i >= 0; $i--) + else { - $output = substr_replace( - $output, - preg_replace('/\s*(:|;|,|}|{|\(|\))\s*/i', '$1', $chunks[$i][0]), - $chunks[$i][1], - strlen($chunks[$i][0]) - ); + $varname = 'output'; } - // Replace tabs with spaces - // Replace carriage returns & multiple new lines with single new line - // and trim any leading or trailing whitespace - $output = trim(preg_replace(array('/\t+/', '/\r/', '/\n+/'), array(' ', "\n", "\n"), $output)); + // Standartize new lines + $$varname = str_replace(array("\r\n", "\r"), "\n", $$varname); - // Remove spaces when safe to do so. - $in_string = $in_dstring = $prev = FALSE; - $array_output = str_split($output); - foreach ($array_output as $key => $value) + if ($type === 'js') { - if ($in_string === FALSE && $in_dstring === FALSE) - { - if ($value === ' ') - { - // Get the next element in the array for comparisons - $next = $array_output[$key + 1]; - - // Strip spaces preceded/followed by a non-ASCII character - // that are not preceded/followed by an alphanumeric character, - // '\', '$', '_', '.' and '#' - if ((preg_match('/^[\x20-\x7f]*$/D', $next) OR preg_match('/^[\x20-\x7f]*$/D', $prev)) - && ( ! ctype_alnum($next) OR ! ctype_alnum($prev)) - && ! in_array($next, array('\\', '_', '$', '.', '#'), TRUE) - && ! in_array($prev, array('\\', '_', '$', '.', '#'), TRUE) - ) - { - unset($array_output[$key]); - } - } - else - { - // Save this value as previous for the next iteration - // if it is not a blank space - $prev = $value; - } - } - - if ($value === "'" && ! $in_dstring) - { - $in_string = ! $in_string; - } - elseif ($value === '"' && ! $in_string) - { - $in_dstring = ! $in_dstring; - } + $patterns = array( + '#\n?//[^\n]*#' => '', // Remove // line comments + '#\s*([!\#%&()*+,\-./:;<=>?@\[\]^`{|}~])\s*#' => '$1', // Remove spaces following and preceeding JS-wise non-special & non-word characters + '#\s{2,}#' => ' ' // Reduce the remaining multiple whitespace characters to a single space + ); + } + else + { + $patterns = array( + '#/\*.*(?=\*/)\*/#s' => '', // Remove /* block comments */ + '#\n?//[^\n]*#' => '', // Remove // line comments + '#\s*([^\w.\#%])\s*#U' => '$1', // Remove spaces following and preceeding non-word characters, excluding dots, hashes and the percent sign + '#\s{2,}#' => ' ' // Reduce the remaining multiple space characters to a single space + ); } - // Put the string back together after spaces have been stripped - $output = implode($array_output); + $$varname = preg_replace(array_keys($patterns), array_values($patterns), $$varname); - // Remove new line characters unless previous or next character is - // printable or Non-ASCII - preg_match_all('/[\n]/', $output, $lf, PREG_OFFSET_CAPTURE); - $removed_lf = 0; - foreach ($lf as $feed_position) + // Glue back JS quoted strings + if ($type === 'js') { - foreach ($feed_position as $position) - { - $position = $position[1] - $removed_lf; - $next = $output[$position + 1]; - $prev = $output[$position - 1]; - if ( ! ctype_print($next) && ! ctype_print($prev) - && ! preg_match('/^[\x20-\x7f]*$/D', $next) - && ! preg_match('/^[\x20-\x7f]*$/D', $prev) - ) - { - $output = substr_replace($output, '', $position, 1); - $removed_lf++; - } - } + $js_code += $js_literals; + ksort($js_code); + $output = implode($js_code); + unset($js_code, $js_literals, $varname, $patterns); } - // Put the opening and closing tags back if applicable - return isset($open_tag) - ? $open_tag.$output.$closing_tag + return is_array($tags) + ? $tags['open'].$output.$tags['close'] : $output; } |