| 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 2007 Ryan Grove <ryan@wonko.com> (PHP port) |
|---|
| 43 | | * @license http://opensource.org/licenses/mit-license.php MIT License |
|---|
| 44 | | * @version 1.1.0 (2007-06-01) |
|---|
| 45 | | * @link http://code.google.com/p/jsmin-php/ |
|---|
| | 3 | * JsMinEnh.php (for PHP 5 only) |
|---|
| | 4 | * |
|---|
| | 5 | * Based on "PHP adaptation of JSMin" |
|---|
| | 6 | * <http://gggeek.altervista.org/2006/12/22/shrink-the-size-of-your-javascript-with-js-min-the-php-way/> |
|---|
| | 7 | * but freely adapted to my own needs. |
|---|
| | 8 | * |
|---|
| | 9 | * PHP adaptation of JSMin, published by Douglas Crockford as jsmin.c, also based |
|---|
| | 10 | * on its Java translation by John Reilly. |
|---|
| | 11 | * |
|---|
| | 12 | * Permission is hereby granted to use the PHP version under the same conditions |
|---|
| | 13 | * as jsmin.c, which has the following notice : |
|---|
| | 14 | * |
|---|
| | 15 | * ---------------------------------------------------------------------------- |
|---|
| | 16 | * |
|---|
| | 17 | * Copyright (c) 2002 Douglas Crockford (www.crockford.com) |
|---|
| | 18 | * |
|---|
| | 19 | * Permission is hereby granted, free of charge, to any person obtaining a copy of |
|---|
| | 20 | * this software and associated documentation files (the "Software"), to deal in |
|---|
| | 21 | * the Software without restriction, including without limitation the rights to |
|---|
| | 22 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|---|
| | 23 | * of the Software, and to permit persons to whom the Software is furnished to do |
|---|
| | 24 | * so, subject to the following conditions: |
|---|
| | 25 | * |
|---|
| | 26 | * The above copyright notice and this permission notice shall be included in all |
|---|
| | 27 | * copies or substantial portions of the Software. |
|---|
| | 28 | * |
|---|
| | 29 | * The Software shall be used for Good, not Evil. |
|---|
| | 30 | * |
|---|
| | 31 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|---|
| | 32 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|---|
| | 33 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|---|
| | 34 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|---|
| | 35 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|---|
| | 36 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|---|
| | 37 | * SOFTWARE. |
|---|
| | 38 | * |
|---|
| | 39 | * ---------------------------------------------------------------------------- |
|---|
| | 40 | * |
|---|
| | 41 | * @copyright No new copyright ; please keep above and following information. |
|---|
| | 42 | * @author David Holmes <dholmes@cfdsoftware.net> of CFD Labs, France |
|---|
| | 43 | * @author Gaetano Giunta |
|---|
| | 44 | * @author Rémi Lanvin (aka cgo2) <cgo2@the-asw.com>, France |
|---|
| | 45 | * @version $Id: $ |
|---|
| | 46 | */ |
|---|
| | 47 | |
|---|
| | 48 | /** |
|---|
| | 49 | * Generic exception class related to JSMin. |
|---|
| 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 = array(); |
|---|
| 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; |
|---|
| 113 | | $this->output[] = $this->b; |
|---|
| 114 | | |
|---|
| 115 | | for (;;) { |
|---|
| 116 | | $this->a = $this->get(); |
|---|
| 117 | | |
|---|
| 118 | | if ($this->a === '/') { |
|---|
| 119 | | break; |
|---|
| 120 | | } |
|---|
| 121 | | elseif ($this->a === '\\') { |
|---|
| 122 | | $this->output[] = $this->a; |
|---|
| 123 | | $this->a = $this->get(); |
|---|
| 124 | | } |
|---|
| 125 | | elseif (ord($this->a) <= self::ORD_LF) { |
|---|
| 126 | | throw new JSMinException('Unterminated regular expression '. |
|---|
| 127 | | 'literal.'); |
|---|
| 128 | | } |
|---|
| 129 | | |
|---|
| 130 | | $this->output[] = $this->a; |
|---|
| 131 | | } |
|---|
| 132 | | |
|---|
| 133 | | $this->b = $this->next(); |
|---|
| 134 | | } |
|---|
| 135 | | } |
|---|
| 136 | | } |
|---|
| 137 | | |
|---|
| 138 | | protected function get() { |
|---|
| 139 | | $c = $this->lookAhead; |
|---|
| 140 | | $this->lookAhead = null; |
|---|
| 141 | | |
|---|
| 142 | | if ($c === null) { |
|---|
| 143 | | if ($this->inputIndex < $this->inputLength) { |
|---|
| 144 | | $c = $this->input[$this->inputIndex]; |
|---|
| 145 | | $this->inputIndex += 1; |
|---|
| 146 | | } |
|---|
| 147 | | else { |
|---|
| 148 | | $c = null; |
|---|
| 149 | | } |
|---|
| 150 | | } |
|---|
| | 51 | class JsMinEnhException extends Exception |
|---|
| | 52 | { |
|---|
| | 53 | public function __construct($msg) |
|---|
| | 54 | { |
|---|
| | 55 | parent :: __construct($msg); |
|---|
| | 56 | } |
|---|
| | 57 | } |
|---|
| | 58 | |
|---|
| | 59 | |
|---|
| | 60 | /** |
|---|
| | 61 | * Main JSMin application class. |
|---|
| | 62 | * |
|---|
| | 63 | * Example of use : |
|---|
| | 64 | * |
|---|
| | 65 | * $code = file_get_contents($file); |
|---|
| | 66 | * $jsMin = new JsMinEnh($code); |
|---|
| | 67 | * $code = $jsMin->minify(); |
|---|
| | 68 | */ |
|---|
| | 69 | class JsMinEnh |
|---|
| | 70 | { |
|---|
| | 71 | /** |
|---|
| | 72 | * How fgetc() reports an End Of File. |
|---|
| | 73 | * N.B. : use === and not == to test the result of fgetc() ! (see manual) |
|---|
| | 74 | */ |
|---|
| | 75 | const EOF = false; |
|---|
| | 76 | |
|---|
| | 77 | /** |
|---|
| | 78 | * Some ASCII character ordinals. |
|---|
| | 79 | */ |
|---|
| | 80 | const ORD_NL = 10; |
|---|
| | 81 | const ORD_space = 32; |
|---|
| | 82 | const ORD_cA = 65; |
|---|
| | 83 | const ORD_cZ = 90; |
|---|
| | 84 | const ORD_a = 97; |
|---|
| | 85 | const ORD_z = 122; |
|---|
| | 86 | const ORD_0 = 48; |
|---|
| | 87 | const ORD_9 = 57; |
|---|
| | 88 | |
|---|
| | 89 | /** |
|---|
| | 90 | * Constant describing an {@link action()} : Output A. Copy B to A. Get the next B. |
|---|
| | 91 | */ |
|---|
| | 92 | const JSMIN_ACT_FULL = 1; |
|---|
| | 93 | |
|---|
| | 94 | /** |
|---|
| | 95 | * Constant describing an {@link action()} : Copy B to A. Get the next B. (Delete A). |
|---|
| | 96 | */ |
|---|
| | 97 | const JSMIN_ACT_BUF = 2; |
|---|
| | 98 | |
|---|
| | 99 | /** |
|---|
| | 100 | * Constant describing an {@link action()} : Get the next B. (Delete B). |
|---|
| | 101 | */ |
|---|
| | 102 | const JSMIN_ACT_IMM = 3; |
|---|
| | 103 | |
|---|
| | 104 | /** |
|---|
| | 105 | * The input stream, from which to read a JS file to minimize. Obtained by fopen(). |
|---|
| | 106 | * NB: might be a string instead of a stream |
|---|
| | 107 | * @var SplFileObject | string |
|---|
| | 108 | */ |
|---|
| | 109 | private $in; |
|---|
| | 110 | |
|---|
| | 111 | /** |
|---|
| | 112 | * The output stream, in which to write the minimized JS file. Obtained by fopen(). |
|---|
| | 113 | * NB: might be a string instead of a stream |
|---|
| | 114 | * @var SplFileObject | string |
|---|
| | 115 | */ |
|---|
| | 116 | private $out; |
|---|
| | 117 | |
|---|
| | 118 | /** |
|---|
| | 119 | * Temporary I/O character (A). |
|---|
| | 120 | * @var string |
|---|
| | 121 | */ |
|---|
| | 122 | private $theA; |
|---|
| | 123 | |
|---|
| | 124 | /** |
|---|
| | 125 | * Temporary I/O character (B). |
|---|
| | 126 | * @var string |
|---|
| | 127 | */ |
|---|
| | 128 | private $theB; |
|---|
| | 129 | |
|---|
| | 130 | /** variables used for string-based parsing **/ |
|---|
| | 131 | private $inLength = 0; |
|---|
| | 132 | private $inPos = 0; |
|---|
| | 133 | |
|---|
| | 134 | /** |
|---|
| | 135 | * Indicates whether a character is alphanumeric or _, $, \ or non-ASCII. |
|---|
| | 136 | * |
|---|
| | 137 | * @param string $c The single character to test. |
|---|
| | 138 | * @return boolean Whether the char is a letter, digit, underscore, dollar, backslash, or non-ASCII. |
|---|
| | 139 | */ |
|---|
| | 140 | private function isAlphaNum($c) { |
|---|
| | 141 | |
|---|
| | 142 | // Get ASCII value of character for C-like comparisons |
|---|
| | 143 | $a = ord($c); |
|---|
| | 144 | |
|---|
| | 145 | // Compare using defined character ordinals, or between PHP strings |
|---|
| | 146 | // Note : === is micro-faster than == when types are known to be the same |
|---|
| | 147 | return |
|---|
| | 148 | ($a >= self::ORD_a && $a <= self::ORD_z) || |
|---|
| | 149 | ($a >= self::ORD_0 && $a <= self::ORD_9) || |
|---|
| | 150 | ($a >= self::ORD_cA && $a <= self::ORD_cZ) || |
|---|
| | 151 | $c === '_' || $c === '$' || $c === '\\' || $a > 126 |
|---|
| | 152 | ; |
|---|
| | 153 | } |
|---|
| | 154 | |
|---|
| | 155 | /** |
|---|
| | 156 | * Get the next character from the input stream. |
|---|
| | 157 | * |
|---|
| | 158 | * If said character is a control character, translate it to a space or linefeed. |
|---|
| | 159 | * |
|---|
| | 160 | * @return string The next character from the specified input stream. |
|---|
| | 161 | * @see $in |
|---|
| | 162 | * @see peek() |
|---|
| | 163 | */ |
|---|
| | 164 | private function get() { |
|---|
| | 165 | |
|---|
| | 166 | // Get next input character and advance position in file |
|---|
| | 167 | if ($this->inPos < $this->inLength) { |
|---|
| | 168 | $c = $this->in[$this->inPos]; |
|---|
| | 169 | ++$this->inPos; |
|---|
| | 170 | } |
|---|
| | 171 | else { |
|---|
| | 172 | return self::EOF; |
|---|
| | 173 | } |
|---|
| | 174 | |
|---|
| | 175 | // Test for non-problematic characters |
|---|
| | 176 | if ($c === "\n" || $c === self::EOF || ord($c) >= self::ORD_space) { |
|---|
| | 177 | return $c; |
|---|
| | 178 | } |
|---|
| | 179 | |
|---|
| | 180 | // else |
|---|
| | 181 | // Make linefeeds into newlines |
|---|
| 163 | | protected function isAlphaNum($c) { |
|---|
| 164 | | return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; |
|---|
| 165 | | } |
|---|
| 166 | | |
|---|
| 167 | | protected function min() { |
|---|
| 168 | | $this->a = "\n"; |
|---|
| 169 | | $this->action(3); |
|---|
| 170 | | |
|---|
| 171 | | while ($this->a !== null) { |
|---|
| 172 | | switch ($this->a) { |
|---|
| 173 | | case ' ': |
|---|
| 174 | | if ($this->isAlphaNum($this->b)) { |
|---|
| 175 | | $this->action(1); |
|---|
| | 193 | private function getCloser($string, $needle, $offset) |
|---|
| | 194 | { |
|---|
| | 195 | $closer = false; |
|---|
| | 196 | foreach ($needle as $n) { |
|---|
| | 197 | $pos = strpos($string, $n, $offset); |
|---|
| | 198 | if ( $pos !== false && ($closer === false || $pos < $closer) ) |
|---|
| | 199 | $closer = $pos; |
|---|
| | 200 | } |
|---|
| | 201 | return $closer; |
|---|
| | 202 | } |
|---|
| | 203 | |
|---|
| | 204 | /** |
|---|
| | 205 | * Get the next character from the input stream, without gettng it. |
|---|
| | 206 | * |
|---|
| | 207 | * @return string The next character from the specified input stream, without advancing the position |
|---|
| | 208 | * in the underlying file. |
|---|
| | 209 | * @see $in |
|---|
| | 210 | * @see get() |
|---|
| | 211 | */ |
|---|
| | 212 | private function peek() |
|---|
| | 213 | { |
|---|
| | 214 | return ($this->inPos < $this->inLength) ? $this->in[$this->inPos] : self::EOF; |
|---|
| | 215 | } |
|---|
| | 216 | |
|---|
| | 217 | /** |
|---|
| | 218 | * Adds a char to the output string |
|---|
| | 219 | * @see $out |
|---|
| | 220 | */ |
|---|
| | 221 | function put($c) |
|---|
| | 222 | { |
|---|
| | 223 | $this->out .= $c; |
|---|
| | 224 | } |
|---|
| | 225 | |
|---|
| | 226 | /** |
|---|
| | 227 | * Get the next character from the input stream, excluding comments. |
|---|
| | 228 | * |
|---|
| | 229 | * {@link peek()} is used to see if a '/' is followed by a '*' or '/'. |
|---|
| | 230 | * Multiline comments are actually returned as a single space. |
|---|
| | 231 | * |
|---|
| | 232 | * @return string The next character from the specified input stream, skipping comments. |
|---|
| | 233 | * @see $in |
|---|
| | 234 | */ |
|---|
| | 235 | function next() { |
|---|
| | 236 | |
|---|
| | 237 | // Get next char from input, translated if necessary |
|---|
| | 238 | |
|---|
| | 239 | $c = $this->get(); |
|---|
| | 240 | |
|---|
| | 241 | // Check comment possibility |
|---|
| | 242 | |
|---|
| | 243 | if ($c == '/') { |
|---|
| | 244 | |
|---|
| | 245 | // Look ahead : a comment is two slashes or slashes followed by asterisk (to be closed) |
|---|
| | 246 | |
|---|
| | 247 | switch ($this->peek()) { |
|---|
| | 248 | |
|---|
| | 249 | case '/' : |
|---|
| | 250 | |
|---|
| | 251 | // Comment is up to the end of the line |
|---|
| | 252 | $this->inPos = strpos($this->in, "\n", $this->inPos); |
|---|
| | 253 | return $this->in[$this->inPos]; |
|---|
| | 254 | |
|---|
| | 255 | case '*' : |
|---|
| | 256 | |
|---|
| | 257 | // Comment is up to comment close. |
|---|
| | 258 | // Might not be terminated, if we hit the end of file. |
|---|
| | 259 | |
|---|
| | 260 | $this->inPos = strpos($this->in, "*/", $this->inPos); |
|---|
| | 261 | if ( $this->inPos === false ) { |
|---|
| | 262 | throw new JsMinEnhException('UnterminatedComment'); |
|---|
| | 263 | } |
|---|
| | 264 | $this->inPos += 2; |
|---|
| | 265 | return ' '; |
|---|
| | 266 | default : |
|---|
| | 267 | |
|---|
| | 268 | // Not a comment after all |
|---|
| | 269 | |
|---|
| | 270 | return $c; |
|---|
| | 271 | } |
|---|
| | 272 | } |
|---|
| | 273 | |
|---|
| | 274 | // No risk of a comment |
|---|
| | 275 | |
|---|
| | 276 | return $c; |
|---|
| | 277 | } |
|---|
| | 278 | |
|---|
| | 279 | /** |
|---|
| | 280 | * Do something ! |
|---|
| | 281 | * |
|---|
| | 282 | * The action to perform is determined by the argument : |
|---|
| | 283 | * |
|---|
| | 284 | * JSMin::ACT_FULL : Output A. Copy B to A. Get the next B. |
|---|
| | 285 | * JSMin::ACT_BUF : Copy B to A. Get the next B. (Delete A). |
|---|
| | 286 | * JSMin::ACT_IMM : Get the next B. (Delete B). |
|---|
| | 287 | * |
|---|
| | 288 | * A string is treated as a single character. Also, regular expressions are recognized if preceded |
|---|
| | 289 | * by '(', ',' or '='. |
|---|
| | 290 | * |
|---|
| | 291 | * @param int $action The action to perform : one of the JSMin::ACT_* constants. |
|---|
| | 292 | */ |
|---|
| | 293 | function action($action) { |
|---|
| | 294 | |
|---|
| | 295 | // Choice of possible actions |
|---|
| | 296 | // Note the frequent fallthroughs : the actions are decrementally "long" |
|---|
| | 297 | switch ($action) { |
|---|
| | 298 | |
|---|
| | 299 | case self::JSMIN_ACT_FULL : |
|---|
| | 300 | // Write A to output, then fall through |
|---|
| | 301 | |
|---|
| | 302 | $this->put($this->theA); |
|---|
| | 303 | |
|---|
| | 304 | case self::JSMIN_ACT_BUF : // N.B. possible fallthrough from above |
|---|
| | 305 | // Copy B to A |
|---|
| | 306 | |
|---|
| | 307 | $tmpA = $this->theA = $this->theB; |
|---|
| | 308 | |
|---|
| | 309 | // Treating a string as a single char : outputting it whole |
|---|
| | 310 | // Note that the string-opening char (" or ') is memorized in B |
|---|
| | 311 | |
|---|
| | 312 | if ($tmpA == '\'' || $tmpA == '"') { |
|---|
| | 313 | |
|---|
| | 314 | $pos = $this->inPos; |
|---|
| | 315 | while (true) { |
|---|
| | 316 | // instead of looping char by char, we directly go to the next |
|---|
| | 317 | // revelant char, thanks to php strpos function. |
|---|
| | 318 | $pos = $this->getCloser($this->in, array($this->theB,'\\',"\n"), $pos); |
|---|
| | 319 | |
|---|
| | 320 | if ( $pos === false ) { |
|---|
| | 321 | // Whoopsie |
|---|
| | 322 | throw new JsMinEnhException('UnterminatedStringLiteral'); |
|---|
| | 323 | } |
|---|
| | 324 | |
|---|
| | 325 | $tmpA = $this->in[$pos]; |
|---|
| | 326 | |
|---|
| | 327 | if ($tmpA == $this->theB) { |
|---|
| | 328 | // String terminated |
|---|
| | 329 | break; // from while(true) |
|---|
| | 330 | } |
|---|
| | 331 | if ($tmpA == "\n") { |
|---|
| | 332 | // Whoopsie |
|---|
| | 333 | throw new JsMinEnhException('UnterminatedStringLiteral'); |
|---|
| | 334 | } |
|---|
| | 335 | // else |
|---|
| | 336 | if ($tmpA == '\\') { |
|---|
| | 337 | // Escape next char immediately |
|---|
| | 338 | $pos += 2; |
|---|
| | 339 | } |
|---|
| | 340 | } |
|---|
| | 341 | |
|---|
| | 342 | // cool, we got the whole string |
|---|
| | 343 | $this->put(substr($this->in, $this->inPos - 1, $pos - $this->inPos + 1)); |
|---|
| | 344 | $this->inPos = $pos + 1; |
|---|
| | 345 | $this->theA = $tmpA; |
|---|
| | 346 | } |
|---|
| | 347 | |
|---|
| | 348 | case self::JSMIN_ACT_IMM : // N.B. possible fallthrough from above |
|---|
| | 349 | // Get the next B |
|---|
| | 350 | |
|---|
| | 351 | $this->theB = $this->next(); |
|---|
| | 352 | |
|---|
| | 353 | // Special case of recognising regular expressions (beginning with /) that are |
|---|
| | 354 | // preceded by '(', ',' or '=' |
|---|
| | 355 | |
|---|
| | 356 | $tmpA = $this->theA; |
|---|
| | 357 | |
|---|
| | 358 | if ($this->theB == '/' && ($tmpA == '(' || $tmpA == ',' || $tmpA == '=')) { |
|---|
| | 359 | |
|---|
| | 360 | // Output the two successive chars |
|---|
| | 361 | $this->put($tmpA); |
|---|
| | 362 | $this->put($this->theB); |
|---|
| | 363 | |
|---|
| | 364 | // Look for the end of the RE literal, watching out for escaped chars or a control / |
|---|
| | 365 | // end of line char (the RE literal then being unterminated !) |
|---|
| | 366 | $pos = $this->inPos; |
|---|
| | 367 | while (true) { |
|---|
| | 368 | // instead of looping char by char, we directly go to the next |
|---|
| | 369 | // revelant char, thanks to php strpos function. |
|---|
| | 370 | $pos = $this->getCloser($this->in, array('/','\\',"\n"), $pos); |
|---|
| | 371 | |
|---|
| | 372 | if ( $pos === false ) { |
|---|
| | 373 | // Whoopsie |
|---|
| | 374 | throw new JsMinEnhException('UnterminatedRegExpLiteral'); |
|---|
| | 375 | } |
|---|
| | 376 | |
|---|
| | 377 | $tmpA = $this->in[$pos]; |
|---|
| | 378 | |
|---|
| | 379 | if ($tmpA == '/') { |
|---|
| | 380 | // RE literal terminated |
|---|
| | 381 | break; // from while(true) |
|---|
| | 382 | } |
|---|
| | 383 | if ( $tmpA == "\n") { |
|---|
| | 384 | // Whoopsie |
|---|
| | 385 | throw new JsMinEnhException('UnterminatedRegExpLiteral'); |
|---|
| | 386 | } |
|---|
| | 387 | // else |
|---|
| | 388 | if ($tmpA == '\\') { |
|---|
| | 389 | // Escape next char immediately |
|---|
| | 390 | $pos += 2; |
|---|
| | 391 | } |
|---|
| | 392 | } |
|---|
| | 393 | $this->put(substr($this->in, $this->inPos, $pos - $this->inPos)); |
|---|
| | 394 | $this->inPos = $pos + 1; |
|---|
| | 395 | $this->theA = $tmpA; |
|---|
| | 396 | |
|---|
| | 397 | |
|---|
| | 398 | // Move forward after the RE literal |
|---|
| | 399 | $this->theB = $this->next(); |
|---|
| | 400 | } |
|---|
| | 401 | |
|---|
| | 402 | break; |
|---|
| | 403 | default : |
|---|
| | 404 | throw new JsMinEnhException('Expected a JSMin::ACT_* constant in action()'); |
|---|
| | 405 | } |
|---|
| | 406 | } |
|---|
| | 407 | |
|---|
| | 408 | /** |
|---|
| | 409 | * Run the JSMin application : minify some JS code. |
|---|
| | 410 | * |
|---|
| | 411 | * The code is read from the input stream, and its minified version is written to the output one. |
|---|
| | 412 | * In case input is a string, minified vesrions is also returned by this function as string. |
|---|
| | 413 | * That is : characters which are insignificant to JavaScript are removed, as well as comments ; |
|---|
| | 414 | * tabs are replaced with spaces ; carriage returns are replaced with linefeeds, and finally most |
|---|
| | 415 | * spaces and linefeeds are deleted. |
|---|
| | 416 | * |
|---|
| | 417 | * Note : name was changed from jsmin() because PHP identifiers are case-insensitive, and it is already |
|---|
| | 418 | * the name of this class. |
|---|
| | 419 | * |
|---|
| | 420 | * @see JSMin() |
|---|
| | 421 | * @return null | string |
|---|
| | 422 | */ |
|---|
| | 423 | function minify() { |
|---|
| | 424 | |
|---|
| | 425 | // Initialize A and run the first (minimal) action |
|---|
| | 426 | |
|---|
| | 427 | $this->theA = "\n"; |
|---|
| | 428 | $this->action(self::JSMIN_ACT_IMM); |
|---|
| | 429 | |
|---|
| | 430 | // Proceed all the way to the end of the input file |
|---|
| | 431 | |
|---|
| | 432 | while ($this->theA !== self::EOF) { |
|---|
| | 433 | switch ($this->theA) { |
|---|
| | 434 | case ' ' : |
|---|
| | 435 | |
|---|
| | 436 | if ($this->isAlphaNum($this->theB)) { |
|---|
| | 437 | $this->action(self::JSMIN_ACT_FULL); |
|---|