diff options
author | Andrew Dolgov <[email protected]> | 2011-12-21 12:19:41 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-12-21 12:19:41 +0400 |
commit | bb82bed23f15844b91de0946fbb2fd221d78f160 (patch) | |
tree | 2bb3e7276092ee1fb0eb20e7fd4cb489e8e20d1d /lib/jsmin.php | |
parent | 0415037517bb48d0a9d191b55930d2d2eed45df7 (diff) |
move jsmin.php to lib/
Diffstat (limited to 'lib/jsmin.php')
-rw-r--r-- | lib/jsmin.php | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/lib/jsmin.php b/lib/jsmin.php new file mode 100644 index 000000000..5c3f88143 --- /dev/null +++ b/lib/jsmin.php @@ -0,0 +1,375 @@ +<?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 <[email protected]> + * @copyright 2002 Douglas Crockford <[email protected]> (jsmin.c) + * @copyright 2008 Ryan Grove <[email protected]> (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.1.1 (2008-03-02) + * @link https://github.com/rgrove/jsmin-php/ + */ + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + const ACTION_KEEP_A = 1; + const ACTION_DELETE_A = 2; + const ACTION_DELETE_A_B = 3; + + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + + // -- Public Static Methods -------------------------------------------------- + + /** + * Minify Javascript + * + * @uses __construct() + * @uses min() + * @param string $js Javascript to be minified + * @return string + */ + public static function minify($js) { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + // -- Public Instance Methods ------------------------------------------------ + + /** + * Constructor + * + * @param string $input Javascript to be minified + */ + public function __construct($input) { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); + } + + // -- Protected Instance Methods --------------------------------------------- + + /** + * Action -- do something! What to do is determined by the $command argument. + * + * action treats a string as a single character. Wow! + * action recognizes a regular expression if it is preceded by ( or , or =. + * + * @uses next() + * @uses get() + * @throws JSMinException If parser errors are found: + * - Unterminated string literal + * - Unterminated regular expression set in regex literal + * - Unterminated regular expression literal + * @param int $command One of class constants: + * ACTION_KEEP_A Output A. Copy B to A. Get the next B. + * ACTION_DELETE_A Copy B to A. Get the next B. (Delete A). + * ACTION_DELETE_A_B Get the next B. (Delete B). + */ + protected function action($command) { + switch($command) { + case self::ACTION_KEEP_A: + $this->output .= $this->a; + + case self::ACTION_DELETE_A: + $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 self::ACTION_DELETE_A_B: + $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->a === '{' || $this->a === '}' || $this->a === ';' || + $this->a === "\n" )) { + + $this->output .= $this->a . $this->b; + + for (;;) { + $this->a = $this->get(); + + if ($this->a === '[') { + /* + inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460: + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); + */ + for (;;) { + $this->output .= $this->a; + $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 set in regex literal.'); + } + } + } elseif ($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(); + } + } + } + + /** + * Get next char. Convert ctrl char to space. + * + * @return string|null + */ + 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 ' '; + } + + /** + * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character. + * + * @return bool + */ + protected function isAlphaNum($c) { + return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; + } + + /** + * Perform minification, return result + * + * @uses action() + * @uses isAlphaNum() + * @return string + */ + protected function min() { + $this->a = "\n"; + $this->action(self::ACTION_DELETE_A_B); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(self::ACTION_KEEP_A); + } else { + $this->action(self::ACTION_DELETE_A); + } + break; + + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(self::ACTION_KEEP_A); + break; + + case ' ': + $this->action(self::ACTION_DELETE_A_B); + break; + + default: + if ($this->isAlphaNum($this->b)) { + $this->action(self::ACTION_KEEP_A); + } + else { + $this->action(self::ACTION_DELETE_A); + } + } + break; + + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(self::ACTION_KEEP_A); + break; + } + + $this->action(self::ACTION_DELETE_A_B); + break; + + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(self::ACTION_KEEP_A); + break; + + default: + if ($this->isAlphaNum($this->a)) { + $this->action(self::ACTION_KEEP_A); + } + else { + $this->action(self::ACTION_DELETE_A_B); + } + } + break; + + default: + $this->action(self::ACTION_KEEP_A); + break; + } + } + } + + return $this->output; + } + + /** + * Get the next character, skipping over comments. peek() is used to see + * if a '/' is followed by a '/' or '*'. + * + * @uses get() + * @uses peek() + * @throws JSMinException On unterminated comment. + * @return string + */ + 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; + } + + /** + * Get next char. If is ctrl character, translate to a space or newline. + * + * @uses get() + * @return string|null + */ + protected function peek() { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } +} + +// -- Exceptions --------------------------------------------------------------- +class JSMinException extends Exception {} +?>
\ No newline at end of file |