00001 <?php
00048 class JSMin {
00049 const ORD_LF = 10;
00050 const ORD_SPACE = 32;
00051
00052 protected $a = '';
00053 protected $b = '';
00054 protected $input = '';
00055 protected $inputIndex = 0;
00056 protected $inputLength = 0;
00057 protected $lookAhead = null;
00058 protected $output = '';
00059
00060
00061
00062 public static function minify( $js ) {
00063 $jsmin = new JSMin( $js );
00064 return $jsmin->min();
00065 }
00066
00067
00068
00069 public function __construct( $input ) {
00070 $this->input = str_replace( "\r\n", "\n", $input );
00071 $this->inputLength = strlen( $this->input );
00072 }
00073
00074
00075
00076 protected function action( $d ) {
00077 switch( $d ) {
00078 case 1:
00079 $this->output .= $this->a;
00080
00081 case 2:
00082 $this->a = $this->b;
00083
00084 if ( $this->a === "'" || $this->a === '"' ) {
00085 for ( ; ; ) {
00086 $this->output .= $this->a;
00087 $this->a = $this->get();
00088
00089 if ( $this->a === $this->b ) {
00090 break;
00091 }
00092
00093 if ( ord( $this->a ) <= self::ORD_LF ) {
00094 throw new JSMinException( 'Unterminated string literal.' );
00095 }
00096
00097 if ( $this->a === '\\' ) {
00098 $this->output .= $this->a;
00099 $this->a = $this->get();
00100 }
00101 }
00102 }
00103
00104 case 3:
00105 $this->b = $this->next();
00106
00107 if ( $this->b === '/' && (
00108 $this->a === '(' || $this->a === ',' || $this->a === '=' ||
00109 $this->a === ':' || $this->a === '[' || $this->a === '!' ||
00110 $this->a === '&' || $this->a === '|' || $this->a === '?' ) ) {
00111
00112 $this->output .= $this->a . $this->b;
00113
00114 for ( ; ; ) {
00115 $this->a = $this->get();
00116
00117 if ( $this->a === '/' ) {
00118 break;
00119 } elseif ( $this->a === '\\' ) {
00120 $this->output .= $this->a;
00121 $this->a = $this->get();
00122 } elseif ( ord( $this->a ) <= self::ORD_LF ) {
00123 throw new JSMinException( 'Unterminated regular expression ' .
00124 'literal.' );
00125 }
00126
00127 $this->output .= $this->a;
00128 }
00129
00130 $this->b = $this->next();
00131 }
00132 }
00133 }
00134
00135 protected function get() {
00136 $c = $this->lookAhead;
00137 $this->lookAhead = null;
00138
00139 if ( $c === null ) {
00140 if ( $this->inputIndex < $this->inputLength ) {
00141 $c = substr( $this->input, $this->inputIndex, 1 );
00142 $this->inputIndex += 1;
00143 } else {
00144 $c = null;
00145 }
00146 }
00147
00148 if ( $c === "\r" ) {
00149 return "\n";
00150 }
00151
00152 if ( $c === null || $c === "\n" || ord( $c ) >= self::ORD_SPACE ) {
00153 return $c;
00154 }
00155
00156 return ' ';
00157 }
00158
00159 protected function isAlphaNum( $c ) {
00160 return ord( $c ) > 126 || $c === '\\' || preg_match( '/^[\w\$]$/', $c ) === 1;
00161 }
00162
00163 protected function min() {
00164 $this->a = "\n";
00165 $this->action( 3 );
00166
00167 while ( $this->a !== null ) {
00168 switch ( $this->a ) {
00169 case ' ':
00170 if ( $this->isAlphaNum( $this->b ) ) {
00171 $this->action( 1 );
00172 } else {
00173 $this->action( 2 );
00174 }
00175 break;
00176
00177 case "\n":
00178 switch ( $this->b ) {
00179 case '{':
00180 case '[':
00181 case '(':
00182 case '+':
00183 case '-':
00184 $this->action( 1 );
00185 break;
00186
00187 case ' ':
00188 $this->action( 3 );
00189 break;
00190
00191 default:
00192 if ( $this->isAlphaNum( $this->b ) ) {
00193 $this->action( 1 );
00194 }
00195 else {
00196 $this->action( 2 );
00197 }
00198 }
00199 break;
00200
00201 default:
00202 switch ( $this->b ) {
00203 case ' ':
00204 if ( $this->isAlphaNum( $this->a ) ) {
00205 $this->action( 1 );
00206 break;
00207 }
00208
00209 $this->action( 3 );
00210 break;
00211
00212 case "\n":
00213 switch ( $this->a ) {
00214 case '}':
00215 case ']':
00216 case ')':
00217 case '+':
00218 case '-':
00219 case '"':
00220 case "'":
00221 $this->action( 1 );
00222 break;
00223
00224 default:
00225 if ( $this->isAlphaNum( $this->a ) ) {
00226 $this->action( 1 );
00227 }
00228 else {
00229 $this->action( 3 );
00230 }
00231 }
00232 break;
00233
00234 default:
00235 $this->action( 1 );
00236 break;
00237 }
00238 }
00239 }
00240
00241 return $this->output;
00242 }
00243
00244 protected function next() {
00245 $c = $this->get();
00246
00247 if ( $c === '/' ) {
00248 switch( $this->peek() ) {
00249 case '/':
00250 for ( ; ; ) {
00251 $c = $this->get();
00252
00253 if ( ord( $c ) <= self::ORD_LF ) {
00254 return $c;
00255 }
00256 }
00257
00258 case '*':
00259 $this->get();
00260
00261 for ( ; ; ) {
00262 switch( $this->get() ) {
00263 case '*':
00264 if ( $this->peek() === '/' ) {
00265 $this->get();
00266 return ' ';
00267 }
00268 break;
00269
00270 case null:
00271 throw new JSMinException( 'Unterminated comment.' );
00272 }
00273 }
00274
00275 default:
00276 return $c;
00277 }
00278 }
00279
00280 return $c;
00281 }
00282
00283 protected function peek() {
00284 $this->lookAhead = $this->get();
00285 return $this->lookAhead;
00286 }
00287 }
00288
00289
00290 class JSMinException extends Exception {}