Follow up r78101.
[lhc/web/wiklou.git] / includes / libs / JSMin.php
1 <?php
2 /**
3 * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
4 *
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
8 * string as output.
9 *
10 * PHP 5 or higher is required.
11 *
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:
14 *
15 * --
16 * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
17 *
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:
24 *
25 * The above copyright notice and this permission notice shall be included in all
26 * copies or substantial portions of the Software.
27 *
28 * The Software shall be used for Good, not Evil.
29 *
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
36 * SOFTWARE.
37 * --
38 *
39 * @file
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/
46 */
47
48 class JSMin {
49 const ORD_LF = 10;
50 const ORD_SPACE = 32;
51
52 // Action constants
53 const OUTPUT = 1;
54 const DELETE_A = 2;
55 const DELETE_B = 3;
56
57 /** Current character */
58 protected $a = '';
59
60 /** Next character */
61 protected $b = '';
62
63 protected $input = '';
64 protected $inputIndex = 0;
65 protected $inputLength = 0;
66 protected $lookAhead = null;
67 protected $output = '';
68
69 // -- Public Static Methods --------------------------------------------------
70
71 public static function minify( $js ) {
72 $jsmin = new self( $js );
73 $ret = $jsmin->min();
74 return $ret;
75 }
76
77 // -- Public Instance Methods ------------------------------------------------
78
79 public function __construct( $input ) {
80 // Fix line endings
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 );
85 }
86
87 // -- Protected Instance Methods ---------------------------------------------
88
89 /**
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 =.
96 */
97 protected function action( $d ) {
98 switch( $d ) {
99 case self::OUTPUT:
100 $this->output .= $this->a;
101
102 case self::DELETE_A:
103 $this->a = $this->b;
104
105 if ( $this->a === "'" || $this->a === '"' ) {
106 $interestingChars = $this->a . "\\\n";
107 $this->output .= $this->a;
108 for ( ; ; ) {
109 $runLength = strcspn( $this->input, $interestingChars, $this->inputIndex );
110 $this->output .= substr( $this->input, $this->inputIndex, $runLength );
111 $this->inputIndex += $runLength;
112 $c = $this->get();
113
114 if ( $c === $this->b ) {
115 break;
116 }
117
118 if ( $c === "\n" || $c === null ) {
119 throw new JSMinException( 'Unterminated string literal.' );
120 }
121
122 if ( $c === '\\' ) {
123 $this->output .= $c . $this->get();
124 }
125 }
126 }
127
128 case self::DELETE_B:
129 $this->b = $this->next();
130
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 === '?' ) ) {
135
136 $this->output .= $this->a . $this->b;
137
138 for ( ; ; ) {
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();
143
144 if ( $this->a === '/' ) {
145 break;
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 ' .
151 'literal.' );
152 }
153
154 $this->output .= $this->a;
155 }
156
157 $this->b = $this->next();
158 }
159 }
160 }
161
162 /**
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
165 * linefeed.
166 */
167 protected function get() {
168 if ( $this->inputIndex < $this->inputLength ) {
169 return $this->input[$this->inputIndex++];
170 } else {
171 return null;
172 }
173 }
174
175 /**
176 * Return true if the character is a letter, digit, underscore,
177 * dollar sign, or non-ASCII character.
178 */
179 protected function isAlphaNum( $c ) {
180 return ord( $c ) > 126 || $c === '\\' || preg_match( '/^[\w\$]$/', $c ) === 1;
181 }
182
183 /**
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.
188 */
189 protected function min() {
190 $this->a = "\n";
191 $this->action( self::DELETE_B );
192
193 while ( $this->a !== null ) {
194 switch ( $this->a ) {
195 case ' ':
196 if ( $this->isAlphaNum( $this->b ) ) {
197 $this->action( self::OUTPUT );
198 } else {
199 $this->action( self::DELETE_A );
200 }
201 break;
202
203 case "\n":
204 switch ( $this->b ) {
205 case ' ':
206 $this->action( self::DELETE_B );
207 break;
208
209 default:
210 $this->action( self::OUTPUT );
211 }
212 break;
213
214 default:
215 switch ( $this->b ) {
216 case ' ':
217 if ( $this->isAlphaNum( $this->a ) ) {
218 $this->action( self::OUTPUT );
219 break;
220 }
221
222 $this->action( self::DELETE_B );
223 break;
224 default:
225 $this->action( self::OUTPUT );
226 break;
227 }
228 }
229 }
230
231 // Remove initial line break
232 if ( $this->output[0] !== "\n" ) {
233 throw new JSMinException( 'Unexpected lack of line break.' );
234 }
235 if ( $this->output === "\n" ) {
236 return '';
237 } else {
238 return substr( $this->output, 1 );
239 }
240 }
241
242 /**
243 * Get the next character, excluding comments.
244 */
245 protected function next() {
246 if ( $this->inputIndex >= $this->inputLength ) {
247 return null;
248 }
249 $c = $this->input[$this->inputIndex++];
250
251 if ( $this->inputIndex >= $this->inputLength ) {
252 return $c;
253 }
254
255 if ( $c === '/' ) {
256 switch( $this->input[$this->inputIndex] ) {
257 case '/':
258 $this->inputIndex += strcspn( $this->input, "\n", $this->inputIndex ) + 1;
259 return "\n";
260 case '*':
261 $endPos = strpos( $this->input, '*/', $this->inputIndex + 1 );
262 if ( $endPos === false ) {
263 throw new JSMinException( 'Unterminated comment.' );
264 }
265 $numLines = substr_count( $this->input, "\n", $this->inputIndex,
266 $endPos - $this->inputIndex );
267 $this->inputIndex = $endPos + 2;
268 if ( $numLines ) {
269 return str_repeat( "\n", $numLines );
270 } else {
271 return ' ';
272 }
273 default:
274 return $c;
275 }
276 }
277
278 return $c;
279 }
280 }
281
282 // -- Exceptions ---------------------------------------------------------------
283 class JSMinException extends Exception {}