PageRenderTime 23ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/Minify/drivers/Minify_js.php

https://github.com/k1ng440/CodeIgniter-Minify
PHP | 414 lines | 237 code | 42 blank | 135 comment | 31 complexity | 222aec9d9bfc6de5561465255364903a MD5 | raw file
  1. <?php
  2. /**
  3. * @name CodeIgniter Minify
  4. * @author Jens Segers
  5. * @link http://www.jenssegers.be
  6. * @license MIT License Copyright (c) 2012 Jens Segers
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to deal
  10. * in the Software without restriction, including without limitation the rights
  11. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. */
  26. if (!defined("BASEPATH"))
  27. exit("No direct script access allowed");
  28. class Minify_js extends Minify_Driver {
  29. /* (non-PHPdoc)
  30. * @see Minify_Driver::minify()
  31. */
  32. public function min($source) {
  33. return trim(JSMin::minify($source));
  34. }
  35. }
  36. /**
  37. * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
  38. *
  39. * This is pretty much a direct port of jsmin.c to PHP with just a few
  40. * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
  41. * outputs to stdout, this library accepts a string as input and returns another
  42. * string as output.
  43. *
  44. * PHP 5 or higher is required.
  45. *
  46. * Permission is hereby granted to use this version of the library under the
  47. * same terms as jsmin.c, which has the following license:
  48. *
  49. * --
  50. * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
  51. *
  52. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  53. * this software and associated documentation files (the "Software"), to deal in
  54. * the Software without restriction, including without limitation the rights to
  55. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  56. * of the Software, and to permit persons to whom the Software is furnished to do
  57. * so, subject to the following conditions:
  58. *
  59. * The above copyright notice and this permission notice shall be included in all
  60. * copies or substantial portions of the Software.
  61. *
  62. * The Software shall be used for Good, not Evil.
  63. *
  64. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  65. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  66. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  67. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  68. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  69. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  70. * SOFTWARE.
  71. * --
  72. *
  73. * @package JSMin
  74. * @author Ryan Grove <ryan@wonko.com>
  75. * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
  76. * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
  77. * @license http://opensource.org/licenses/mit-license.php MIT License
  78. * @version 1.1.1 (2008-03-02)
  79. * @link https://github.com/rgrove/jsmin-php/
  80. */
  81. class JSMin {
  82. const ORD_LF = 10;
  83. const ORD_SPACE = 32;
  84. const ACTION_KEEP_A = 1;
  85. const ACTION_DELETE_A = 2;
  86. const ACTION_DELETE_A_B = 3;
  87. protected $a = '';
  88. protected $b = '';
  89. protected $input = '';
  90. protected $inputIndex = 0;
  91. protected $inputLength = 0;
  92. protected $lookAhead = null;
  93. protected $output = '';
  94. // -- Public Static Methods --------------------------------------------------
  95. /**
  96. * Minify Javascript
  97. *
  98. * @uses __construct()
  99. * @uses min()
  100. * @param string $js Javascript to be minified
  101. * @return string
  102. */
  103. public static function minify($js) {
  104. $jsmin = new JSMin($js);
  105. return $jsmin->min();
  106. }
  107. // -- Public Instance Methods ------------------------------------------------
  108. /**
  109. * Constructor
  110. *
  111. * @param string $input Javascript to be minified
  112. */
  113. public function __construct($input) {
  114. $this->input = str_replace("\r\n", "\n", $input);
  115. $this->inputLength = strlen($this->input);
  116. }
  117. // -- Protected Instance Methods ---------------------------------------------
  118. /**
  119. * Action -- do something! What to do is determined by the $command argument.
  120. *
  121. * action treats a string as a single character. Wow!
  122. * action recognizes a regular expression if it is preceded by ( or , or =.
  123. *
  124. * @uses next()
  125. * @uses get()
  126. * @throws JSMinException If parser errors are found:
  127. * - Unterminated string literal
  128. * - Unterminated regular expression set in regex literal
  129. * - Unterminated regular expression literal
  130. * @param int $command One of class constants:
  131. * ACTION_KEEP_A Output A. Copy B to A. Get the next B.
  132. * ACTION_DELETE_A Copy B to A. Get the next B. (Delete A).
  133. * ACTION_DELETE_A_B Get the next B. (Delete B).
  134. */
  135. protected function action($command) {
  136. switch($command) {
  137. case self::ACTION_KEEP_A:
  138. $this->output .= $this->a;
  139. case self::ACTION_DELETE_A:
  140. $this->a = $this->b;
  141. if ($this->a === "'" || $this->a === '"') {
  142. for (;;) {
  143. $this->output .= $this->a;
  144. $this->a = $this->get();
  145. if ($this->a === $this->b) {
  146. break;
  147. }
  148. if (ord($this->a) <= self::ORD_LF) {
  149. throw new JSMinException('Unterminated string literal.');
  150. }
  151. if ($this->a === '\\') {
  152. $this->output .= $this->a;
  153. $this->a = $this->get();
  154. }
  155. }
  156. }
  157. case self::ACTION_DELETE_A_B:
  158. $this->b = $this->next();
  159. if ($this->b === '/' && (
  160. $this->a === '(' || $this->a === ',' || $this->a === '=' ||
  161. $this->a === ':' || $this->a === '[' || $this->a === '!' ||
  162. $this->a === '&' || $this->a === '|' || $this->a === '?' ||
  163. $this->a === '{' || $this->a === '}' || $this->a === ';' ||
  164. $this->a === "\n" )) {
  165. $this->output .= $this->a . $this->b;
  166. for (;;) {
  167. $this->a = $this->get();
  168. if ($this->a === '[') {
  169. /*
  170. inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
  171. 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'));
  172. */
  173. for (;;) {
  174. $this->output .= $this->a;
  175. $this->a = $this->get();
  176. if ($this->a === ']') {
  177. break;
  178. } elseif ($this->a === '\\') {
  179. $this->output .= $this->a;
  180. $this->a = $this->get();
  181. } elseif (ord($this->a) <= self::ORD_LF) {
  182. throw new JSMinException('Unterminated regular expression set in regex literal.');
  183. }
  184. }
  185. } elseif ($this->a === '/') {
  186. break;
  187. } elseif ($this->a === '\\') {
  188. $this->output .= $this->a;
  189. $this->a = $this->get();
  190. } elseif (ord($this->a) <= self::ORD_LF) {
  191. throw new JSMinException('Unterminated regular expression literal.');
  192. }
  193. $this->output .= $this->a;
  194. }
  195. $this->b = $this->next();
  196. }
  197. }
  198. }
  199. /**
  200. * Get next char. Convert ctrl char to space.
  201. *
  202. * @return string|null
  203. */
  204. protected function get() {
  205. $c = $this->lookAhead;
  206. $this->lookAhead = null;
  207. if ($c === null) {
  208. if ($this->inputIndex < $this->inputLength) {
  209. $c = substr($this->input, $this->inputIndex, 1);
  210. $this->inputIndex += 1;
  211. } else {
  212. $c = null;
  213. }
  214. }
  215. if ($c === "\r") {
  216. return "\n";
  217. }
  218. if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
  219. return $c;
  220. }
  221. return ' ';
  222. }
  223. /**
  224. * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
  225. *
  226. * @return bool
  227. */
  228. protected function isAlphaNum($c) {
  229. return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
  230. }
  231. /**
  232. * Perform minification, return result
  233. *
  234. * @uses action()
  235. * @uses isAlphaNum()
  236. * @return string
  237. */
  238. protected function min() {
  239. $this->a = "\n";
  240. $this->action(self::ACTION_DELETE_A_B);
  241. while ($this->a !== null) {
  242. switch ($this->a) {
  243. case ' ':
  244. if ($this->isAlphaNum($this->b)) {
  245. $this->action(self::ACTION_KEEP_A);
  246. } else {
  247. $this->action(self::ACTION_DELETE_A);
  248. }
  249. break;
  250. case "\n":
  251. switch ($this->b) {
  252. case '{':
  253. case '[':
  254. case '(':
  255. case '+':
  256. case '-':
  257. $this->action(self::ACTION_KEEP_A);
  258. break;
  259. case ' ':
  260. $this->action(self::ACTION_DELETE_A_B);
  261. break;
  262. default:
  263. if ($this->isAlphaNum($this->b)) {
  264. $this->action(self::ACTION_KEEP_A);
  265. }
  266. else {
  267. $this->action(self::ACTION_DELETE_A);
  268. }
  269. }
  270. break;
  271. default:
  272. switch ($this->b) {
  273. case ' ':
  274. if ($this->isAlphaNum($this->a)) {
  275. $this->action(self::ACTION_KEEP_A);
  276. break;
  277. }
  278. $this->action(self::ACTION_DELETE_A_B);
  279. break;
  280. case "\n":
  281. switch ($this->a) {
  282. case '}':
  283. case ']':
  284. case ')':
  285. case '+':
  286. case '-':
  287. case '"':
  288. case "'":
  289. $this->action(self::ACTION_KEEP_A);
  290. break;
  291. default:
  292. if ($this->isAlphaNum($this->a)) {
  293. $this->action(self::ACTION_KEEP_A);
  294. }
  295. else {
  296. $this->action(self::ACTION_DELETE_A_B);
  297. }
  298. }
  299. break;
  300. default:
  301. $this->action(self::ACTION_KEEP_A);
  302. break;
  303. }
  304. }
  305. }
  306. return $this->output;
  307. }
  308. /**
  309. * Get the next character, skipping over comments. peek() is used to see
  310. * if a '/' is followed by a '/' or '*'.
  311. *
  312. * @uses get()
  313. * @uses peek()
  314. * @throws JSMinException On unterminated comment.
  315. * @return string
  316. */
  317. protected function next() {
  318. $c = $this->get();
  319. if ($c === '/') {
  320. switch($this->peek()) {
  321. case '/':
  322. for (;;) {
  323. $c = $this->get();
  324. if (ord($c) <= self::ORD_LF) {
  325. return $c;
  326. }
  327. }
  328. case '*':
  329. $this->get();
  330. for (;;) {
  331. switch($this->get()) {
  332. case '*':
  333. if ($this->peek() === '/') {
  334. $this->get();
  335. return ' ';
  336. }
  337. break;
  338. case null:
  339. throw new JSMinException('Unterminated comment.');
  340. }
  341. }
  342. default:
  343. return $c;
  344. }
  345. }
  346. return $c;
  347. }
  348. /**
  349. * Get next char. If is ctrl character, translate to a space or newline.
  350. *
  351. * @uses get()
  352. * @return string|null
  353. */
  354. protected function peek() {
  355. $this->lookAhead = $this->get();
  356. return $this->lookAhead;
  357. }
  358. }
  359. // -- Exceptions ---------------------------------------------------------------
  360. class JSMinException extends Exception {}
  361. ?>