]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/jsmin.php
Release 6.2.2
[Github/sugarcrm.git] / jssource / jsmin.php
1 <?php
2
3 class JSMin {
4     /**
5      * Calls the SugarMin minify function.
6      *
7      * @param string $js Javascript to be minified
8      * @return string Minified javascript
9      */
10     public static function minify($js, $filename = '') {
11         return SugarMin::minify($js);
12     }
13 }
14
15 /**
16  * SugarMin is a Javascript minifier with two levels of compression. The default compression
17  * is set to light. Light compression will preserve some line breaks that may interfere with
18  * operation of the script. Deep compression will remove all the line breaks, but before using
19  * deep compression make sure the script has passed JSLint.
20  */
21 class SugarMin {
22     protected $noSpaceChars = array('\\', '$', '_', '/');
23     protected $postNewLineSafeChars = array('\\', '$', '_', '{', '[', '(', '+', '-');
24     protected $preNewLineSafeChars = array('\\', '$', '_', '}', ']', ')', '+', '-', '"', "'");
25     protected $regexChars = array('(', ',',  '=', ':', '[', '!', '&', '|', '?', '{', '}', ';');
26     protected $compression;
27
28     private function __construct() {}
29
30     /**
31      * Entry point function to minify javascript.
32      *
33      * @param string $js Javascript source code as a string.
34      * @param string $compression Compression option. {light, deep}.
35      * @return string $output Output javascript code as a string.
36      */
37     static public function minify($js, $compression = 'light') {
38         try {
39             $me = new SugarMin();
40             $output = $me->jsParser($js, $compression);
41             return $output;
42         } catch (Exception $e) {
43             // Exception handling is left up to the implementer.
44             throw $e;
45         }
46     }
47
48     /**
49          * jsParser will take javascript source code and minify it.
50      *
51      * Note: There is a lot of redundant code since both passes
52      * operate similarly but with slight differences. It will probably
53      * be a good idea to refactor the code at a later point when it is stable.
54          *
55      * JSParser will perform 3 passes on the code. Pass 1 takes care of single
56      * line and mult-line comments. Pass 2 performs some sanitation on each of the lines
57      * and pass 3 works on stripping out unnecessary spaces.
58      *
59          * @param string $js
60          * @param string $currentOptions
61          * @return void
62          */
63     protected function jsParser($js, $compression = 'light') {
64
65         // We first perform a few operations to simplify our
66         // minification process. We convert all carriage returns
67         // into line breaks.
68         $js = str_replace("\r\n", "\n", $js);
69         $js = str_replace("\r", "\n", $js);
70         $js = preg_replace("/\n+/","\n", $js);
71
72         $stripped_js = '';
73         $prevChar = null;
74         $lastChar = null;
75
76         // Pass 1, strip out single line and multi-line comments.
77         for ($i = 0; $i < strlen($js); $i++) {
78             $char = $js[$i];
79
80             if (strlen($stripped_js) > 0) {
81                 $lastChar = $stripped_js[strlen($stripped_js) - 1];
82             }
83
84             if ($lastChar != " " && $lastChar != "\n" && $lastChar != null) {
85                 $prevChar = $lastChar;
86             }
87             
88             switch ($char) {
89                 case "\\": // If escape character
90                     $stripped_js .= $char.$js[$i + 1];
91                     $i++;
92                     break;
93                 case '"': // If string literal
94                 case "'":
95                     $literal = $delimiter = $char;
96
97                     for ($j = $i + 1; $j < strlen($js); $j++) {
98                         $literal .= $js[$j];
99
100                         if ($js[$j] == "\\") {
101                             $literal .= $js[$j + 1];
102                             $j++;
103                             continue;
104                         }
105
106                         if ($js[$j] == $delimiter) {
107                             break;
108                         }
109                     }
110
111                     $i = $j;
112                     $stripped_js .= $literal;
113                     break;
114                 case "/": // If comment or regex
115                     if ($js[$i + 1] == "/") {
116                         $comment = $char;
117                         for ($j = $i + 1; $j < strlen($js); $j++) {
118                             if ($js[$j] == "\\") {
119                                 $j++;
120                                 continue;
121                             }
122
123                             if ($js[$j + 1] == "\n") {
124                                 break;
125                             }
126
127                             $comment .= $js[$j];
128                         }
129                         $i = $j;
130                         break;
131                     } else if ($js[$i + 1] == "*") {
132                         $mlcomment = $char;
133                         for ($j = $i + 1; $j < strlen($js); $j++) {
134                             if ($js[$j] == "\\") {
135                                 $j++;
136                                 continue;
137                             }
138
139                             if ($js[$j] == "*" && $js[$j + 1] == "/") {
140                                 break;
141                             }
142
143                             $mlcomment .= $js[$j];
144                         }
145
146                         $i = $j + 1;
147                         break;
148                     } else if (in_array($prevChar, $this->regexChars) || $prevChar == null) {
149                         $nesting = 0;
150                         $stripped_js .= $js[$i];
151
152                         for ($j = $i + 1; $j < strlen($js); $j++) {
153                             if ($js[$j] == "\\") {
154                                 $stripped_js .= $js[$j].$js[$j + 1];
155                                 $j++;
156                                 continue;
157                             }
158
159                             if ($js[$j] == '[') {
160                                 $nesting++;
161                             } else if ($js[$j] == ']') {
162                                 $nesting--;
163                             }
164
165                             $stripped_js .= $js[$j];
166
167                             if ($js[$j] == '/' && $nesting == 0 && $prevChar != "\\") {
168                                 break;
169                             }
170                         }
171                         $i = $j;
172                         break;
173                     }
174                 default:
175                     $stripped_js .= $char;
176                     break;
177             }
178         }
179
180         // Split our string up into an array and iterate over each line
181         // to do processing.
182         $input = explode("\n", $stripped_js);
183         $primedInput = '';
184
185         // Pass 2, remove space and tabs from each line.
186         for ($index = 0; $index < count($input); $index++) {
187             $line = $input[$index];
188
189             $line = trim($line, " \t");
190
191             // If the line is empty, ignore it.
192             if (strlen($line) == 0) {
193                 continue;
194             }
195
196             $primedInput[] = $line;
197         }
198
199         $input = $primedInput;
200         $output = '';
201
202         // Pass 3, remove extra spaces
203         for ($index = 0; $index < count($input); $index++) {
204             $line = $input[$index];
205             $newLine = '';
206             $len = strlen($line);
207
208             $nextLine = ($index < count($input) -1 ) ? $input[$index + 1] : '';
209
210             $lastChar = ($len > 0) ? $line[$len - 1] : $line[0];
211             $nextChar = ($nextLine) ? $nextLine[0] : null;
212
213             // Iterate through the string one character at a time.
214             for ($i = 0; $i < $len; $i++) {
215                 switch($line[$i]) {
216                     case "\\":
217                         $newLine .= $line[$i].$line[$i + 1];
218                         $i++;
219                         break;
220                     case '/':
221                         // Check if regular expression
222                         if (strlen($newLine) > 0 && in_array($newLine[strlen($newLine) - 1], $this->regexChars)) {
223                             $nesting = 0;
224                             $newLine .= $line[$i];
225
226                             for ($j = $i + 1; $j < $len; $j++) {
227                                 if ($line[$j] == "\\") {
228                                     $newLine .= $line[$j].$line[$j + 1];
229                                     $j++;
230                                     continue;
231                                 }
232
233                                 if ($line[$j] == '[') {
234                                     $nesting++;
235                                 } else if ($line[$j] == ']') {
236                                     $nesting--;
237                                 }
238
239                                 $newLine .= $line[$j];
240                                 if ($line[$j] == '/' && $nesting == 0 && $newLine[strlen($newLine) - 1] != "\\") {
241                                     break;
242                                 }
243                             }
244                             $i = $j;
245                         } else {
246                             $newLine .= $line[$i];
247                         }
248                         break;
249                     // String literals shall be transcribed as is.
250                     case '"':
251                     case "'":
252                         $literal = $delimiter = $line[$i];
253
254                         for ($j = $i + 1; $j < strlen($line); $j++) {
255                             $literal .= $line[$j];
256
257                             if ($line[$j] == "\\") {
258                                 $literal .= $line[$j + 1];
259                                 $j++;
260                                 continue;
261                             }
262
263                             if ($line[$j] == $delimiter) {
264                                 break;
265                             }
266
267                             if ($line[$j] == "\n") {
268
269                             }
270                         }
271
272                         $i = $j;
273                         $newLine .= $literal;
274                         break;
275                     // Tabs must be replaced with spaces and then re-evaluated to see if the space is necessary.
276                     case "\t":
277                         $line[$i] = " ";
278                         $i--;
279                         break;
280                     case ' ':
281                         if ( !(
282                                 (ctype_alnum($line[$i - 1]) || in_array($line[$i - 1], $this->noSpaceChars))
283                                 &&
284                                 (in_array($line[$i+1], $this->noSpaceChars) || ctype_alnum($line[$i+1]))
285                             )) {
286                             // Omit space;
287                             break;
288                         }
289                     default:
290                         $newLine .= $line[$i];
291                         break;
292                 }
293             }
294
295             if ((ctype_alnum($lastChar) || in_array($lastChar, $this->preNewLineSafeChars)) && ((in_array($nextChar, $this->postNewLineSafeChars) || ctype_alnum($nextChar)))) {
296                 $newLine .= "\n";
297             }
298
299             $output .= $newLine;
300         }
301
302         if ($compression == 'deep') {
303             return trim(str_replace("\n", "", $output));
304         } else {
305            return "\n".$output."\n";
306         }
307         }
308 }