3 * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
5 * This is pretty much a direct port of jsmin.c to PHP with just a few
6 * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
7 * outputs to stdout, this library accepts a string as input and returns another
10 * PHP 5 or higher is required.
12 * Permission is hereby granted to use this version of the library under the
13 * same terms as jsmin.c, which has the following license:
16 * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
18 * Permission is hereby granted, free of charge, to any person obtaining a copy of
19 * this software and associated documentation files (the "Software"), to deal in
20 * the Software without restriction, including without limitation the rights to
21 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
22 * of the Software, and to permit persons to whom the Software is furnished to do
23 * so, subject to the following conditions:
25 * The above copyright notice and this permission notice shall be included in all
26 * copies or substantial portions of the Software.
28 * The Software shall be used for Good, not Evil.
30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40 * @author Ryan Grove <ryan@wonko.com>
41 * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
42 * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
43 * @license http://opensource.org/licenses/mit-license.php MIT License
44 * @version 1.1.1 (2008-03-02)
45 * @link http://github.com/rgrove/jsmin-php/
57 /** Current character */
63 protected $input = '';
64 protected $inputIndex = 0;
65 protected $inputLength = 0;
66 protected $lookAhead = null;
67 protected $output = '';
69 // -- Public Static Methods --------------------------------------------------
71 public static function minify( $js ) {
72 $jsmin = new self( $js );
77 // -- Public Instance Methods ------------------------------------------------
79 public function __construct( $input ) {
81 $this->input
= str_replace( "\r\n", "\n", $input );
82 // Replace tabs and other control characters (except LF) with spaces
83 $this->input
= preg_replace( '/[\x00-\x09\x0b-\x1f]/', ' ', $this->input
);
84 $this->inputLength
= strlen( $this->input
);
87 // -- Protected Instance Methods ---------------------------------------------
90 * Do something! What you do is determined by the argument:
91 * - self::OUTPUT Output A. Copy B to A. Get the next B.
92 * - self::DELETE_A Copy B to A. Get the next B. (Delete A).
93 * - self::DELETE_B Get the next B. (Delete B).
94 * action treats a string as a single character. Wow!
95 * action recognizes a regular expression if it is preceded by ( or , or =.
97 protected function action( $d ) {
100 $this->output
.= $this->a
;
105 if ( $this->a
=== "'" ||
$this->a
=== '"' ) {
106 $interestingChars = $this->a
. "\\\n";
107 $this->output
.= $this->a
;
109 $runLength = strcspn( $this->input
, $interestingChars, $this->inputIndex
);
110 $this->output
.= substr( $this->input
, $this->inputIndex
, $runLength );
111 $this->inputIndex +
= $runLength;
114 if ( $c === $this->b
) {
118 if ( $c === "\n" ||
$c === null ) {
119 throw new JSMinException( 'Unterminated string literal.' );
123 $this->output
.= $c . $this->get();
129 $this->b
= $this->next();
131 if ( $this->b
=== '/' && (
132 $this->a
=== '(' ||
$this->a
=== ',' ||
$this->a
=== '=' ||
133 $this->a
=== ':' ||
$this->a
=== '[' ||
$this->a
=== '!' ||
134 $this->a
=== '&' ||
$this->a
=== '|' ||
$this->a
=== '?' ) ) {
136 $this->output
.= $this->a
. $this->b
;
139 $runLength = strcspn( $this->input
, "/\\\n", $this->inputIndex
);
140 $this->output
.= substr( $this->input
, $this->inputIndex
, $runLength );
141 $this->inputIndex +
= $runLength;
142 $this->a
= $this->get();
144 if ( $this->a
=== '/' ) {
146 } elseif ( $this->a
=== '\\' ) {
147 $this->output
.= $this->a
;
148 $this->a
= $this->get();
149 } elseif ( $this->a
=== "\n" ||
$this->a
=== null ) {
150 throw new JSMinException( 'Unterminated regular expression ' .
154 $this->output
.= $this->a
;
157 $this->b
= $this->next();
163 * Return the next character from the input. Watch out for lookahead. If
164 * the character is a control character, translate it to a space or
167 protected function get() {
168 if ( $this->inputIndex
< $this->inputLength
) {
169 return $this->input
[$this->inputIndex++
];
176 * Return true if the character is a letter, digit, underscore,
177 * dollar sign, or non-ASCII character.
179 protected function isAlphaNum( $c ) {
180 return ord( $c ) > 126 ||
$c === '\\' ||
preg_match( '/^[\w\$]$/', $c ) === 1;
184 * Copy the input to the output, deleting the characters which are
185 * insignificant to JavaScript. Comments will be removed. Tabs will be
186 * replaced with spaces. Carriage returns will be replaced with linefeeds.
187 * Most spaces and linefeeds will be removed.
189 protected function min() {
191 $this->action( self
::DELETE_B
);
193 while ( $this->a
!== null ) {
194 switch ( $this->a
) {
196 if ( $this->isAlphaNum( $this->b
) ) {
197 $this->action( self
::OUTPUT
);
199 $this->action( self
::DELETE_A
);
204 switch ( $this->b
) {
206 $this->action( self
::DELETE_B
);
210 $this->action( self
::OUTPUT
);
215 switch ( $this->b
) {
217 if ( $this->isAlphaNum( $this->a
) ) {
218 $this->action( self
::OUTPUT
);
222 $this->action( self
::DELETE_B
);
225 $this->action( self
::OUTPUT
);
231 // Remove initial line break
232 if ( $this->output
[0] !== "\n" ) {
233 throw new JSMinException( 'Unexpected lack of line break.' );
235 if ( $this->output
=== "\n" ) {
238 return substr( $this->output
, 1 );
243 * Get the next character, excluding comments.
245 protected function next() {
246 if ( $this->inputIndex
>= $this->inputLength
) {
249 $c = $this->input
[$this->inputIndex++
];
251 if ( $this->inputIndex
>= $this->inputLength
) {
256 switch( $this->input
[$this->inputIndex
] ) {
258 $this->inputIndex +
= strcspn( $this->input
, "\n", $this->inputIndex
) +
1;
261 $endPos = strpos( $this->input
, '*/', $this->inputIndex +
1 );
262 if ( $endPos === false ) {
263 throw new JSMinException( 'Unterminated comment.' );
265 $numLines = substr_count( $this->input
, "\n", $this->inputIndex
,
266 $endPos - $this->inputIndex
);
267 $this->inputIndex
= $endPos +
2;
269 return str_repeat( "\n", $numLines );
282 // -- Exceptions ---------------------------------------------------------------
283 class JSMinException
extends Exception
{}