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 | * @package JSMin |
---|
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://code.google.com/p/jsmin-php/ |
---|
46 | */ |
---|
47 | |
---|
48 | class JSMin { |
---|
49 | const ORD_LF = 10; |
---|
50 | const ORD_SPACE = 32; |
---|
51 | |
---|
52 | protected $a = ''; |
---|
53 | protected $b = ''; |
---|
54 | protected $input = ''; |
---|
55 | protected $inputIndex = 0; |
---|
56 | protected $inputLength = 0; |
---|
57 | protected $lookAhead = null; |
---|
58 | protected $output = ''; |
---|
59 | |
---|
60 | // -- Public Static Methods -------------------------------------------------- |
---|
61 | |
---|
62 | public static function minify($js) { |
---|
63 | $jsmin = new JSMin($js); |
---|
64 | return $jsmin->min(); |
---|
65 | } |
---|
66 | |
---|
67 | // -- Public Instance Methods ------------------------------------------------ |
---|
68 | |
---|
69 | public function __construct($input) { |
---|
70 | $this->input = str_replace("\r\n", "\n", $input); |
---|
71 | $this->inputLength = strlen($this->input); |
---|
72 | } |
---|
73 | |
---|
74 | // -- Protected Instance Methods --------------------------------------------- |
---|
75 | |
---|
76 | protected function action($d) { |
---|
77 | switch($d) { |
---|
78 | case 1: |
---|
79 | $this->output .= $this->a; |
---|
80 | |
---|
81 | case 2: |
---|
82 | $this->a = $this->b; |
---|
83 | |
---|
84 | if ($this->a === "'" || $this->a === '"') { |
---|
85 | for (;;) { |
---|
86 | $this->output .= $this->a; |
---|
87 | $this->a = $this->get(); |
---|
88 | |
---|
89 | if ($this->a === $this->b) { |
---|
90 | break; |
---|
91 | } |
---|
92 | |
---|
93 | if (ord($this->a) <= self::ORD_LF) { |
---|
94 | throw new JSMinException('Unterminated string literal.'); |
---|
95 | } |
---|
96 | |
---|
97 | if ($this->a === '\\') { |
---|
98 | $this->output .= $this->a; |
---|
99 | $this->a = $this->get(); |
---|
100 | } |
---|
101 | } |
---|
102 | } |
---|
103 | |
---|
104 | case 3: |
---|
105 | $this->b = $this->next(); |
---|
106 | |
---|
107 | if ($this->b === '/' && ( |
---|
108 | $this->a === '(' || $this->a === ',' || $this->a === '=' || |
---|
109 | $this->a === ':' || $this->a === '[' || $this->a === '!' || |
---|
110 | $this->a === '&' || $this->a === '|' || $this->a === '?')) { |
---|
111 | |
---|
112 | $this->output .= $this->a . $this->b; |
---|
113 | |
---|
114 | for (;;) { |
---|
115 | $this->a = $this->get(); |
---|
116 | |
---|
117 | if ($this->a === '/') { |
---|
118 | break; |
---|
119 | } elseif ($this->a === '\\') { |
---|
120 | $this->output .= $this->a; |
---|
121 | $this->a = $this->get(); |
---|
122 | } elseif (ord($this->a) <= self::ORD_LF) { |
---|
123 | throw new JSMinException('Unterminated regular expression '. |
---|
124 | 'literal.'); |
---|
125 | } |
---|
126 | |
---|
127 | $this->output .= $this->a; |
---|
128 | } |
---|
129 | |
---|
130 | $this->b = $this->next(); |
---|
131 | } |
---|
132 | } |
---|
133 | } |
---|
134 | |
---|
135 | protected function get() { |
---|
136 | $c = $this->lookAhead; |
---|
137 | $this->lookAhead = null; |
---|
138 | |
---|
139 | if ($c === null) { |
---|
140 | if ($this->inputIndex < $this->inputLength) { |
---|
141 | $c = substr($this->input, $this->inputIndex, 1); |
---|
142 | $this->inputIndex += 1; |
---|
143 | } else { |
---|
144 | $c = null; |
---|
145 | } |
---|
146 | } |
---|
147 | |
---|
148 | if ($c === "\r") { |
---|
149 | return "\n"; |
---|
150 | } |
---|
151 | |
---|
152 | if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { |
---|
153 | return $c; |
---|
154 | } |
---|
155 | |
---|
156 | return ' '; |
---|
157 | } |
---|
158 | |
---|
159 | protected function isAlphaNum($c) { |
---|
160 | return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; |
---|
161 | } |
---|
162 | |
---|
163 | protected function min() { |
---|
164 | $this->a = "\n"; |
---|
165 | $this->action(3); |
---|
166 | |
---|
167 | while ($this->a !== null) { |
---|
168 | switch ($this->a) { |
---|
169 | case ' ': |
---|
170 | if ($this->isAlphaNum($this->b)) { |
---|
171 | $this->action(1); |
---|
172 | } else { |
---|
173 | $this->action(2); |
---|
174 | } |
---|
175 | break; |
---|
176 | |
---|
177 | case "\n": |
---|
178 | switch ($this->b) { |
---|
179 | case '{': |
---|
180 | case '[': |
---|
181 | case '(': |
---|
182 | case '+': |
---|
183 | case '-': |
---|
184 | $this->action(1); |
---|
185 | break; |
---|
186 | |
---|
187 | case ' ': |
---|
188 | $this->action(3); |
---|
189 | break; |
---|
190 | |
---|
191 | default: |
---|
192 | if ($this->isAlphaNum($this->b)) { |
---|
193 | $this->action(1); |
---|
194 | } |
---|
195 | else { |
---|
196 | $this->action(2); |
---|
197 | } |
---|
198 | } |
---|
199 | break; |
---|
200 | |
---|
201 | default: |
---|
202 | switch ($this->b) { |
---|
203 | case ' ': |
---|
204 | if ($this->isAlphaNum($this->a)) { |
---|
205 | $this->action(1); |
---|
206 | break; |
---|
207 | } |
---|
208 | |
---|
209 | $this->action(3); |
---|
210 | break; |
---|
211 | |
---|
212 | case "\n": |
---|
213 | switch ($this->a) { |
---|
214 | case '}': |
---|
215 | case ']': |
---|
216 | case ')': |
---|
217 | case '+': |
---|
218 | case '-': |
---|
219 | case '"': |
---|
220 | case "'": |
---|
221 | $this->action(1); |
---|
222 | break; |
---|
223 | |
---|
224 | default: |
---|
225 | if ($this->isAlphaNum($this->a)) { |
---|
226 | $this->action(1); |
---|
227 | } |
---|
228 | else { |
---|
229 | $this->action(3); |
---|
230 | } |
---|
231 | } |
---|
232 | break; |
---|
233 | |
---|
234 | default: |
---|
235 | $this->action(1); |
---|
236 | break; |
---|
237 | } |
---|
238 | } |
---|
239 | } |
---|
240 | |
---|
241 | return $this->output; |
---|
242 | } |
---|
243 | |
---|
244 | protected function next() { |
---|
245 | $c = $this->get(); |
---|
246 | |
---|
247 | if ($c === '/') { |
---|
248 | switch($this->peek()) { |
---|
249 | case '/': |
---|
250 | for (;;) { |
---|
251 | $c = $this->get(); |
---|
252 | |
---|
253 | if (ord($c) <= self::ORD_LF) { |
---|
254 | return $c; |
---|
255 | } |
---|
256 | } |
---|
257 | |
---|
258 | case '*': |
---|
259 | $this->get(); |
---|
260 | |
---|
261 | for (;;) { |
---|
262 | switch($this->get()) { |
---|
263 | case '*': |
---|
264 | if ($this->peek() === '/') { |
---|
265 | $this->get(); |
---|
266 | return ' '; |
---|
267 | } |
---|
268 | break; |
---|
269 | |
---|
270 | case null: |
---|
271 | throw new JSMinException('Unterminated comment.'); |
---|
272 | } |
---|
273 | } |
---|
274 | |
---|
275 | default: |
---|
276 | return $c; |
---|
277 | } |
---|
278 | } |
---|
279 | |
---|
280 | return $c; |
---|
281 | } |
---|
282 | |
---|
283 | protected function peek() { |
---|
284 | $this->lookAhead = $this->get(); |
---|
285 | return $this->lookAhead; |
---|
286 | } |
---|
287 | } |
---|
288 | |
---|
289 | // -- Exceptions --------------------------------------------------------------- |
---|
290 | class JSMinException extends Exception {} |
---|
291 | ?> |
---|