2 // @codingStandardsIgnoreFile
3 // @ignore comment_docblock_file:file
4 // @ignore style_curly_braces:file
5 // @ignore style_string_spacing:file
6 // @ignore style_else_spacing:file
7 // @ignore comment_comment_docblock_missing:file
8 // @ignore comment_comment_eg:file
9 // @ignore production_code:file
10 // @ignore druplart_unary:file
11 // @ignore style_uppercase_constants:file
12 // @ignore comment_comment_space:file
13 // @ignore druplart_conditional_assignment:file
14 // @ignore style_paren_spacing:file
15 // @ignore style_no_tabs:file
16 // @ignore comment_docblock_comment:file
17 // @ignore comment_comment_indent:file
18 // @ignore style_comma_spacing:file
19 // @ignore style_elseif:file
23 * Copyright (C) 2015 Nicolas Grekas - p@tchwork.com
25 * This library is free software; you can redistribute it and/or modify it
26 * under the terms of the (at your option):
27 * Apache License v2.0 (see provided LICENCE.ASL20 file), or
28 * GNU General Public License v2.0 (see provided LICENCE.GPLv2 file).
35 * This class shrinks Javascript code
36 * (a process called minification nowadays)
38 * Should work with most valid Javascript code,
39 * even when semi-colons are missing.
42 * - Removes comments and white spaces.
43 * - Renames every local vars, typically to a single character.
44 * - Renames also global vars, methods and properties, but only if they
45 * are marked special by some naming convention. By default, special
46 * var names begin with one or more "$", or with a single "_".
47 * - Renames also local/global vars found in strings,
48 * but only if they are marked special.
49 * - Keep Microsoft's conditional comments.
50 * - Output is optimized for later HTTP compression.
53 * - Source code must be parse error free before processing.
54 * - In order to maximise later HTTP compression (deflate, gzip),
55 * new variables names are chosen by considering closures,
56 * variables' frequency and characters' frequency.
57 * - If you use with/eval then be careful.
60 * - Replaces false/true by !1/!0
61 * - Replaces new Array/Object by []/{}
62 * - Merges consecutive "var" declarations with commas
63 * - Merges consecutive concatened strings
64 * - Fix a bug in Safari's parser (http://forums.asp.net/thread/1585609.aspx)
65 * - Can replace optional semi-colons by line feeds,
66 * thus facilitating output debugging.
67 * - Keep important comments marked with /*!...
68 * - Treats three semi-colons ;;; like single-line comments
69 * (http://dean.edwards.name/packer/2/usage/#triple-semi-colon).
70 * - Fix special catch scope across browsers
71 * - Work around buggy-handling of named function expressions in IE<=8
74 * - foo['bar'] => foo.bar
75 * - {'foo':'bar'} => {foo:'bar'}
76 * - Dead code removal (never used function)
77 * - Munge primitives: var WINDOW=window, etc.
84 SPECIAL_VAR_PACKER = '(\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
98 $keepImportantComments,
100 $varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*',
102 'abstract','as','boolean','break','byte','case','catch','char','class',
103 'const','continue','debugger','default','delete','do','double','else',
104 'enum','export','extends','false','final','finally','float','for',
105 'function','goto','if','implements','import','in','instanceof','int',
106 'long','native','new','null','package','private','protected','public',
107 'return','short','static','super','switch','synchronized','this',
108 'throw','throws','transient','true','try','typeof','var','void',
109 'while','with','yield','let','interface',
113 function __construct()
115 $this->reserved = array_flip($this->reserved);
116 $this->charFreq = array_fill(0, 256, 0);
120 * Squeezes a JavaScript source code.
122 * Set $singleLine to false if you want optional
123 * semi-colons to be replaced by line feeds.
125 * Set $keepImportantComments to false if you want /*! comments to be removed.
127 * $specialVarRx defines the regular expression of special variables names
128 * for global vars, methods, properties and in string substitution.
129 * Set it to false if you don't want any.
131 * If the analysed javascript source contains a single line comment like
132 * this one, then the directive will overwrite $specialVarRx:
134 * // jsqueeze.specialVarRx = your_special_var_regexp_here
136 * Only the first directive is parsed, others are ignored. It is not possible
137 * to redefine $specialVarRx in the middle of the javascript source.
140 * $parser = new JSqueeze;
141 * $squeezed_js = $parser->squeeze($fat_js);
144 function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false)
147 if ('' === $code) return '';
149 $this->argFreq = array(-1 => 0);
150 $this->specialVarRx = $specialVarRx;
151 $this->keepImportantComments = !!$keepImportantComments;
153 if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\1#i", $code, $key))
157 $key[2] = trim($key[2]);
158 $key[1] = strtolower($key[2]);
159 $key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
162 $this->specialVarRx = $key[1] ? $key[2] : false;
165 // Remove capturing parentheses
166 $this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx);
168 false !== strpos($code, "\r" ) && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n");
169 false !== strpos($code, "\xC2\x85" ) && $code = str_replace("\xC2\x85" , "\n", $code); // Next Line
170 false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator
171 false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator
173 list($code, $this->strings ) = $this->extractStrings( $code);
174 list($code, $this->closures) = $this->extractClosures($code);
176 $key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings
177 $this->closures[$key] =& $code;
179 $tree = array($key => array('parent' => false));
180 $this->makeVars($code, $tree[$key], $key);
181 $this->renameVars($tree[$key], true);
183 $code = substr($tree[$key]['code'], 1);
184 $code = preg_replace("'\breturn !'", 'return!', $code);
185 $code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
186 $code = str_replace(array_keys($this->strings), array_values($this->strings), $code);
188 if ($singleLine) $code = strtr($code, "\n", ';');
189 else $code = str_replace("\n", ";\n", $code);
190 false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n");
193 $this->charFreq = array_fill(0, 256, 0);
194 $this->strings = $this->closures = $this->argFreq = array();
195 $this->str0 = $this->str1 = '';
201 protected function extractStrings($f)
203 if ($cc_on = false !== strpos($f, '@cc_on'))
205 // Protect conditional comments from being removed
206 $f = str_replace('#', '##', $f);
207 $f = str_replace('/*@', '1#@', $f);
208 $f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
209 $f = str_replace('@*/', '@#1', $f);
213 $code = str_repeat(' ', $len);
227 // Extract strings, removes comments
228 for ($i = 0; $i < $len; ++$i)
240 else if ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr))
243 else if ('*' == $instr)
255 while (isset ($f[$i+1]) && false !== strpos('gmi', $f[$i+1])) $s[] = $f[$i++];
262 else if ('*' == $instr) ;
263 else if ('!' == $instr)
265 if ('*' == $f[$i] && '/' == $f[$i+1])
271 else if ("\n" == $f[$i]) $s[] = "\r";
274 else if ('\\' == $f[$i])
280 isset($q[$f[$i]]) && ++$q[$f[$i]];
281 $s[] = '\\' . $f[$i];
284 else if ('[' == $f[$i] && "/'" == $instr)
289 else if (']' == $f[$i] && '/[' == $instr)
294 else if ("'" == $f[$i] || '"' == $f[$i])
297 $s[] = '\\' . $f[$i];
304 // Remove triple semi-colon
305 if ($i>0 && ';' == $f[$i-1] && $i+1 < $len && ';' == $f[$i+1]) $f[$i] = $f[$i+1] = '/';
318 if ($this->keepImportantComments && '!' == $f[$i+1])
325 else if ('/' == $f[$i+1])
333 $a = $j && ' ' == $code[$j] ? $code[$j-1] : $code[$j];
334 if (false !== strpos('-!%&;<=>~:^+|,(*?[{ ', $a)
335 || (false !== strpos('oenfd', $a)
337 "'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield) ?$'",
338 substr($code, $j-7, 8)
341 $key = "//''\"\"" . $K++ . $instr = "/'";
344 while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j;
345 isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
346 $strings[$key] = array('/');
347 $s =& $strings[$key];
349 else $code[++$j] = '/';
357 $key = "//''\"\"" . $K++ . ('!' == $instr ? ']' : "'");
360 while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j;
361 isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
362 $strings[$key] = array();
363 $s =& $strings[$key];
364 '!' == $instr && $s[] = "\r/*!";
371 ' ' == $code[$j] && --$j;
374 false !== strpos('kend', $code[$j-1])
376 "'(?<![\$.a-zA-Z0-9_])(break|continue|return|yield) ?$'",
377 substr($code, $j-8, 9)
384 case "\t": $f[$i] = ' ';
386 if (!$j || ' ' == $code[$j]) break;
389 $code[++$j] = $f[$i];
393 isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
396 $code = substr($code, 0, $j+1);
397 $cc_on && $this->restoreCc($code, false);
399 // Protect wanted spaces and remove unwanted ones
400 $code = str_replace('- -', "-\x7F-", $code);
401 $code = str_replace('+ +', "+\x7F+", $code);
402 $code = preg_replace("'(\d)\s+\.\s*([a-zA-Z\$_[(])'", "$1\x7F.$2", $code);
403 $code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\]{}/']+)#", '$1', $code);
404 $code = preg_replace( "#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code);
406 // Replace new Array/Object by []/{}
407 false !== strpos($code, 'new Array' ) && $code = preg_replace( "'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code);
408 false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code);
410 // Add missing semi-colons after curly braces
411 // This adds more semi-colons than strictly needed,
412 // but it seems that later gzipping is favorable to the repetition of "};"
413 $code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
415 // Tag possible empty instruction for easy detection
416 $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('" , '1#(', $code);
417 $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('" , '2#(', $code);
418 $code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code);
421 $instrPool = array();
427 // Remove as much semi-colon as possible
428 $len = strlen($code);
429 for ($i = 0; $i < $len; ++$i)
434 if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';';
438 if ($i && '#' == $code[$i-1])
440 $instrPool[$s - 1] = 1;
441 if ('2' == $code[$i-2]) $forPool[$s] = 1;
449 if ($i+1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s-1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i+1]))
454 else $f[++$j] = $code[$i];
456 if (')' == $code[$i])
465 if ("\n" == $f[$j]) $f[$j] = '}';
470 if (isset($forPool[$s]) || isset($instrPool[$s])) $f[++$j] = ';';
471 else if ($j>=0 && "\n" != $f[$j] && ';' != $f[$j]) $f[++$j] = "\n";
478 case '1': $f[$j] = 'if'; break 2;
479 case '2': $f[$j] = 'for'; break 2;
480 case '3': $f[$j] = 'while'; break 2;
484 if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';';
486 default: $f[++$j] = $code[$i];
489 unset($instrPool[$s]);
492 $f = implode('', $f);
493 $cc_on && $f = str_replace('@#3', "\n", $f);
495 // Fix "else ;" empty instructions
496 $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f);
498 $r1 = array( // keywords with a direct object
499 'case','delete','do','else','function','in','instanceof','break',
500 'new','return','throw','typeof','var','void','yield','let','if',
504 $r2 = array( // keywords with a subject
508 // Fix missing semi-colons
509 $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1) . ") (?!(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f);
510 $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f);
511 $f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n$1", $f);
512 $f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f);
513 $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f);
516 if ($q["'"] > $q['"']) $q = array($q[1], $q[0]);
517 $f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f);
518 strpos($f, $q[0] . '+' . $q[0]) && $f = str_replace($q[0] . '+' . $q[0], '', $f);
519 $len = count($strings);
520 foreach ($strings as $r1 => &$r2)
522 $r2 = "/'" == substr($r1, -2)
523 ? str_replace(array("\\'", '\\"'), array("'", '"'), $r2)
524 : str_replace('\\' . $q[1], $q[1], $r2);
527 // Restore wanted spaces
528 $f = strtr($f, "\x7F", ' ');
530 return array($f, $strings);
533 protected function extractClosures($code)
537 $this->argFreq[-1] += substr_count($code, '}catch(');
539 if ($this->argFreq[-1])
541 // Special catch scope handling
543 // FIXME: this implementation doesn't work with nested catch scopes who need
544 // access to their parent's caught variable (but who needs that?).
546 $f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
548 $code = 'catch$scope$var' . mt_rand();
549 $this->specialVarRx = $this->specialVarRx ? '(?:' . $this->specialVarRx . '|' . preg_quote($code) . ')' : preg_quote($code);
558 while ($c && $j < $l)
561 $c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
567 $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
569 while ($c && $j < $l);
571 $c = preg_quote($f[$i-1], '#');
572 $f[$i-2] .= '}catch(' . preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1' . $code, $f[$i-1] . substr($f[$i], 0, $j)) . substr($f[$i], $j);
574 unset($f[$i--], $f[$i--]);
580 $f = preg_split("'(?<![a-zA-Z0-9_\$])(function[ (].*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
590 while ($c && $j < $l)
593 $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
596 switch (substr($f[$i-2], -1))
598 default: if (false !== $c = strpos($f[$i-1], ' ', 8)) break;
599 case false: case "\n": case ';': case '{': case '}': case ')': case ']':
600 $c = strpos($f[$i-1], '(', 8);
604 $code = substr($f[$i-1], $c);
605 $closures[$l] = $code . substr($f[$i], 0, $j);
606 $f[$i-2] .= substr($f[$i-1], 0, $c) . $l . substr($f[$i], $j);
610 $j = substr_count($code, ',');
611 do isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1;
618 return array($f[0], $closures);
621 protected function makeVars($closure, &$tree, $key)
623 $tree['code'] =& $closure;
624 $tree['nfe'] = false;
625 $tree['used'] = array();
626 $tree['local'] = array();
628 // Replace multiple "var" declarations by a single one
629 $closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\}]+(?:\nvar [^\n\{\}]+)+'", array(&$this, 'mergeVarDeclarations'), $closure);
631 // Get all local vars (functions, arguments and "var" prefixed)
633 $vars =& $tree['local'];
635 if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v))
639 $vars[$tree['nfe'] = substr($v[1], 1)] = -1;
640 $tree['parent']['local'][';' . $key] =& $vars[$tree['nfe']];
646 $v = explode(',', $v[2]);
647 foreach ($v as $w) $vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables
651 $v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
652 if ($i = count($v) - 1)
665 case '(': case '[': case '{':
669 case ')': case ']': case '}':
670 if ($c-- <= 0) break 2;
677 $c || $w[] = $v[$i][$j];
687 $v = explode(',', implode('', $w));
688 foreach ($v as $w) if (preg_match("'^{$this->varRx}'", $w, $v)) isset($vars[$v[0]]) || $vars[$v[0]] = 0;
691 if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v))
693 foreach ($v[1] as $w) isset($vars[$w]) || $vars[$w] = 0;
696 if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v))
699 foreach ($v[1] as $w) isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1;
700 foreach ($v[0] as $w => $v) $vars[$w] = $this->argFreq[-1] - $v;
703 // Get all used vars, local and non-local
705 $vars =& $tree['used'];
707 if (preg_match_all("#([.,{]?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER))
711 if (',' === $k[1] || '{' === $k[1])
713 if (':' === substr($k[3], -1)) $k = '.' . $k[2];
716 else $k = $k[1] . $k[2];
718 isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1;
722 if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) foreach ($w[0] as $a)
724 $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx
725 ? preg_split("#([.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE)
726 : array($this->strings[$a]);
729 for ($i = 0; $i < $a; ++$i)
735 if (',' === $k[0] || '{' === $k[0])
737 if (':' === substr($k, -1)) $k = '.' . substr($k, 1, -1);
738 else $k = substr($k, 1);
740 else if (':' === substr($k, -1)) $k = substr($k, 0, -1);
744 while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) $w =& $w['parent'];
746 (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1);
751 if (0 === $i%2 || !isset($vars[$k])) foreach (count_chars($v[$i], 1) as $k => $w) $this->charFreq[$k] += $w;
755 // Propagate the usage number to parents
757 foreach ($vars as $w => $a)
763 $vars =& $k['local'];
765 if (isset($vars[$w]))
767 unset($k['used'][$w]);
768 if (isset($vars[$w])) $vars[$w] += $a;
774 while ($k['parent'] && $k =& $k['parent']);
776 if ($a && !$k['parent'])
778 if (isset($vars[$w])) $vars[$w] += $a;
782 if (isset($tree['used'][$w]) && isset($vars[$w])) foreach ($chain as &$b)
784 isset($b['local'][$w]) || $b['used'][$w] =& $vars[$w];
790 $tree['childs'] = array();
791 $vars =& $tree['childs'];
793 if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w))
795 foreach ($w[0] as $a)
797 $vars[$a] = array('parent' => &$tree);
798 $this->makeVars($this->closures[$a], $vars[$a], $a);
803 protected function mergeVarDeclarations($m)
805 return str_replace("\nvar ", ',', $m[0]);
808 protected function renameVars(&$tree, $root)
812 $tree['local'] += $tree['used'];
813 $tree['used'] = array();
815 foreach ($tree['local'] as $k => $v)
817 if ('.' == $k[0]) $k = substr($k, 1);
819 if ('true' === $k) $this->charFreq[48] += $v;
820 else if ('false' === $k) $this->charFreq[49] += $v;
821 else if (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k))
823 foreach (count_chars($k, 1) as $k => $w) $this->charFreq[$k] += $w * $v;
825 else if (2 == strlen($k)) $tree['used'][] = $k[1];
828 arsort($this->charFreq);
833 foreach ($this->charFreq as $k => $v)
839 if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) // A-Z a-z
844 else if (47 < $k && $k < 58) // 0-9
850 if ('' === $this->str0)
852 $this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
853 $this->str1 = $this->str0 . '0123456789';
856 foreach ($tree['local'] as $var => $root)
858 if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) $tree['local'][$var] += $tree['local'][".{$var}"];
861 foreach ($tree['local'] as $var => $root)
863 if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) $tree['local'][$var] = $tree['local'][substr($var, 1)];
866 arsort($tree['local']);
868 foreach ($tree['local'] as $var => $root) switch (substr($var, 0, 1))
871 if (!isset($tree['local'][substr($var, 1)]))
873 $tree['local'][$var] = '#' . ($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : substr($var, 1));
877 case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
881 $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : $var;
882 $tree['local'][$var] = $root;
883 if (isset($tree['local'][".{$var}"])) $tree['local'][".{$var}"] = '#' . $root;
886 foreach ($tree['local'] as $var => $root) $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
890 arsort($tree['local']);
891 if (false !== $tree['nfe']) $tree['used'][] = $tree['local'][$tree['nfe']];
893 foreach ($tree['local'] as $var => $root)
894 if ($tree['nfe'] !== $var)
895 $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
898 $this->local_tree =& $tree['local'];
899 $this->used_tree =& $tree['used'];
901 $tree['code'] = preg_replace_callback("#[.,{ ]?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array(&$this, 'getNewName'), $tree['code']);
902 $this->specialVarRx && $tree['code'] = preg_replace_callback("#//''\"\"[0-9]+'#", array(&$this, 'renameInString'), $tree['code']);
904 foreach ($tree['childs'] as $a => &$b)
906 $this->renameVars($b, false);
907 $tree['code'] = str_replace($a, $b['code'], $tree['code']);
908 unset($tree['childs'][$a]);
912 protected function renameInString($a)
914 $b =& $this->strings[$a[0]];
915 unset($this->strings[$a[0]]);
917 return preg_replace_callback(
918 "#[.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#",
919 array(&$this, 'getNewName'),
924 protected function getNewName($m)
928 $pre = '.' === $m[0] ? '.' : '';
931 if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0])
935 if (':' === substr($m, -1))
938 $m = (' ' !== $m[0] ? '.' : '') . substr($m, 1, -1);
940 else $m = substr($m, 1);
942 else if (':' === substr($m, -1))
945 $m = substr($m, 0, -1);
948 $post = (isset($this->reserved[$m])
949 ? ('true' === $m ? '!0' : ('false' === $m ? '!1': $m))
951 isset($this->local_tree[$m])
952 ? $this->local_tree[$m]
954 isset($this->used_tree[$m])
955 ? $this->used_tree[$m]
961 return '' === $post ? '' : ($pre . ('.' === $post[0] ? substr($post, 1) : $post));
964 protected function getNextName(&$tree = array(), &$counter = false)
966 if (false === $counter)
968 $counter =& $tree['counter'];
969 isset($counter) || $counter = -1;
970 $exclude = array_flip($tree['used']);
972 else $exclude = $tree;
976 $len0 = strlen($this->str0);
977 $len1 = strlen($this->str0);
979 $name = $this->str0[$counter % $len0];
981 $i = intval($counter / $len0) - 1;
984 $name .= $this->str1[ $i % $len1 ];
985 $i = intval($i / $len1) - 1;
988 return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
991 protected function restoreCc(&$s, $lf = true)
993 $lf && $s = str_replace('@#3', '', $s);
995 $s = str_replace('@#1', '@*/', $s);
996 $s = str_replace('2#@', '//@', $s);
997 $s = str_replace('1#@', '/*@', $s);
998 $s = str_replace('##', '#', $s);