diff options
-rw-r--r-- | include/jshrink.class.php | 469 | ||||
-rw-r--r-- | include/jsmin.class.php | 291 | ||||
-rw-r--r-- | include/template.class.php | 4 |
3 files changed, 471 insertions, 293 deletions
diff --git a/include/jshrink.class.php b/include/jshrink.class.php new file mode 100644 index 000000000..b32807c4a --- /dev/null +++ b/include/jshrink.class.php @@ -0,0 +1,469 @@ +<?php +/** + * JShrink + * + * Copyright (c) 2009-2012, Robert Hafner <tedivm@tedivm.com>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Hafner nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package JShrink + * @author Robert Hafner <tedivm@tedivm.com> + * @copyright 2009-2012 Robert Hafner <tedivm@tedivm.com> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link https://github.com/tedivm/JShrink + * @version Release: 0.5.1 + */ + + +/** + * JShrink_Minifier + * + * Usage - JShrink_Minifier::minify($js); + * Usage - JShrink_Minifier::minify($js, $options); + * Usage - JShrink_Minifier::minify($js, array('flaggedComments' => false)); + * + * @package JShrink + * @author Robert Hafner <tedivm@tedivm.com> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ +class JShrink_Minifier +{ + /** + * The input javascript to be minified. + * + * @var string + */ + protected $input; + + /** + * The location of the character (in the input string) that is next to be + * processed. + * + * @var int + */ + protected $index = 0; + + /** + * The first of the characters currently being looked at. + * + * @var string + */ + protected $a = ''; + + + /** + * The next character being looked at (after a); + * + * @var string + */ + protected $b = ''; + + /** + * This character is only active when certain look ahead actions take place. + * + * @var string + */ + protected $c; + + /** + * Contains the options for the current minification process. + * + * @var array + */ + protected $options; + + /** + * Contains the default options for minification. This array is merged with + * the one passed in by the user to create the request specific set of + * options (stored in the $options attribute). + * + * @var array + */ + static protected $defaultOptions = array('flaggedComments' => true); + + /** + * Contains a copy of the JShrink object used to run minification. This is + * only used internally, and is only stored for performance reasons. There + * is no internal data shared between minification requests. + */ + static protected $jshrink; + + /** + * Minifier::minify takes a string containing javascript and removes + * unneeded characters in order to shrink the code without altering it's + * functionality. + */ + static public function minify($js, $options = array()) + { + try{ + ob_start(); + $currentOptions = array_merge(self::$defaultOptions, $options); + + if(!isset(self::$jshrink)) + self::$jshrink = new JShrink_Minifier(); + + self::$jshrink->breakdownScript($js, $currentOptions); + return ob_get_clean(); + + }catch(Exception $e){ + if(isset(self::$jshrink)) + self::$jshrink->clean(); + + ob_end_clean(); + throw $e; + } + } + + /** + * Processes a javascript string and outputs only the required characters, + * stripping out all unneeded characters. + * + * @param string $js The raw javascript to be minified + * @param array $currentOptions Various runtime options in an associative array + */ + protected function breakdownScript($js, $currentOptions) + { + // reset work attributes in case this isn't the first run. + $this->clean(); + + $this->options = $currentOptions; + + $js = str_replace("\r\n", "\n", $js); + $this->input = str_replace("\r", "\n", $js); + + + $this->a = $this->getReal(); + + // the only time the length can be higher than 1 is if a conditional + // comment needs to be displayed and the only time that can happen for + // $a is on the very first run + while(strlen($this->a) > 1) + { + echo $this->a; + $this->a = $this->getReal(); + } + + $this->b = $this->getReal(); + + while($this->a !== false && !is_null($this->a) && $this->a !== '') + { + + // now we give $b the same check for conditional comments we gave $a + // before we began looping + if(strlen($this->b) > 1) + { + echo $this->a . $this->b; + $this->a = $this->getReal(); + $this->b = $this->getReal(); + continue; + } + + switch($this->a) + { + // new lines + case "\n": + // if the next line is something that can't stand alone + // preserve the newline + if(strpos('(-+{[@', $this->b) !== false) + { + echo $this->a; + $this->saveString(); + break; + } + + // if its a space we move down to the string test below + if($this->b === ' ') + break; + + // otherwise we treat the newline like a space + + case ' ': + if(self::isAlphaNumeric($this->b)) + echo $this->a; + + $this->saveString(); + break; + + default: + switch($this->b) + { + case "\n": + if(strpos('}])+-"\'', $this->a) !== false) + { + echo $this->a; + $this->saveString(); + break; + }else{ + if(self::isAlphaNumeric($this->a)) + { + echo $this->a; + $this->saveString(); + } + } + break; + + case ' ': + if(!self::isAlphaNumeric($this->a)) + break; + + default: + // check for some regex that breaks stuff + if($this->a == '/' && ($this->b == '\'' || $this->b == '"')) + { + $this->saveRegex(); + continue; + } + + echo $this->a; + $this->saveString(); + break; + } + } + + // do reg check of doom + $this->b = $this->getReal(); + + if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) + $this->saveRegex(); + } + $this->clean(); + } + + /** + * Returns the next string for processing based off of the current index. + * + * @return string + */ + protected function getChar() + { + if(isset($this->c)) + { + $char = $this->c; + unset($this->c); + }else{ + $tchar = substr($this->input, $this->index, 1); + if(isset($tchar) && $tchar !== false) + { + $char = $tchar; + $this->index++; + }else{ + return false; + } + } + + if($char !== "\n" && ord($char) < 32) + return ' '; + + return $char; + } + + /** + * This function gets the next "real" character. It is essentially a wrapper + * around the getChar function that skips comments. This has significant + * performance benefits as the skipping is done using native functions (ie, + * c code) rather than in script php. + * + * @return string Next 'real' character to be processed. + */ + protected function getReal() + { + $startIndex = $this->index; + $char = $this->getChar(); + + if($char == '/') + { + $this->c = $this->getChar(); + + if($this->c == '/') + { + $thirdCommentString = substr($this->input, $this->index, 1); + + // kill rest of line + $char = $this->getNext("\n"); + + if($thirdCommentString == '@') + { + $endPoint = ($this->index) - $startIndex; + unset($this->c); + $char = "\n" . substr($this->input, $startIndex, $endPoint); + }else{ + $char = $this->getChar(); + $char = $this->getChar(); + } + + }elseif($this->c == '*'){ + + $this->getChar(); // current C + $thirdCommentString = $this->getChar(); + + if($thirdCommentString == '@') + { + // conditional comment + + // we're gonna back up a bit and and send the comment back, + // where the first char will be echoed and the rest will be + // treated like a string + $this->index = $this->index-2; + return '/'; + + }elseif($this->getNext('*/')){ + // kill everything up to the next */ + + $this->getChar(); // get * + $this->getChar(); // get / + + $char = $this->getChar(); // get next real character + + // if YUI-style comments are enabled we reinsert it into the stream + if($this->options['flaggedComments'] && $thirdCommentString == '!') + { + $endPoint = ($this->index - 1) - $startIndex; + echo "\n" . substr($this->input, $startIndex, $endPoint) . "\n"; + } + + }else{ + $char = false; + } + + if($char === false) + throw new RuntimeException('Stray comment. ' . $this->index); + + // if we're here c is part of the comment and therefore tossed + if(isset($this->c)) + unset($this->c); + } + } + return $char; + } + + /** + * Pushes the index ahead to the next instance of the supplied string. If it + * is found the first character of the string is returned. + * + * @return string|false Returns the first character of the string or false. + */ + protected function getNext($string) + { + $pos = strpos($this->input, $string, $this->index); + + if($pos === false) + return false; + + $this->index = $pos; + return substr($this->input, $this->index, 1); + } + + /** + * When a javascript string is detected this function crawls for the end of + * it and saves the whole string. + * + */ + protected function saveString() + { + $this->a = $this->b; + if($this->a == "'" || $this->a == '"') // is the character a quote + { + // save literal string + $stringType = $this->a; + + while(1) + { + echo $this->a; + $this->a = $this->getChar(); + + switch($this->a) + { + case $stringType: + break 2; + + case "\n": + throw new RuntimeException('Unclosed string. ' . $this->index); + break; + + case '\\': + echo $this->a; + $this->a = $this->getChar(); + } + } + } + } + + /** + * When a regular expression is detected this funcion crawls for the end of + * it and saves the whole regex. + */ + protected function saveRegex() + { + echo $this->a . $this->b; + + while(($this->a = $this->getChar()) !== false) + { + if($this->a == '/') + break; + + if($this->a == '\\') + { + echo $this->a; + $this->a = $this->getChar(); + } + + if($this->a == "\n") + throw new RuntimeException('Stray regex pattern. ' . $this->index); + + echo $this->a; + } + $this->b = $this->getReal(); + } + + /** + * Resets attributes that do not need to be stored between requests so that + * the next request is ready to go. + */ + protected function clean() + { + unset($this->input); + $this->index = 0; + $this->a = $this->b = ''; + unset($this->c); + unset($this->options); + } + + /** + * Checks to see if a character is alphanumeric. + * + * @return bool + */ + static protected function isAlphaNumeric($char) + { + return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/'; + } + +}
\ No newline at end of file diff --git a/include/jsmin.class.php b/include/jsmin.class.php deleted file mode 100644 index 3c2f859df..000000000 --- a/include/jsmin.class.php +++ /dev/null @@ -1,291 +0,0 @@ -<?php -/** - * jsmin.php - PHP implementation of Douglas Crockford's JSMin. - * - * This is pretty much a direct port of jsmin.c to PHP with just a few - * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and - * outputs to stdout, this library accepts a string as input and returns another - * string as output. - * - * PHP 5 or higher is required. - * - * Permission is hereby granted to use this version of the library under the - * same terms as jsmin.c, which has the following license: - * - * -- - * Copyright (c) 2002 Douglas Crockford (www.crockford.com) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is furnished to do - * so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * The Software shall be used for Good, not Evil. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * -- - * - * @package JSMin - * @author Ryan Grove <ryan@wonko.com> - * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) - * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) - * @license http://opensource.org/licenses/mit-license.php MIT License - * @version 1.1.1 (2008-03-02) - * @link http://code.google.com/p/jsmin-php/ - */ - -class JSMin { - const ORD_LF = 10; - const ORD_SPACE = 32; - - protected $a = ''; - protected $b = ''; - protected $input = ''; - protected $inputIndex = 0; - protected $inputLength = 0; - protected $lookAhead = null; - protected $output = ''; - - // -- Public Static Methods -------------------------------------------------- - - public static function minify($js) { - $jsmin = new JSMin($js); - return $jsmin->min(); - } - - // -- Public Instance Methods ------------------------------------------------ - - public function __construct($input) { - $this->input = str_replace("\r\n", "\n", $input); - $this->inputLength = strlen($this->input); - } - - // -- Protected Instance Methods --------------------------------------------- - - protected function action($d) { - switch($d) { - case 1: - $this->output .= $this->a; - - case 2: - $this->a = $this->b; - - if ($this->a === "'" || $this->a === '"') { - for (;;) { - $this->output .= $this->a; - $this->a = $this->get(); - - if ($this->a === $this->b) { - break; - } - - if (ord($this->a) <= self::ORD_LF) { - throw new JSMinException('Unterminated string literal.'); - } - - if ($this->a === '\\') { - $this->output .= $this->a; - $this->a = $this->get(); - } - } - } - - case 3: - $this->b = $this->next(); - - if ($this->b === '/' && ( - $this->a === '(' || $this->a === ',' || $this->a === '=' || - $this->a === ':' || $this->a === '[' || $this->a === '!' || - $this->a === '&' || $this->a === '|' || $this->a === '?')) { - - $this->output .= $this->a . $this->b; - - for (;;) { - $this->a = $this->get(); - - if ($this->a === '/') { - break; - } elseif ($this->a === '\\') { - $this->output .= $this->a; - $this->a = $this->get(); - } elseif (ord($this->a) <= self::ORD_LF) { - throw new JSMinException('Unterminated regular expression '. - 'literal.'); - } - - $this->output .= $this->a; - } - - $this->b = $this->next(); - } - } - } - - protected function get() { - $c = $this->lookAhead; - $this->lookAhead = null; - - if ($c === null) { - if ($this->inputIndex < $this->inputLength) { - $c = substr($this->input, $this->inputIndex, 1); - $this->inputIndex += 1; - } else { - $c = null; - } - } - - if ($c === "\r") { - return "\n"; - } - - if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { - return $c; - } - - return ' '; - } - - protected function isAlphaNum($c) { - return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; - } - - protected function min() { - $this->a = "\n"; - $this->action(3); - - while ($this->a !== null) { - switch ($this->a) { - case ' ': - if ($this->isAlphaNum($this->b)) { - $this->action(1); - } else { - $this->action(2); - } - break; - - case "\n": - switch ($this->b) { - case '{': - case '[': - case '(': - case '+': - case '-': - $this->action(1); - break; - - case ' ': - $this->action(3); - break; - - default: - if ($this->isAlphaNum($this->b)) { - $this->action(1); - } - else { - $this->action(2); - } - } - break; - - default: - switch ($this->b) { - case ' ': - if ($this->isAlphaNum($this->a)) { - $this->action(1); - break; - } - - $this->action(3); - break; - - case "\n": - switch ($this->a) { - case '}': - case ']': - case ')': - case '+': - case '-': - case '"': - case "'": - $this->action(1); - break; - - default: - if ($this->isAlphaNum($this->a)) { - $this->action(1); - } - else { - $this->action(3); - } - } - break; - - default: - $this->action(1); - break; - } - } - } - - return $this->output; - } - - protected function next() { - $c = $this->get(); - - if ($c === '/') { - switch($this->peek()) { - case '/': - for (;;) { - $c = $this->get(); - - if (ord($c) <= self::ORD_LF) { - return $c; - } - } - - case '*': - $this->get(); - - for (;;) { - switch($this->get()) { - case '*': - if ($this->peek() === '/') { - $this->get(); - return ' '; - } - break; - - case null: - throw new JSMinException('Unterminated comment.'); - } - } - - default: - return $c; - } - } - - return $c; - } - - protected function peek() { - $this->lookAhead = $this->get(); - return $this->lookAhead; - } -} - -// -- Exceptions --------------------------------------------------------------- -class JSMinException extends Exception {} -?>
\ No newline at end of file diff --git a/include/template.class.php b/include/template.class.php index d43f8a79a..1dea68164 100644 --- a/include/template.class.php +++ b/include/template.class.php @@ -1387,8 +1387,8 @@ final class FileCombiner $js = file_get_contents(PHPWG_ROOT_PATH . $file); if (strpos($file, '.min')===false and strpos($file, '.packed')===false ) { - require_once(PHPWG_ROOT_PATH.'include/jsmin.class.php'); - try { $js = JSMin::minify($js); } catch(Exception $e) {} + require_once(PHPWG_ROOT_PATH.'include/jshrink.class.php'); + try { $js = JShrink_Minifier::minify($js); } catch(Exception $e) {} } return trim($js, " \t\r\n;").";\n"; } |