TYPO3 API  SVNRelease
jsmin.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
00004  *
00005  * This is pretty much a direct port of jsmin.c to PHP with just a few
00006  * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
00007  * outputs to stdout, this library accepts a string as input and returns another
00008  * string as output.
00009  *
00010  * PHP 5 or higher is required.
00011  *
00012  * Permission is hereby granted to use this version of the library under the
00013  * same terms as jsmin.c, which has the following license:
00014  *
00015  * --
00016  * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
00017  *
00018  * Permission is hereby granted, free of charge, to any person obtaining a copy of
00019  * this software and associated documentation files (the "Software"), to deal in
00020  * the Software without restriction, including without limitation the rights to
00021  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
00022  * of the Software, and to permit persons to whom the Software is furnished to do
00023  * so, subject to the following conditions:
00024  *
00025  * The above copyright notice and this permission notice shall be included in all
00026  * copies or substantial portions of the Software.
00027  *
00028  * The Software shall be used for Good, not Evil.
00029  *
00030  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00031  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00032  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00033  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00034  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00035  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00036  * SOFTWARE.
00037  * --
00038  *
00039  * @package JSMin
00040  * @author Ryan Grove <ryan@wonko.com>
00041  * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
00042  * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port)
00043  * @license http://opensource.org/licenses/mit-license.php MIT License
00044  * @version 1.1.0 (2007-06-01)
00045  * @link http://code.google.com/p/jsmin-php/
00046  *
00047  * TYPO3 SVN ID: $Id: jsmin.php 2663 2007-11-05 09:22:23Z ingmars $
00048  */
00049 
00050 class JSMin {
00051   const ORD_LF    = 10;
00052   const ORD_SPACE = 32;
00053 
00054   protected $a           = '';
00055   protected $b           = '';
00056   protected $input       = '';
00057   protected $inputIndex  = 0;
00058   protected $inputLength = 0;
00059   protected $lookAhead   = null;
00060   protected $output      = array();
00061 
00062   // -- Public Static Methods --------------------------------------------------
00063 
00064   public static function minify($js) {
00065     $jsmin = new JSMin($js);
00066     return $jsmin->min();
00067   }
00068 
00069   // -- Public Instance Methods ------------------------------------------------
00070 
00071   public function __construct($input) {
00072     $this->input       = str_replace("\r\n", "\n", $input);
00073     $this->inputLength = strlen($this->input);
00074   }
00075 
00076   // -- Protected Instance Methods ---------------------------------------------
00077 
00078   protected function action($d) {
00079     switch($d) {
00080       case 1:
00081         $this->output[] = $this->a;
00082 
00083       case 2:
00084         $this->a = $this->b;
00085 
00086         if ($this->a === "'" || $this->a === '"') {
00087           for (;;) {
00088             $this->output[] = $this->a;
00089             $this->a        = $this->get();
00090 
00091             if ($this->a === $this->b) {
00092               break;
00093             }
00094 
00095             if (ord($this->a) <= self::ORD_LF) {
00096               throw new JSMinException('Unterminated string literal.');
00097             }
00098 
00099             if ($this->a === '\\') {
00100               $this->output[] = $this->a;
00101               $this->a        = $this->get();
00102             }
00103           }
00104         }
00105 
00106       case 3:
00107         $this->b = $this->next();
00108 
00109         if ($this->b === '/' && (
00110             $this->a === '(' || $this->a === ',' || $this->a === '=' ||
00111             $this->a === ':' || $this->a === '[' || $this->a === '!' ||
00112             $this->a === '&' || $this->a === '|' || $this->a === '?')) {
00113 
00114           $this->output[] = $this->a;
00115           $this->output[] = $this->b;
00116 
00117           for (;;) {
00118             $this->a = $this->get();
00119 
00120             if ($this->a === '/') {
00121               break;
00122             }
00123             elseif ($this->a === '\\') {
00124               $this->output[] = $this->a;
00125               $this->a        = $this->get();
00126             }
00127             elseif (ord($this->a) <= self::ORD_LF) {
00128               throw new JSMinException('Unterminated regular expression '.
00129                   'literal.');
00130             }
00131 
00132             $this->output[] = $this->a;
00133           }
00134 
00135           $this->b = $this->next();
00136         }
00137     }
00138   }
00139 
00140   protected function get() {
00141     $c = $this->lookAhead;
00142     $this->lookAhead = null;
00143 
00144     if ($c === null) {
00145       if ($this->inputIndex < $this->inputLength) {
00146         $c = $this->input[$this->inputIndex];
00147         $this->inputIndex += 1;
00148       }
00149       else {
00150         $c = null;
00151       }
00152     }
00153 
00154     if ($c === "\r") {
00155       return "\n";
00156     }
00157 
00158     if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
00159       return $c;
00160     }
00161 
00162     return ' ';
00163   }
00164 
00165   protected function isAlphaNum($c) {
00166     return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
00167   }
00168 
00169   protected function min() {
00170     $this->a = "\n";
00171     $this->action(3);
00172 
00173     while ($this->a !== null) {
00174       switch ($this->a) {
00175         case ' ':
00176           if ($this->isAlphaNum($this->b)) {
00177             $this->action(1);
00178           }
00179           else {
00180             $this->action(2);
00181           }
00182           break;
00183 
00184         case "\n":
00185           switch ($this->b) {
00186             case '{':
00187             case '[':
00188             case '(':
00189             case '+':
00190             case '-':
00191               $this->action(1);
00192               break;
00193 
00194             case ' ':
00195               $this->action(3);
00196               break;
00197 
00198             default:
00199               if ($this->isAlphaNum($this->b)) {
00200                 $this->action(1);
00201               }
00202               else {
00203                 $this->action(2);
00204               }
00205           }
00206           break;
00207 
00208         default:
00209           switch ($this->b) {
00210             case ' ':
00211               if ($this->isAlphaNum($this->a)) {
00212                 $this->action(1);
00213                 break;
00214               }
00215 
00216               $this->action(3);
00217               break;
00218 
00219             case "\n":
00220               switch ($this->a) {
00221                 case '}':
00222                 case ']':
00223                 case ')':
00224                 case '+':
00225                 case '-':
00226                 case '"':
00227                 case "'":
00228                   $this->action(1);
00229                   break;
00230 
00231                 default:
00232                   if ($this->isAlphaNum($this->a)) {
00233                     $this->action(1);
00234                   }
00235                   else {
00236                     $this->action(3);
00237                   }
00238               }
00239               break;
00240 
00241             default:
00242               $this->action(1);
00243               break;
00244           }
00245       }
00246     }
00247 
00248     return implode('', $this->output);
00249   }
00250 
00251   protected function next() {
00252     $c = $this->get();
00253 
00254     if ($c === '/') {
00255       switch($this->peek()) {
00256         case '/':
00257           for (;;) {
00258             $c = $this->get();
00259 
00260             if (ord($c) <= self::ORD_LF) {
00261               return $c;
00262             }
00263           }
00264 
00265         case '*':
00266           $this->get();
00267 
00268           for (;;) {
00269             switch($this->get()) {
00270               case '*':
00271                 if ($this->peek() === '/') {
00272                   $this->get();
00273                   return ' ';
00274                 }
00275                 break;
00276 
00277               case null:
00278                 throw new JSMinException('Unterminated comment.');
00279             }
00280           }
00281 
00282         default:
00283           return $c;
00284       }
00285     }
00286 
00287     return $c;
00288   }
00289 
00290   protected function peek() {
00291     $this->lookAhead = $this->get();
00292     return $this->lookAhead;
00293   }
00294 }
00295 
00296 // -- Exceptions ---------------------------------------------------------------
00297 class JSMinException extends Exception {}
00298 ?>