You are here

protected function JSqueeze::extractStrings in Advanced CSS/JS Aggregation 8.4

Same name and namespace in other branches
  1. 8.2 advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze::extractStrings()
  2. 8.3 advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze::extractStrings()
  3. 7.2 advagg_js_compress/jsqueeze.inc \Patchwork\JSqueeze::extractStrings()
1 call to JSqueeze::extractStrings()
JSqueeze::squeeze in advagg_js_minify/jsqueeze.inc
Squeezes a JavaScript source code.

File

advagg_js_minify/jsqueeze.inc, line 201

Class

JSqueeze

Namespace

Patchwork

Code

protected function extractStrings($f) {
  if ($cc_on = false !== strpos($f, '@cc_on')) {

    // Protect conditional comments from being removed
    $f = str_replace('#', '##', $f);
    $f = str_replace('/*@', '1#@', $f);
    $f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
    $f = str_replace('@*/', '@#1', $f);
  }
  $len = strlen($f);
  $code = str_repeat(' ', $len);
  $j = 0;
  $strings = array();
  $K = 0;
  $instr = false;
  $q = array(
    "'",
    '"',
    "'" => 0,
    '"' => 0,
  );

  // Extract strings, removes comments
  for ($i = 0; $i < $len; ++$i) {
    if ($instr) {
      if ('//' == $instr) {
        if ("\n" == $f[$i]) {
          $f[$i--] = ' ';
          $instr = false;
        }
      }
      else {
        if ($f[$i] == $instr || '/' == $f[$i] && "/'" == $instr) {
          if ('!' == $instr) {
          }
          else {
            if ('*' == $instr) {
              if ('/' == $f[$i + 1]) {
                ++$i;
                $instr = false;
              }
            }
            else {
              if ("/'" == $instr) {
                while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
                  $s[] = $f[$i++];
                }
                $s[] = $f[$i];
              }
              $instr = false;
            }
          }
        }
        else {
          if ('*' == $instr) {
          }
          else {
            if ('!' == $instr) {
              if ('*' == $f[$i] && '/' == $f[$i + 1]) {
                $s[] = "*/\r";
                ++$i;
                $instr = false;
              }
              else {
                if ("\n" == $f[$i]) {
                  $s[] = "\r";
                }
                else {
                  $s[] = $f[$i];
                }
              }
            }
            else {
              if ('\\' == $f[$i]) {
                ++$i;
                if ("\n" != $f[$i]) {
                  isset($q[$f[$i]]) && ++$q[$f[$i]];
                  $s[] = '\\' . $f[$i];
                }
              }
              else {
                if ('[' == $f[$i] && "/'" == $instr) {
                  $instr = '/[';
                  $s[] = '[';
                }
                else {
                  if (']' == $f[$i] && '/[' == $instr) {
                    $instr = "/'";
                    $s[] = ']';
                  }
                  else {
                    if ("'" == $f[$i] || '"' == $f[$i]) {
                      ++$q[$f[$i]];
                      $s[] = '\\' . $f[$i];
                    }
                    else {
                      $s[] = $f[$i];
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    else {
      switch ($f[$i]) {
        case ';':

          // Remove triple semi-colon
          if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
            $f[$i] = $f[$i + 1] = '/';
          }
          else {
            $code[++$j] = ';';
            break;
          }
        case '/':
          if ('*' == $f[$i + 1]) {
            ++$i;
            $instr = '*';
            if ($this->keepImportantComments && '!' == $f[$i + 1]) {
              ++$i;

              // no break here
            }
            else {
              break;
            }
          }
          else {
            if ('/' == $f[$i + 1]) {
              ++$i;
              $instr = '//';
              break;
            }
            else {
              $a = $j && ' ' == $code[$j] ? $code[$j - 1] : $code[$j];
              if (false !== strpos('-!%&;<=>~:^+|,(*?[{ ', $a) || false !== strpos('oenfd', $a) && preg_match("'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield) ?\$'", substr($code, $j - 7, 8))) {
                $key = "//''\"\"" . $K++ . ($instr = "/'");
                $a = $j;
                $code .= $key;
                while (isset($key[++$j - $a - 1])) {
                  $code[$j] = $key[$j - $a - 1];
                }
                --$j;
                isset($s) && ($s = implode('', $s)) && $cc_on && $this
                  ->restoreCc($s);
                $strings[$key] = array(
                  '/',
                );
                $s =& $strings[$key];
              }
              else {
                $code[++$j] = '/';
              }
              break;
            }
          }
        case "'":
        case '"':
          $instr = $f[$i];
          $key = "//''\"\"" . $K++ . ('!' == $instr ? ']' : "'");
          $a = $j;
          $code .= $key;
          while (isset($key[++$j - $a - 1])) {
            $code[$j] = $key[$j - $a - 1];
          }
          --$j;
          isset($s) && ($s = implode('', $s)) && $cc_on && $this
            ->restoreCc($s);
          $strings[$key] = array();
          $s =& $strings[$key];
          '!' == $instr && ($s[] = "\r/*!");
          break;
        case "\n":
          if ($j > 5) {
            ' ' == $code[$j] && --$j;
            $code[++$j] = false !== strpos('kend', $code[$j - 1]) && preg_match("'(?<![\$.a-zA-Z0-9_])(break|continue|return|yield) ?\$'", substr($code, $j - 8, 9)) ? ';' : ' ';
            break;
          }
        case "\t":
          $f[$i] = ' ';
        case ' ':
          if (!$j || ' ' == $code[$j]) {
            break;
          }
        default:
          $code[++$j] = $f[$i];
      }
    }
  }
  isset($s) && ($s = implode('', $s)) && $cc_on && $this
    ->restoreCc($s);
  unset($s);
  $code = substr($code, 0, $j + 1);
  $cc_on && $this
    ->restoreCc($code, false);

  // Protect wanted spaces and remove unwanted ones
  $code = str_replace('- -', "--", $code);
  $code = str_replace('+ +', "++", $code);
  $code = preg_replace("'(\\d)\\s+\\.\\s*([a-zA-Z\$_[(])'", "\$1.\$2", $code);
  $code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\\]{}/']+)#", '$1', $code);
  $code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\\]{}/]+) #", '$1', $code);

  // Replace new Array/Object by []/{}
  false !== strpos($code, 'new Array') && ($code = preg_replace("'new Array(?:\\(\\)|([;\\])},:]))'", '[]$1', $code));
  false !== strpos($code, 'new Object') && ($code = preg_replace("'new Object(?:\\(\\)|([;\\])},:]))'", '{}$1', $code));

  // Add missing semi-colons after curly braces
  // This adds more semi-colons than strictly needed,
  // but it seems that later gzipping is favorable to the repetition of "};"
  $code = preg_replace("'\\}(?![:,;.()\\[\\]}\\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);

  // Tag possible empty instruction for easy detection
  $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\\('", '1#(', $code);
  $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\\('", '2#(', $code);
  $code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\\('", '3#(', $code);
  $forPool = array();
  $instrPool = array();
  $s = 0;
  $f = array();
  $j = -1;

  // Remove as much semi-colon as possible
  $len = strlen($code);
  for ($i = 0; $i < $len; ++$i) {
    switch ($code[$i]) {
      case '(':
        if ($j >= 0 && "\n" == $f[$j]) {
          $f[$j] = ';';
        }
        ++$s;
        if ($i && '#' == $code[$i - 1]) {
          $instrPool[$s - 1] = 1;
          if ('2' == $code[$i - 2]) {
            $forPool[$s] = 1;
          }
        }
        $f[++$j] = '(';
        break;
      case ']':
      case ')':
        if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
          $f[$j] .= $code[$i];
          $f[++$j] = "\n";
        }
        else {
          $f[++$j] = $code[$i];
        }
        if (')' == $code[$i]) {
          unset($forPool[$s]);
          --$s;
        }
        continue 2;
      case '}':
        if ("\n" == $f[$j]) {
          $f[$j] = '}';
        }
        else {
          $f[++$j] = '}';
        }
        break;
      case ';':
        if (isset($forPool[$s]) || isset($instrPool[$s])) {
          $f[++$j] = ';';
        }
        else {
          if ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
            $f[++$j] = "\n";
          }
        }
        break;
      case '#':
        switch ($f[$j]) {
          case '1':
            $f[$j] = 'if';
            break 2;
          case '2':
            $f[$j] = 'for';
            break 2;
          case '3':
            $f[$j] = 'while';
            break 2;
        }
      case '[':
        if ($j >= 0 && "\n" == $f[$j]) {
          $f[$j] = ';';
        }
      default:
        $f[++$j] = $code[$i];
    }
    unset($instrPool[$s]);
  }
  $f = implode('', $f);
  $cc_on && ($f = str_replace('@#3', "\n", $f));

  // Fix "else ;" empty instructions
  $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f);
  $r1 = array(
    // keywords with a direct object
    'case',
    'delete',
    'do',
    'else',
    'function',
    'in',
    'instanceof',
    'break',
    'new',
    'return',
    'throw',
    'typeof',
    'var',
    'void',
    'yield',
    'let',
    'if',
    'const',
  );
  $r2 = array(
    // keywords with a subject
    'in',
    'instanceof',
  );

  // Fix missing semi-colons
  $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\\$])', $r1) . ") (?!(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f);
  $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\\('", "\nif(", $f);
  $f = preg_replace("'(?<=--|\\+\\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n\$1", $f);
  $f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\\('", 'for each(', $f);
  $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f);

  // Merge strings
  if ($q["'"] > $q['"']) {
    $q = array(
      $q[1],
      $q[0],
    );
  }
  $f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f);
  strpos($f, $q[0] . '+' . $q[0]) && ($f = str_replace($q[0] . '+' . $q[0], '', $f));
  $len = count($strings);
  foreach ($strings as $r1 => &$r2) {
    $r2 = "/'" == substr($r1, -2) ? str_replace(array(
      "\\'",
      '\\"',
    ), array(
      "'",
      '"',
    ), $r2) : str_replace('\\' . $q[1], $q[1], $r2);
  }

  // Restore wanted spaces
  $f = strtr($f, "", ' ');
  return array(
    $f,
    $strings,
  );
}