From 0415037517bb48d0a9d191b55930d2d2eed45df7 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Wed, 21 Dec 2011 12:15:03 +0400 Subject: add jsmin stuff to index.php --- jsmin.php | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 jsmin.php (limited to 'jsmin.php') diff --git a/jsmin.php b/jsmin.php new file mode 100644 index 000000000..5c3f88143 --- /dev/null +++ b/jsmin.php @@ -0,0 +1,375 @@ + + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2008 Ryan Grove (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 -- cgit v1.2.3