]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappix/php/jsmin.php
Twitter: Fetch the contact relation
[friendica-addons.git] / jappixmini / jappix / php / 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  * @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 https://github.com/rgrove/jsmin-php/
46  */
47
48 class JSMin {
49   const ORD_LF            = 10;
50   const ORD_SPACE         = 32;
51   const ACTION_KEEP_A     = 1;
52   const ACTION_DELETE_A   = 2;
53   const ACTION_DELETE_A_B = 3;
54
55   protected $a           = '';
56   protected $b           = '';
57   protected $input       = '';
58   protected $inputIndex  = 0;
59   protected $inputLength = 0;
60   protected $lookAhead   = null;
61   protected $output      = '';
62
63   // -- Public Static Methods --------------------------------------------------
64
65   /**
66    * Minify Javascript
67    *
68    * @uses __construct()
69    * @uses min()
70    * @param string $js Javascript to be minified
71    * @return string
72    */
73   public static function minify($js) {
74     $jsmin = new JSMin($js);
75     return $jsmin->min();
76   }
77
78   // -- Public Instance Methods ------------------------------------------------
79
80   /**
81    * Constructor
82    *
83    * @param string $input Javascript to be minified
84    */
85   public function __construct($input) {
86     $this->input       = str_replace("\r\n", "\n", $input);
87     $this->inputLength = strlen($this->input);
88   }
89
90   // -- Protected Instance Methods ---------------------------------------------
91
92   /**
93    * Action -- do something! What to do is determined by the $command argument.
94    *
95    * action treats a string as a single character. Wow!
96    * action recognizes a regular expression if it is preceded by ( or , or =.
97    *
98    * @uses next()
99    * @uses get()
100    * @throws JSMinException If parser errors are found:
101    *         - Unterminated string literal
102    *         - Unterminated regular expression set in regex literal
103    *         - Unterminated regular expression literal
104    * @param int $command One of class constants:
105    *      ACTION_KEEP_A      Output A. Copy B to A. Get the next B.
106    *      ACTION_DELETE_A    Copy B to A. Get the next B. (Delete A).
107    *      ACTION_DELETE_A_B  Get the next B. (Delete B).
108   */
109   protected function action($command) {
110     switch($command) {
111       case self::ACTION_KEEP_A:
112         $this->output .= $this->a;
113
114       case self::ACTION_DELETE_A:
115         $this->a = $this->b;
116
117         if ($this->a === "'" || $this->a === '"') {
118           for (;;) {
119             $this->output .= $this->a;
120             $this->a       = $this->get();
121
122             if ($this->a === $this->b) {
123               break;
124             }
125
126             if (ord($this->a) <= self::ORD_LF) {
127               throw new JSMinException('Unterminated string literal.');
128             }
129
130             if ($this->a === '\\') {
131               $this->output .= $this->a;
132               $this->a       = $this->get();
133             }
134           }
135         }
136
137       case self::ACTION_DELETE_A_B:
138         $this->b = $this->next();
139
140         if ($this->b === '/' && (
141             $this->a === '(' || $this->a === ',' || $this->a === '=' ||
142             $this->a === ':' || $this->a === '[' || $this->a === '!' ||
143             $this->a === '&' || $this->a === '|' || $this->a === '?' ||
144             $this->a === '{' || $this->a === '}' || $this->a === ';' ||
145             $this->a === "\n" )) {
146
147           $this->output .= $this->a . $this->b;
148
149           for (;;) {
150             $this->a = $this->get();
151
152             if ($this->a === '[') {
153               /*
154                 inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
155                   return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
156               */
157               for (;;) {
158                 $this->output .= $this->a;
159                 $this->a = $this->get();
160
161                 if ($this->a === ']') {
162                     break;
163                 } elseif ($this->a === '\\') {
164                   $this->output .= $this->a;
165                   $this->a       = $this->get();
166                 } elseif (ord($this->a) <= self::ORD_LF) {
167                   throw new JSMinException('Unterminated regular expression set in regex literal.');
168                 }
169               }
170             } elseif ($this->a === '/') {
171               break;
172             } elseif ($this->a === '\\') {
173               $this->output .= $this->a;
174               $this->a       = $this->get();
175             } elseif (ord($this->a) <= self::ORD_LF) {
176               throw new JSMinException('Unterminated regular expression literal.');
177             }
178
179             $this->output .= $this->a;
180           }
181
182           $this->b = $this->next();
183         }
184     }
185   }
186
187   /**
188    * Get next char. Convert ctrl char to space.
189    *
190    * @return string|null
191    */
192   protected function get() {
193     $c = $this->lookAhead;
194     $this->lookAhead = null;
195
196     if ($c === null) {
197       if ($this->inputIndex < $this->inputLength) {
198         $c = substr($this->input, $this->inputIndex, 1);
199         $this->inputIndex += 1;
200       } else {
201         $c = null;
202       }
203     }
204
205     if ($c === "\r") {
206       return "\n";
207     }
208
209     if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
210       return $c;
211     }
212
213     return ' ';
214   }
215
216   /**
217    * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
218    *
219    * @return bool
220    */
221   protected function isAlphaNum($c) {
222     return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
223   }
224
225   /**
226    * Perform minification, return result
227    *
228    * @uses action()
229    * @uses isAlphaNum()
230    * @return string
231    */
232   protected function min() {
233     $this->a = "\n";
234     $this->action(self::ACTION_DELETE_A_B);
235
236     while ($this->a !== null) {
237       switch ($this->a) {
238         case ' ':
239           if ($this->isAlphaNum($this->b)) {
240             $this->action(self::ACTION_KEEP_A);
241           } else {
242             $this->action(self::ACTION_DELETE_A);
243           }
244           break;
245
246         case "\n":
247           switch ($this->b) {
248             case '{':
249             case '[':
250             case '(':
251             case '+':
252             case '-':
253               $this->action(self::ACTION_KEEP_A);
254               break;
255
256             case ' ':
257               $this->action(self::ACTION_DELETE_A_B);
258               break;
259
260             default:
261               if ($this->isAlphaNum($this->b)) {
262                 $this->action(self::ACTION_KEEP_A);
263               }
264               else {
265                 $this->action(self::ACTION_DELETE_A);
266               }
267           }
268           break;
269
270         default:
271           switch ($this->b) {
272             case ' ':
273               if ($this->isAlphaNum($this->a)) {
274                 $this->action(self::ACTION_KEEP_A);
275                 break;
276               }
277
278               $this->action(self::ACTION_DELETE_A_B);
279               break;
280
281             case "\n":
282               switch ($this->a) {
283                 case '}':
284                 case ']':
285                 case ')':
286                 case '+':
287                 case '-':
288                 case '"':
289                 case "'":
290                   $this->action(self::ACTION_KEEP_A);
291                   break;
292
293                 default:
294                   if ($this->isAlphaNum($this->a)) {
295                     $this->action(self::ACTION_KEEP_A);
296                   }
297                   else {
298                     $this->action(self::ACTION_DELETE_A_B);
299                   }
300               }
301               break;
302
303             default:
304               $this->action(self::ACTION_KEEP_A);
305               break;
306           }
307       }
308     }
309
310     return $this->output;
311   }
312
313   /**
314    * Get the next character, skipping over comments. peek() is used to see
315    *  if a '/' is followed by a '/' or '*'.
316    *
317    * @uses get()
318    * @uses peek()
319    * @throws JSMinException On unterminated comment.
320    * @return string
321    */
322   protected function next() {
323     $c = $this->get();
324
325     if ($c === '/') {
326       switch($this->peek()) {
327         case '/':
328           for (;;) {
329             $c = $this->get();
330
331             if (ord($c) <= self::ORD_LF) {
332               return $c;
333             }
334           }
335
336         case '*':
337           $this->get();
338
339           for (;;) {
340             switch($this->get()) {
341               case '*':
342                 if ($this->peek() === '/') {
343                   $this->get();
344                   return ' ';
345                 }
346                 break;
347
348               case null:
349                 throw new JSMinException('Unterminated comment.');
350             }
351           }
352
353         default:
354           return $c;
355       }
356     }
357
358     return $c;
359   }
360
361   /**
362    * Get next char. If is ctrl character, translate to a space or newline.
363    *
364    * @uses get()
365    * @return string|null
366    */
367   protected function peek() {
368     $this->lookAhead = $this->get();
369     return $this->lookAhead;
370   }
371 }
372
373 // -- Exceptions ---------------------------------------------------------------
374 class JSMinException extends Exception {}
375 ?>