ILIAS  Release_4_1_x_branch Revision 61804
 All Data Structures Namespaces Files Functions Variables Groups Pages
JSMin_lib.php
Go to the documentation of this file.
1 <?php
85 define('JSMIN_VERSION', '0.2');
86 
92 define('EOF', FALSE);
93 
99 define('ORD_NL', ord("\n"));
100 define('ORD_space', ord(' '));
101 define('ORD_cA', ord('A'));
102 define('ORD_cZ', ord('Z'));
103 define('ORD_a', ord('a'));
104 define('ORD_z', ord('z'));
105 define('ORD_0', ord('0'));
106 define('ORD_9', ord('9'));
107 
111 /*
112 class JSMinException extends Exception {
113 }
114 */
116 }
117 
123 }
124 
130 }
131 
137 }
138 
144 }
145 
150 define ('JSMIN_ACT_FULL', 1);
151 
156 define ('JSMIN_ACT_BUF', 2);
157 
162 define ('JSMIN_ACT_IMM', 3);
163 
175 class JSMin {
176 
182  var $in;
183 
189  var $out;
190 
195  var $theA;
196 
201  var $theB;
202 
204  var $inLength = 0;
205  var $inPos = 0;
206  var $isString = false;
207 
214  function isAlphaNum($c) {
215 
216  // Get ASCII value of character for C-like comparisons
217 
218  $a = ord($c);
219 
220  // Compare using defined character ordinals, or between PHP strings
221  // Note : === is micro-faster than == when types are known to be the same
222 
223  return
224  ($a >= ORD_a && $a <= ORD_z) ||
225  ($a >= ORD_0 && $a <= ORD_9) ||
226  ($a >= ORD_cA && $a <= ORD_cZ) ||
227  $c === '_' || $c === '$' || $c === '\\' || $a > 126
228  ;
229  }
230 
240  function get() {
241 
242  // Get next input character and advance position in file
243 
244  if ($this->isString) {
245  if ($this->inPos < $this->inLength) {
246  $c = $this->in[$this->inPos];
247  ++$this->inPos;
248  }
249  else {
250  return EOF;
251  }
252  }
253  else
254  $c = $this->in->fgetc();
255 
256  // Test for non-problematic characters
257 
258  if ($c === "\n" || $c === EOF || ord($c) >= ORD_space) {
259  return $c;
260  }
261 
262  // else
263  // Make linefeeds into newlines
264 
265  if ($c === "\r") {
266  return "\n";
267  }
268 
269  // else
270  // Consider space
271 
272  return ' ';
273  }
274 
283  function peek() {
284 
285  if ($this->isString) {
286  if ($this->inPos < $this->inLength) {
287  $c = $this->in[$this->inPos];
288  }
289  else {
290  return EOF;
291  }
292  }
293  else {
294  // Get next input character
295 
296  $c = $this->in->fgetc();
297 
298  // Regress position in file
299 
300  $this->in->fseek(-1, SEEK_CUR);
301 
302  // Return character obtained
303  }
304 
305  return $c;
306  }
307 
312  function put($c)
313  {
314  if ($this->isString) {
315  $this->out .= $c;
316  }
317  else {
318  $this->out->fwrite($c);
319  }
320  }
321 
331  function next() {
332 
333  // Get next char from input, translated if necessary
334 
335  $c = $this->get();
336 
337  // Check comment possibility
338 
339  if ($c == '/') {
340 
341  // Look ahead : a comment is two slashes or slashes followed by asterisk (to be closed)
342 
343  switch ($this->peek()) {
344 
345  case '/' :
346 
347  // Comment is up to the end of the line
348  // TOTEST : simple $this->in->fgets()
349 
350  while (true) {
351 
352  $c = $this->get();
353 
354  if (ord($c) <= ORD_NL) {
355  return $c;
356  }
357  }
358 
359  case '*' :
360 
361  // Comment is up to comment close.
362  // Might not be terminated, if we hit the end of file.
363 
364  while (true) {
365 
366  // N.B. not using switch() because of having to test EOF with ===
367 
368  $c = $this->get();
369 
370  if ($c == '*') {
371 
372  // Comment termination if the char ahead is a slash
373 
374  if ($this->peek() == '/') {
375 
376  // Advance again and make into a single space
377 
378  $this->get();
379  return ' ';
380  }
381  }
382  else if ($c === EOF) {
383 
384  // Whoopsie
385 
386  //throw new UnterminatedCommentJSMinException();
387  trigger_error('UnterminatedComment', E_USER_ERROR);
388  }
389  }
390 
391  default :
392 
393  // Not a comment after all
394 
395  return $c;
396  }
397  }
398 
399  // No risk of a comment
400 
401  return $c;
402  }
403 
418  function action($action) {
419 
420  // Choice of possible actions
421  // Note the frequent fallthroughs : the actions are decrementally "long"
422 
423  switch ($action) {
424 
425  case JSMIN_ACT_FULL :
426 
427  // Write A to output, then fall through
428 
429  $this->put($this->theA);
430 
431  case JSMIN_ACT_BUF : // N.B. possible fallthrough from above
432 
433  // Copy B to A
434 
435  $tmpA = $this->theA = $this->theB;
436 
437  // Treating a string as a single char : outputting it whole
438  // Note that the string-opening char (" or ') is memorized in B
439 
440  if ($tmpA == '\'' || $tmpA == '"') {
441 
442  while (true) {
443 
444  // Output string contents
445 
446  $this->put($tmpA);
447 
448  // Get next character, watching out for termination of the current string,
449  // new line & co (then the string is not terminated !), or a backslash
450  // (upon which the following char is directly output to serve the escape mechanism)
451 
452  $tmpA = $this->theA = $this->get();
453 
454  if ($tmpA == $this->theB) {
455 
456  // String terminated
457 
458  break; // from while(true)
459  }
460 
461  // else
462 
463  if (ord($tmpA) <= ORD_NL) {
464 
465  // Whoopsie
466 
467  //throw new UnterminatedStringLiteralJSMinException();
468  trigger_error('UnterminatedStringLiteral', E_USER_ERROR);
469  }
470 
471  // else
472 
473  if ($tmpA == '\\') {
474 
475  // Escape next char immediately
476 
477  $this->put($tmpA);
478  $tmpA = $this->theA = $this->get();
479  }
480  }
481  }
482 
483  case JSMIN_ACT_IMM : // N.B. possible fallthrough from above
484 
485  // Get the next B
486 
487  $this->theB = $this->next();
488 
489  // Special case of recognising regular expressions (beginning with /) that are
490  // preceded by '(', ',' or '='
491 
492  $tmpA = $this->theA;
493 
494  if ($this->theB == '/' && ($tmpA == '(' || $tmpA == ',' || $tmpA == '=')) {
495 
496  // Output the two successive chars
497 
498  $this->put($tmpA);
499  $this->put($this->theB);
500 
501  // Look for the end of the RE literal, watching out for escaped chars or a control /
502  // end of line char (the RE literal then being unterminated !)
503 
504  while (true) {
505 
506  $tmpA = $this->theA = $this->get();
507 
508  if ($tmpA == '/') {
509 
510  // RE literal terminated
511 
512  break; // from while(true)
513  }
514 
515  // else
516 
517  if ($tmpA == '\\') {
518 
519  // Escape next char immediately
520 
521  $this->put($tmpA);
522  $tmpA = $this->theA = $this->get();
523  }
524  else if (ord($tmpA) <= ORD_NL) {
525 
526  // Whoopsie
527 
528  //throw new UnterminatedRegExpLiteralJSMinException();
529  trigger_error('UnterminatedRegExpLiteral', E_USER_ERROR);
530  }
531 
532  // Output RE characters
533 
534  $this->put($tmpA);
535  }
536 
537  // Move forward after the RE literal
538 
539  $this->theB = $this->next();
540  }
541 
542  break;
543  default :
544  //throw new JSMinException('Expected a JSMin::ACT_* constant in action().');
545  trigger_error('Expected a JSMin::ACT_* constant in action()', E_USER_ERROR);
546  }
547  }
548 
564  function minify() {
565 
566  // Initialize A and run the first (minimal) action
567 
568  $this->theA = "\n";
569  $this->action(JSMIN_ACT_IMM);
570 
571  // Proceed all the way to the end of the input file
572 
573  while ($this->theA !== EOF) {
574 
575  switch ($this->theA) {
576 
577  case ' ' :
578 
579  if (JSMin::isAlphaNum($this->theB)) {
580  $this->action(JSMIN_ACT_FULL);
581  }
582  else {
583  $this->action(JSMIN_ACT_BUF);
584  }
585 
586  break;
587  case "\n" :
588 
589  switch ($this->theB) {
590 
591  case '{' : case '[' : case '(' :
592  case '+' : case '-' :
593 
594  $this->action(JSMIN_ACT_FULL);
595 
596  break;
597  case ' ' :
598 
599  $this->action(JSMIN_ACT_IMM);
600 
601  break;
602  default :
603 
604  if (JSMin::isAlphaNum($this->theB)) {
605  $this->action(JSMIN_ACT_FULL);
606  }
607  else {
608  $this->action(JSMIN_ACT_BUF);
609  }
610 
611  break;
612  }
613 
614  break;
615  default :
616 
617  switch ($this->theB) {
618 
619  case ' ' :
620 
621  if (JSMin::isAlphaNum($this->theA)) {
622 
623  $this->action(JSMIN_ACT_FULL);
624  break;
625  }
626 
627  // else
628 
629  $this->action(JSMIN_ACT_IMM);
630 
631  break;
632  case "\n" :
633 
634  switch ($this->theA) {
635 
636  case '}' : case ']' : case ')' : case '+' :
637  case '-' : case '"' : case '\'' :
638 
639  $this->action(JSMIN_ACT_FULL);
640 
641  break;
642  default :
643 
644  if (JSMin::isAlphaNum($this->theA)) {
645  $this->action(JSMIN_ACT_FULL);
646  }
647  else {
648  $this->action(JSMIN_ACT_IMM);
649  }
650 
651  break;
652  }
653 
654  break;
655  default :
656 
657  $this->action(JSMIN_ACT_FULL);
658 
659  break;
660  }
661 
662  break;
663  }
664  }
665 
666  if ($this->isString) {
667  return $this->out;
668 
669  }
670  }
671 
682  function JSMin($inFileName = '-', $outFileName = '-', $comments = NULL) {
683  if ($outFileName === FALSE) {
684  $this->JSMin_String($inFileName, $comments);
685  }
686  else {
687  $this->JSMin_File($inFileName, $outFileName, $comments);
688  }
689  }
690 
691  function JSMin_File($inFileName = '-', $outFileName = '-', $comments = NULL) {
692 
693  // Recuperate input and output streams.
694  // Use STDIN and STDOUT by default, if they are defined (CLI mode) and no file names are provided
695 
696  if ($inFileName == '-') $inFileName = 'php://stdin';
697  if ($outFileName == '-') $outFileName = 'php://stdout';
698 
699  /*try {
700 
701  $this->in = new SplFileObject($inFileName, 'rb', TRUE);
702  }
703  catch (Exception $e) {
704 
705  throw new FileOpenFailedJSMinException(
706  'Failed to open "'.$inFileName.'" for reading only.'
707  );
708  }
709 
710  try {
711 
712  $this->out = new SplFileObject($outFileName, 'wb', TRUE);
713  }
714  catch (Exception $e) {
715 
716  throw new FileOpenFailedJSMinException(
717  'Failed to open "'.$outFileName.'" for writing only.'
718  );
719  }
720  */
721  $this->in = fopen($inFileName, 'rb');
722  if (!$this->in) {
723  trigger_error('Failed to open "'.$inFileName, E_USER_ERROR);
724  }
725  $this->out = fopen($outFileName, 'wb');
726  if (!$this->out) {
727  trigger_error('Failed to open "'.$outFileName, E_USER_ERROR);
728  }
729 
730  // Present possible initial comments
731 
732  if ($comments !== NULL) {
733  foreach ($comments as $comm) {
734  $this->out->fwrite('// '.str_replace("\n", " ", $comm)."\n");
735  }
736  }
737 
738  }
739 
740  function JSMin_String($inString, $comments = NULL) {
741  $this->in = $inString;
742  $this->out = '';
743  $this->inLength = strlen($inString);
744  $this->inPos = 0;
745  $this->isString = true;
746 
747  if ($comments !== NULL) {
748  foreach ($comments as $comm) {
749  $this->out .= '// '.str_replace("\n", " ", $comm)."\n";
750  }
751  }
752  }
753 }
754 
760 ?>