summaryrefslogtreecommitdiffstats
path: root/system/core/Output.php
diff options
context:
space:
mode:
authorAndrey Andreev <narf@devilix.net>2013-10-31 14:14:15 +0100
committerAndrey Andreev <narf@devilix.net>2013-10-31 14:14:15 +0100
commit0c51847e0afc94b145104b415adb72cb4143dbd6 (patch)
tree15142faeeda3bc1e66cc6de9c33c125510624f57 /system/core/Output.php
parent5e3d48c21dea8a97dcea1b820ebc14700a336312 (diff)
parentd6feb5abb9333fb430a773f20f24e178f6c486da (diff)
Merge branch 'feature/minify' into develop
Diffstat (limited to 'system/core/Output.php')
-rw-r--r--system/core/Output.php203
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;
}