From db8280ef3b9564df5a8ae4b36151f284d33bc600 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 4 Mar 2014 11:24:00 +0000 Subject: [PATCH] New vendor library: JShrink --- library/vendor/JShrink/LICENSE | 24 ++ library/vendor/JShrink/Minifier.php | 494 ++++++++++++++++++++++++++++ library/vendor/JShrink/SOURCE | 2 + 3 files changed, 520 insertions(+) create mode 100644 library/vendor/JShrink/LICENSE create mode 100644 library/vendor/JShrink/Minifier.php create mode 100644 library/vendor/JShrink/SOURCE diff --git a/library/vendor/JShrink/LICENSE b/library/vendor/JShrink/LICENSE new file mode 100644 index 000000000..68caa37c6 --- /dev/null +++ b/library/vendor/JShrink/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2009, Robert Hafner +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 the Stash Project nor the + names of its 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 Robert Hafner 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. \ No newline at end of file diff --git a/library/vendor/JShrink/Minifier.php b/library/vendor/JShrink/Minifier.php new file mode 100644 index 000000000..e76ae162a --- /dev/null +++ b/library/vendor/JShrink/Minifier.php @@ -0,0 +1,494 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * JShrink + * + * + * @package JShrink + * @author Robert Hafner + */ + +namespace JShrink; + +/** + * Minifier + * + * Usage - Minifier::minify($js); + * Usage - Minifier::minify($js, $options); + * Usage - Minifier::minify($js, array('flaggedComments' => false)); + * + * @package JShrink + * @author Robert Hafner + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ +class 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 + */ + protected static $defaultOptions = array('flaggedComments' => true); + + /** + * Minifier::minify takes a string containing javascript and removes + * unneeded characters in order to shrink the code without altering it's + * functionality. + */ + public static function minify($js, $options = array()) + { + try { + ob_start(); + $currentOptions = array_merge(static::$defaultOptions, $options); + + $jshrink = new Minifier(); + $jshrink->breakdownScript($js, $currentOptions); + unset($jshrink); + + // Sometimes there's a leading new line, so we trim that out here. + return ltrim(ob_get_clean()); + + } catch (\Exception $e) { + + if (isset($jshrink)) { + // Since the breakdownScript function probably wasn't finished + // we clean it out before discarding it. + $jshrink->clean(); + unset($jshrink); + } + + // without this call things get weird, with partially outputted js. + 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) + { + $this->options = $currentOptions; + + $js = str_replace("\r\n", "\n", $js); + $this->input = str_replace("\r", "\n", $js); + + // We add a newline to the end of the script to make it easier to deal + // with comments at the bottom of the script- this prevents the unclosed + // comment error that can otherwise occur. + $this->input .= PHP_EOL; + + + $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(static::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 (static::isAlphaNumeric($this->a)) { + echo $this->a; + $this->saveString(); + } + } + break; + + case ' ': + if(!static::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() + { + // Check to see if we had anything in the look ahead buffer and use that. + if (isset($this->c)) { + $char = $this->c; + unset($this->c); + + // Otherwise we start pulling from the input. + } else { + $char = substr($this->input, $this->index, 1); + + // If the next character doesn't exist return false. + if (isset($char) && $char === false) { + return false; + } + + // Otherwise increment the pointer and use this char. + $this->index++; + } + + // Normalize all whitespace except for the newline character into a + // standard space. + 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. + * + * @throws \RuntimeException + * @return string Next 'real' character to be processed. + */ + protected function getReal() + { + $startIndex = $this->index; + $char = $this->getChar(); + + + // Check to see if we're potentially in a comment + 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('Unclosed multiline comment at position: ' . ($this->index - 2)); + + // 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 and the index is set + * to it's position. + * + * @param $string + * @return string|false Returns the first character of the string or false. + */ + protected function getNext($string) + { + // Find the next occurrence of "string" after the current position. + $pos = strpos($this->input, $string, $this->index); + + // If it's not there return false. + if($pos === false) + + return false; + + // Adjust position of index to jump ahead to the asked for string + $this->index = $pos; + + // Return the first character of that string. + 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() + { + $startpos = $this->index; + + // saveString is always called after a gets cleared, so we push b into + // that spot. + $this->a = $this->b; + + // If this isn't a string we don't need to do anything. + if ($this->a != "'" && $this->a != '"') { + return; + } + + // String type is the quote used, " or ' + $stringType = $this->a; + + // Echo out that starting quote + echo $this->a; + + + // Loop until the string is done + while (1) { + + // Grab the very next character and load it into a + $this->a = $this->getChar(); + + switch ($this->a) { + + // If the string opener (single or double quote) is used + // output it and break out of the while loop- + // The string is finished! + case $stringType: + break 2; + + + + // New lines in strings without line delimiters are bad- actual + // new lines will be represented by the string \n and not the actual + // character, so those will be treated just fine using the switch + // block below. + case "\n": + throw new \RuntimeException('Unclosed string at position: ' . $startpos ); + break; + + + // Escaped characters get picked up here. If it's an escaped new line it's not really needed + case '\\': + + // a is a slash. We want to keep it, and the next character, + // unless it's a new line. New lines as actual strings will be + // preserved, but escaped new lines should be reduced. + $this->b = $this->getChar(); + + // If b is a new line we discard a and b and restart the loop. + if ($this->b == "\n") { + break; + } + + // echo out the escaped character and restart the loop. + echo $this->a . $this->b; + break; + + + // Since we're not dealing with any special cases we simply + // output the character and continue our loop. + default: + echo $this->a; + } + + // Echo a- it'll be set to the next char at the start of the loop + // echo $this->a; + } + } + + /** + * When a regular expression is detected this function 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('Unclosed regex pattern at position: ' . $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. Another reason for this is to make sure + * the variables are cleared and are not taking up memory. + */ + 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. + * + * @param $char string Just one character + * @return bool + */ + protected static function isAlphaNumeric($char) + { + return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/'; + } + +} diff --git a/library/vendor/JShrink/SOURCE b/library/vendor/JShrink/SOURCE new file mode 100644 index 000000000..e01b72fe0 --- /dev/null +++ b/library/vendor/JShrink/SOURCE @@ -0,0 +1,2 @@ +https://github.com/tedivm/JShrink.git +d9238750fdf763dc4f38631be64866fd84fe5ec8