Show
Ignore:
Timestamp:
02/06/08 22:58:28 (1 year ago)
Author:
rambo
Message:

scripted whitespace normalization, see r14772

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/midcom/build/class.JavaScriptPacker.php

    r14083 r14773  
    6565 
    6666class JavaScriptPacker { 
    67        // constants 
    68        const IGNORE = '$1'; 
    69  
    70        // validate parameters 
    71        private $_script = ''; 
    72        private $_encoding = 62; 
    73        private $_fastDecode = true; 
    74        private $_specialChars = false; 
    75          
    76        private $LITERAL_ENCODING = array( 
    77                'None' => 0, 
    78                'Numeric' => 10, 
    79                'Normal' => 62, 
    80                'High ASCII' => 95 
    81        ); 
    82          
    83        public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false) 
    84        
    85                $this->_script = $_script . "\n"; 
    86                if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) 
    87                        $_encoding = $this->LITERAL_ENCODING[$_encoding]; 
    88                $this->_encoding = min((int)$_encoding, 95); 
    89                 $this->_fastDecode = $_fastDecode;       
    90                $this->_specialChars = $_specialChars; 
    91        
    92          
    93        public function pack() { 
    94                $this->_addParser('_basicCompression'); 
    95                if ($this->_specialChars) 
    96                        $this->_addParser('_encodeSpecialChars'); 
    97                if ($this->_encoding) 
    98                        $this->_addParser('_encodeKeywords'); 
    99                  
    100                // go! 
    101                return $this->_pack($this->_script); 
    102        
    103          
    104        // apply all parsing routines 
    105        private function _pack($script) { 
    106                for ($i = 0; isset($this->_parsers[$i]); $i++) { 
    107                        $script = call_user_func(array(&$this,$this->_parsers[$i]), $script); 
    108                
    109                return $script; 
    110        
    111          
    112        // keep a list of parsing functions, they'll be executed all at once 
    113        private $_parsers = array(); 
    114        private function _addParser($parser) { 
    115                $this->_parsers[] = $parser; 
    116        
    117          
    118        // zero encoding - just removal of white space and comments 
    119        private function _basicCompression($script) { 
    120                $parser = new ParseMaster(); 
    121                // make safe 
    122                $parser->escapeChar = '\\'; 
    123                // protect strings 
    124                $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE); 
    125                $parser->add('/"[^"\\n\\r]*"/', self::IGNORE); 
    126                // remove comments 
    127                $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' '); 
    128                $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' '); 
    129                // protect regular expressions 
    130                $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE 
    131                $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE); 
    132                // remove: ;;; doSomething(); 
    133                if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/'); 
    134                // remove redundant semi-colons 
    135                $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops 
    136                $parser->add('/;+\\s*([};])/', '$2'); 
    137                // apply the above 
    138                $script = $parser->exec($script); 
    139  
    140                // remove white-space 
    141                $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3'); 
    142                $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3'); 
    143                $parser->add('/\\s+/', ''); 
    144                // done 
    145                return $parser->exec($script); 
    146        
    147          
    148        private function _encodeSpecialChars($script) { 
    149                $parser = new ParseMaster(); 
    150                // replace: $name -> n, $$name -> na 
    151                $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/', 
    152                                         array('fn' => '_replace_name') 
    153                ); 
    154                // replace: _name -> _0, double-underscore (__name) is ignored 
    155                $regexp = '/\\b_[A-Za-z\\d]\\w*/'; 
    156                // build the word list 
    157                $keywords = $this->_analyze($script, $regexp, '_encodePrivate'); 
    158                // quick ref 
    159                $encoded = $keywords['encoded']; 
    160                  
    161                $parser->add($regexp, 
    162                        array( 
    163                                'fn' => '_replace_encoded', 
    164                                'data' => $encoded 
    165                        
    166                ); 
    167                return $parser->exec($script); 
    168        
    169          
    170        private function _encodeKeywords($script) { 
    171                // escape high-ascii values already in the script (i.e. in strings) 
    172                if ($this->_encoding > 62) 
    173                        $script = $this->_escape95($script); 
    174                // create the parser 
    175                $parser = new ParseMaster(); 
    176                $encode = $this->_getEncoder($this->_encoding); 
    177                // for high-ascii, don't encode single character low-ascii 
    178                $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/'; 
    179                // build the word list 
    180                $keywords = $this->_analyze($script, $regexp, $encode); 
    181                $encoded = $keywords['encoded']; 
    182                  
    183                // encode 
    184                $parser->add($regexp, 
    185                        array( 
    186                                'fn' => '_replace_encoded', 
    187                                'data' => $encoded 
    188                        
    189                ); 
    190                if (empty($script)) return $script; 
    191                else { 
    192                        //$res = $parser->exec($script); 
    193                        //$res = $this->_bootStrap($res, $keywords); 
    194                        //return $res; 
    195                        return $this->_bootStrap($parser->exec($script), $keywords); 
    196                
    197        
    198          
    199        private function _analyze($script, $regexp, $encode) { 
    200                // analyse 
    201                // retrieve all words in the script 
    202                $all = array(); 
    203                preg_match_all($regexp, $script, $all); 
    204                $_sorted = array(); // list of words sorted by frequency 
    205                $_encoded = array(); // dictionary of word->encoding 
    206                $_protected = array(); // instances of "protected" words 
    207                $all = $all[0]; // simulate the javascript comportement of global match 
    208                if (!empty($all)) { 
    209                        $unsorted = array(); // same list, not sorted 
    210                        $protected = array(); // "protected" words (dictionary of word->"word") 
    211                        $value = array(); // dictionary of charCode->encoding (eg. 256->ff) 
    212                        $this->_count = array(); // word->count 
    213                        $i = count($all); $j = 0; //$word = null; 
    214                        // count the occurrences - used for sorting later 
    215                        do { 
    216                                --$i; 
    217                                $word = '$' . $all[$i]; 
    218                                if (!isset($this->_count[$word])) { 
    219                                        $this->_count[$word] = 0; 
    220                                        $unsorted[$j] = $word; 
    221                                        // make a dictionary of all of the protected words in this script 
    222                                        //  these are words that might be mistaken for encoding 
    223                                        //if (is_string($encode) && method_exists($this, $encode)) 
    224                                        $values[$j] = call_user_func(array(&$this, $encode), $j); 
    225                                        $protected['$' . $values[$j]] = $j++; 
    226                                
    227                                // increment the word counter 
    228                                $this->_count[$word]++; 
    229                        } while ($i > 0); 
    230                        // prepare to sort the word list, first we must protect 
    231                        //  words that are also used as codes. we assign them a code 
    232                        //  equivalent to the word itself. 
    233                        // e.g. if "do" falls within our encoding range 
    234                        //      then we store keywords["do"] = "do"; 
    235                        // this avoids problems when decoding 
    236                        $i = count($unsorted); 
    237                        do { 
    238                                $word = $unsorted[--$i]; 
    239                                if (isset($protected[$word]) /*!= null*/) { 
    240                                        $_sorted[$protected[$word]] = substr($word, 1); 
    241                                        $_protected[$protected[$word]] = true; 
    242                                        $this->_count[$word] = 0; 
    243                                
    244                        } while ($i); 
    245                          
    246                        // sort the words by frequency 
    247                        // Note: the javascript and php version of sort can be different : 
    248                        // in php manual, usort : 
    249                        // " If two members compare as equal, 
    250                        // their order in the sorted array is undefined." 
    251                        // so the final packed script is different of the Dean's javascript version 
    252                        // but equivalent. 
    253                        // the ECMAscript standard does not guarantee this behaviour, 
    254                        // and thus not all browsers (e.g. Mozilla versions dating back to at 
    255                        // least 2003) respect this.  
    256                        usort($unsorted, array(&$this, '_sortWords')); 
    257                        $j = 0; 
    258                        // because there are "protected" words in the list 
    259                        //  we must add the sorted words around them 
    260                        do { 
    261                                if (!isset($_sorted[$i])) 
    262                                        $_sorted[$i] = substr($unsorted[$j++], 1); 
    263                                $_encoded[$_sorted[$i]] = $values[$i]; 
    264                        } while (++$i < count($unsorted)); 
    265                
    266                return array( 
    267                        'sorted'  => $_sorted, 
    268                        'encoded' => $_encoded, 
    269                        'protected' => $_protected); 
    270        
    271          
    272        private $_count = array(); 
    273        private function _sortWords($match1, $match2) { 
    274                return $this->_count[$match2] - $this->_count[$match1]; 
    275        
    276          
    277        // build the boot function used for loading and decoding 
    278        private function _bootStrap($packed, $keywords) { 
    279                $ENCODE = $this->_safeRegExp('$encode\\($count\\)'); 
    280  
    281                // $packed: the packed script 
    282                $packed = "'" . $this->_escape($packed) . "'"; 
    283  
    284                // $ascii: base for encoding 
    285                $ascii = min(count($keywords['sorted']), $this->_encoding); 
    286                if ($ascii == 0) $ascii = 1; 
    287  
    288                // $count: number of words contained in the script 
    289                $count = count($keywords['sorted']); 
    290  
    291                // $keywords: list of words contained in the script 
    292                foreach ($keywords['protected'] as $i=>$value) { 
    293                        $keywords['sorted'][$i] = ''; 
    294                
    295                // convert from a string to an array 
    296                ksort($keywords['sorted']); 
    297                $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')"; 
    298  
    299                $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii); 
    300                $encode = $this->_getJSFunction($encode); 
    301                $encode = preg_replace('/_encoding/','$ascii', $encode); 
    302                $encode = preg_replace('/arguments\\.callee/','$encode', $encode); 
    303                $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : ''); 
    304  
    305                // $decode: code snippet to speed up decoding 
    306                if ($this->_fastDecode) { 
    307                        // create the decoder 
    308                        $decode = $this->_getJSFunction('_decodeBody'); 
    309                        if ($this->_encoding > 62) 
    310                                $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode); 
    311                        // perform the encoding inline for lower ascii values 
    312                        elseif ($ascii < 36) 
    313                                $decode = preg_replace($ENCODE, $inline, $decode); 
    314                        // special case: when $count==0 there are no keywords. I want to keep 
    315                        //  the basic shape of the unpacking funcion so i'll frig the code... 
    316                        if ($count == 0) 
    317                                $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); 
    318                
    319  
    320                // boot function 
    321                $unpack = $this->_getJSFunction('_unpack'); 
    322                if ($this->_fastDecode) { 
    323                        // insert the decoder 
    324                        $this->buffer = $decode; 
    325                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1); 
    326                
    327                $unpack = preg_replace('/"/', "'", $unpack); 
    328                if ($this->_encoding > 62) { // high-ascii 
    329                        // get rid of the word-boundaries for regexp matches 
    330                        $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack); 
    331                
    332                if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) { 
    333                        // insert the encode function 
    334                        $this->buffer = $encode; 
    335                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1); 
    336                } else { 
    337                        // perform the encoding inline 
    338                        $unpack = preg_replace($ENCODE, $inline, $unpack); 
    339                
    340                // pack the boot function too 
    341                $unpackPacker = new JavaScriptPacker($unpack, 0, false, true); 
    342                $unpack = $unpackPacker->pack(); 
    343                  
    344                // arguments 
    345                $params = array($packed, $ascii, $count, $keywords); 
    346                if ($this->_fastDecode) { 
    347                        $params[] = 0; 
    348                        $params[] = '{}'; 
    349                
    350                $params = implode(',', $params); 
    351                  
    352                // the whole thing 
    353                return 'eval(' . $unpack . '(' . $params . "))\n"; 
    354        
    355          
    356        private $buffer; 
    357        private function _insertFastDecode($match) { 
    358                return '{' . $this->buffer . ';'; 
    359        
    360        private function _insertFastEncode($match) { 
    361                return '{$encode=' . $this->buffer . ';'; 
    362        
    363          
    364        // mmm.. ..which one do i need ?? 
    365        private function _getEncoder($ascii) { 
    366                return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ? 
    367                       '_encode95' : '_encode62' : '_encode36' : '_encode10'; 
    368        
    369          
    370        // zero encoding 
    371        // characters: 0123456789 
    372        private function _encode10($charCode) { 
    373                return $charCode; 
    374        
    375          
    376        // inherent base36 support 
    377        // characters: 0123456789abcdefghijklmnopqrstuvwxyz 
    378        private function _encode36($charCode) { 
    379                return base_convert($charCode, 10, 36); 
    380        
    381          
    382        // hitch a ride on base36 and add the upper case alpha characters 
    383        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 
    384        private function _encode62($charCode) { 
    385                $res = ''; 
    386                if ($charCode >= $this->_encoding) { 
    387                        $res = $this->_encode62((int)($charCode / $this->_encoding)); 
    388                
    389                $charCode = $charCode % $this->_encoding; 
    390                  
    391                if ($charCode > 35) 
    392                        return $res . chr($charCode + 29); 
    393                else 
    394                        return $res . base_convert($charCode, 10, 36); 
    395        
    396          
    397        // use high-ascii values 
    398        // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄà
     67    // constants 
     68    const IGNORE = '$1'; 
     69 
     70    // validate parameters 
     71    private $_script = ''; 
     72    private $_encoding = 62; 
     73    private $_fastDecode = true; 
     74    private $_specialChars = false; 
     75     
     76    private $LITERAL_ENCODING = array( 
     77        'None' => 0, 
     78        'Numeric' => 10, 
     79        'Normal' => 62, 
     80        'High ASCII' => 95 
     81    ); 
     82     
     83    public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false) 
     84   
     85        $this->_script = $_script . "\n"; 
     86        if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) 
     87            $_encoding = $this->LITERAL_ENCODING[$_encoding]; 
     88        $this->_encoding = min((int)$_encoding, 95); 
     89        $this->_fastDecode = $_fastDecode;     
     90        $this->_specialChars = $_specialChars; 
     91   
     92     
     93    public function pack() { 
     94        $this->_addParser('_basicCompression'); 
     95        if ($this->_specialChars) 
     96            $this->_addParser('_encodeSpecialChars'); 
     97        if ($this->_encoding) 
     98            $this->_addParser('_encodeKeywords'); 
     99         
     100        // go! 
     101        return $this->_pack($this->_script); 
     102   
     103     
     104    // apply all parsing routines 
     105    private function _pack($script) { 
     106        for ($i = 0; isset($this->_parsers[$i]); $i++) { 
     107            $script = call_user_func(array(&$this,$this->_parsers[$i]), $script); 
     108       
     109        return $script; 
     110   
     111     
     112    // keep a list of parsing functions, they'll be executed all at once 
     113    private $_parsers = array(); 
     114    private function _addParser($parser) { 
     115        $this->_parsers[] = $parser; 
     116   
     117     
     118    // zero encoding - just removal of white space and comments 
     119    private function _basicCompression($script) { 
     120        $parser = new ParseMaster(); 
     121        // make safe 
     122        $parser->escapeChar = '\\'; 
     123        // protect strings 
     124        $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE); 
     125        $parser->add('/"[^"\\n\\r]*"/', self::IGNORE); 
     126        // remove comments 
     127        $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' '); 
     128        $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' '); 
     129        // protect regular expressions 
     130        $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE 
     131        $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE); 
     132        // remove: ;;; doSomething(); 
     133        if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/'); 
     134        // remove redundant semi-colons 
     135        $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops 
     136        $parser->add('/;+\\s*([};])/', '$2'); 
     137        // apply the above 
     138        $script = $parser->exec($script); 
     139 
     140        // remove white-space 
     141        $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3'); 
     142        $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3'); 
     143        $parser->add('/\\s+/', ''); 
     144        // done 
     145        return $parser->exec($script); 
     146   
     147     
     148    private function _encodeSpecialChars($script) { 
     149        $parser = new ParseMaster(); 
     150        // replace: $name -> n, $$name -> na 
     151        $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/', 
     152                    array('fn' => '_replace_name') 
     153        ); 
     154        // replace: _name -> _0, double-underscore (__name) is ignored 
     155        $regexp = '/\\b_[A-Za-z\\d]\\w*/'; 
     156        // build the word list 
     157        $keywords = $this->_analyze($script, $regexp, '_encodePrivate'); 
     158        // quick ref 
     159        $encoded = $keywords['encoded']; 
     160         
     161        $parser->add($regexp, 
     162            array( 
     163                'fn' => '_replace_encoded', 
     164                'data' => $encoded 
     165           
     166        ); 
     167        return $parser->exec($script); 
     168   
     169     
     170    private function _encodeKeywords($script) { 
     171        // escape high-ascii values already in the script (i.e. in strings) 
     172        if ($this->_encoding > 62) 
     173            $script = $this->_escape95($script); 
     174        // create the parser 
     175        $parser = new ParseMaster(); 
     176        $encode = $this->_getEncoder($this->_encoding); 
     177        // for high-ascii, don't encode single character low-ascii 
     178        $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/'; 
     179        // build the word list 
     180        $keywords = $this->_analyze($script, $regexp, $encode); 
     181        $encoded = $keywords['encoded']; 
     182         
     183        // encode 
     184        $parser->add($regexp, 
     185            array( 
     186                'fn' => '_replace_encoded', 
     187                'data' => $encoded 
     188           
     189        ); 
     190        if (empty($script)) return $script; 
     191        else { 
     192            //$res = $parser->exec($script); 
     193            //$res = $this->_bootStrap($res, $keywords); 
     194            //return $res; 
     195            return $this->_bootStrap($parser->exec($script), $keywords); 
     196       
     197   
     198     
     199    private function _analyze($script, $regexp, $encode) { 
     200        // analyse 
     201        // retrieve all words in the script 
     202        $all = array(); 
     203        preg_match_all($regexp, $script, $all); 
     204        $_sorted = array(); // list of words sorted by frequency 
     205        $_encoded = array(); // dictionary of word->encoding 
     206        $_protected = array(); // instances of "protected" words 
     207        $all = $all[0]; // simulate the javascript comportement of global match 
     208        if (!empty($all)) { 
     209            $unsorted = array(); // same list, not sorted 
     210            $protected = array(); // "protected" words (dictionary of word->"word") 
     211            $value = array(); // dictionary of charCode->encoding (eg. 256->ff) 
     212            $this->_count = array(); // word->count 
     213            $i = count($all); $j = 0; //$word = null; 
     214            // count the occurrences - used for sorting later 
     215            do { 
     216                --$i; 
     217                $word = '$' . $all[$i]; 
     218                if (!isset($this->_count[$word])) { 
     219                    $this->_count[$word] = 0; 
     220                    $unsorted[$j] = $word; 
     221                    // make a dictionary of all of the protected words in this script 
     222                    //  these are words that might be mistaken for encoding 
     223                    //if (is_string($encode) && method_exists($this, $encode)) 
     224                    $values[$j] = call_user_func(array(&$this, $encode), $j); 
     225                    $protected['$' . $values[$j]] = $j++; 
     226               
     227                // increment the word counter 
     228                $this->_count[$word]++; 
     229            } while ($i > 0); 
     230            // prepare to sort the word list, first we must protect 
     231            //  words that are also used as codes. we assign them a code 
     232            //  equivalent to the word itself. 
     233            // e.g. if "do" falls within our encoding range 
     234            //      then we store keywords["do"] = "do"; 
     235            // this avoids problems when decoding 
     236            $i = count($unsorted); 
     237            do { 
     238                $word = $unsorted[--$i]; 
     239                if (isset($protected[$word]) /*!= null*/) { 
     240                    $_sorted[$protected[$word]] = substr($word, 1); 
     241                    $_protected[$protected[$word]] = true; 
     242                    $this->_count[$word] = 0; 
     243               
     244            } while ($i); 
     245             
     246            // sort the words by frequency 
     247            // Note: the javascript and php version of sort can be different : 
     248            // in php manual, usort : 
     249            // " If two members compare as equal, 
     250            // their order in the sorted array is undefined." 
     251            // so the final packed script is different of the Dean's javascript version 
     252            // but equivalent. 
     253            // the ECMAscript standard does not guarantee this behaviour, 
     254            // and thus not all browsers (e.g. Mozilla versions dating back to at 
     255            // least 2003) respect this.  
     256            usort($unsorted, array(&$this, '_sortWords')); 
     257            $j = 0; 
     258            // because there are "protected" words in the list 
     259            //  we must add the sorted words around them 
     260            do { 
     261                if (!isset($_sorted[$i])) 
     262                    $_sorted[$i] = substr($unsorted[$j++], 1); 
     263                $_encoded[$_sorted[$i]] = $values[$i]; 
     264            } while (++$i < count($unsorted)); 
     265       
     266        return array( 
     267            'sorted'  => $_sorted, 
     268            'encoded' => $_encoded, 
     269            'protected' => $_protected); 
     270   
     271     
     272    private $_count = array(); 
     273    private function _sortWords($match1, $match2) { 
     274        return $this->_count[$match2] - $this->_count[$match1]; 
     275   
     276     
     277    // build the boot function used for loading and decoding 
     278    private function _bootStrap($packed, $keywords) { 
     279        $ENCODE = $this->_safeRegExp('$encode\\($count\\)'); 
     280 
     281        // $packed: the packed script 
     282        $packed = "'" . $this->_escape($packed) . "'"; 
     283 
     284        // $ascii: base for encoding 
     285        $ascii = min(count($keywords['sorted']), $this->_encoding); 
     286        if ($ascii == 0) $ascii = 1; 
     287 
     288        // $count: number of words contained in the script 
     289        $count = count($keywords['sorted']); 
     290 
     291        // $keywords: list of words contained in the script 
     292        foreach ($keywords['protected'] as $i=>$value) { 
     293            $keywords['sorted'][$i] = ''; 
     294       
     295        // convert from a string to an array 
     296        ksort($keywords['sorted']); 
     297        $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')"; 
     298 
     299        $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii); 
     300        $encode = $this->_getJSFunction($encode); 
     301        $encode = preg_replace('/_encoding/','$ascii', $encode); 
     302        $encode = preg_replace('/arguments\\.callee/','$encode', $encode); 
     303        $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : ''); 
     304 
     305        // $decode: code snippet to speed up decoding 
     306        if ($this->_fastDecode) { 
     307            // create the decoder 
     308            $decode = $this->_getJSFunction('_decodeBody'); 
     309            if ($this->_encoding > 62) 
     310                $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode); 
     311            // perform the encoding inline for lower ascii values 
     312            elseif ($ascii < 36) 
     313                $decode = preg_replace($ENCODE, $inline, $decode); 
     314            // special case: when $count==0 there are no keywords. I want to keep 
     315            //  the basic shape of the unpacking funcion so i'll frig the code... 
     316            if ($count == 0) 
     317                $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); 
     318       
     319 
     320        // boot function 
     321        $unpack = $this->_getJSFunction('_unpack'); 
     322        if ($this->_fastDecode) { 
     323            // insert the decoder 
     324            $this->buffer = $decode; 
     325            $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1); 
     326       
     327        $unpack = preg_replace('/"/', "'", $unpack); 
     328        if ($this->_encoding > 62) { // high-ascii 
     329            // get rid of the word-boundaries for regexp matches 
     330            $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack); 
     331       
     332        if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) { 
     333            // insert the encode function 
     334            $this->buffer = $encode; 
     335            $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1); 
     336        } else { 
     337            // perform the encoding inline 
     338            $unpack = preg_replace($ENCODE, $inline, $unpack); 
     339       
     340        // pack the boot function too 
     341        $unpackPacker = new JavaScriptPacker($unpack, 0, false, true); 
     342        $unpack = $unpackPacker->pack(); 
     343         
     344        // arguments 
     345        $params = array($packed, $ascii, $count, $keywords); 
     346        if ($this->_fastDecode) { 
     347            $params[] = 0; 
     348            $params[] = '{}'; 
     349       
     350        $params = implode(',', $params); 
     351         
     352        // the whole thing 
     353        return 'eval(' . $unpack . '(' . $params . "))\n"; 
     354   
     355     
     356    private $buffer; 
     357    private function _insertFastDecode($match) { 
     358        return '{' . $this->buffer . ';'; 
     359   
     360    private function _insertFastEncode($match) { 
     361        return '{$encode=' . $this->buffer . ';'; 
     362   
     363     
     364    // mmm.. ..which one do i need ?? 
     365    private function _getEncoder($ascii) { 
     366        return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ? 
     367               '_encode95' : '_encode62' : '_encode36' : '_encode10'; 
     368   
     369     
     370    // zero encoding 
     371    // characters: 0123456789 
     372    private function _encode10($charCode) { 
     373        return $charCode; 
     374   
     375     
     376    // inherent base36 support 
     377    // characters: 0123456789abcdefghijklmnopqrstuvwxyz 
     378    private function _encode36($charCode) { 
     379        return base_convert($charCode, 10, 36); 
     380   
     381     
     382    // hitch a ride on base36 and add the upper case alpha characters 
     383    // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 
     384    private function _encode62($charCode) { 
     385        $res = ''; 
     386        if ($charCode >= $this->_encoding) { 
     387            $res = $this->_encode62((int)($charCode / $this->_encoding)); 
     388       
     389        $charCode = $charCode % $this->_encoding; 
     390         
     391        if ($charCode > 35) 
     392            return $res . chr($charCode + 29); 
     393        else 
     394            return $res . base_convert($charCode, 10, 36); 
     395   
     396     
     397    // use high-ascii values 
     398    // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄà
    399399ÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãÀåÊçÚéêëìíîïðñòóÎõö÷ÞùúûÌÜß 
    400        private function _encode95($charCode) { 
    401                $res = ''; 
    402                if ($charCode >= $this->_encoding) 
    403                        $res = $this->_encode95($charCode / $this->_encoding); 
    404                  
    405                return $res . chr(($charCode % $this->_encoding) + 161); 
    406        
    407          
    408        private function _safeRegExp($string) { 
    409                return '/'.preg_replace('/\$/', '\\\$', $string).'/'; 
    410        
    411          
    412        private function _encodePrivate($charCode) { 
    413                return "_" . $charCode; 
    414        
    415          
    416        // protect characters used by the parser 
    417        private function _escape($script) { 
    418                return preg_replace('/([\\\\\'])/', '\\\$1', $script); 
    419        
    420          
    421        // protect high-ascii characters already in the script 
    422        private function _escape95($script) { 
    423                return preg_replace_callback( 
    424                        '/[\\xa1-\\xff]/', 
    425                        array(&$this, '_escape95Bis'), 
    426                        $script 
    427                ); 
    428        
    429        private function _escape95Bis($match) { 
    430                return '\x'.((string)dechex(ord($match))); 
    431        
    432          
    433          
    434        private function _getJSFunction($aName) { 
    435                if (defined('self::JSFUNCTION'.$aName)) 
    436                        return constant('self::JSFUNCTION'.$aName); 
    437                else  
    438                        return ''; 
    439        
    440          
    441        // JavaScript Functions used. 
    442        // Note : In Dean's version, these functions are converted 
    443        // with 'String(aFunctionName);'. 
    444        // This internal conversion complete the original code, ex : 
    445        // 'while (aBool) anAction();' is converted to 
    446        // 'while (aBool) { anAction(); }'. 
    447        // The JavaScript functions below are corrected. 
    448          
    449        // unpacking function - this is the boot strap function 
    450        //  data extracted from this packing routine is passed to 
    451        //  this function when decoded in the target 
    452        // NOTE ! : without the ';' final. 
    453        const JSFUNCTION_unpack = 
     400    private function _encode95($charCode) { 
     401        $res = ''; 
     402        if ($charCode >= $this->_encoding) 
     403            $res = $this->_encode95($charCode / $this->_encoding); 
     404         
     405        return $res . chr(($charCode % $this->_encoding) + 161); 
     406   
     407     
     408    private function _safeRegExp($string) { 
     409        return '/'.preg_replace('/\$/', '\\\$', $string).'/'; 
     410   
     411     
     412    private function _encodePrivate($charCode) { 
     413        return "_" . $charCode; 
     414   
     415     
     416    // protect characters used by the parser 
     417    private function _escape($script) { 
     418        return preg_replace('/([\\\\\'])/', '\\\$1', $script); 
     419   
     420     
     421    // protect high-ascii characters already in the script 
     422    private function _escape95($script) { 
     423        return preg_replace_callback( 
     424            '/[\\xa1-\\xff]/', 
     425            array(&$this, '_escape95Bis'), 
     426            $script 
     427        ); 
     428   
     429    private function _escape95Bis($match) { 
     430        return '\x'.((string)dechex(ord($match))); 
     431   
     432     
     433     
     434    private function _getJSFunction($aName) { 
     435        if (defined('self::JSFUNCTION'.$aName)) 
     436            return constant('self::JSFUNCTION'.$aName); 
     437        else  
     438            return ''; 
     439   
     440     
     441    // JavaScript Functions used. 
     442    // Note : In Dean's version, these functions are converted 
     443    // with 'String(aFunctionName);'. 
     444    // This internal conversion complete the original code, ex : 
     445    // 'while (aBool) anAction();' is converted to 
     446    // 'while (aBool) { anAction(); }'. 
     447    // The JavaScript functions below are corrected. 
     448     
     449    // unpacking function - this is the boot strap function 
     450    //  data extracted from this packing routine is passed to 
     451    //  this function when decoded in the target 
     452    // NOTE ! : without the ';' final. 
     453    const JSFUNCTION_unpack = 
    454454 
    455455'function($packed, $ascii, $count, $keywords, $encode, $decode) { 
     
    469469}'; 
    470470*/ 
    471          
    472        // code-snippet inserted into the unpacker to speed up decoding 
    473        const JSFUNCTION_decodeBody = 
     471     
     472    // code-snippet inserted into the unpacker to speed up decoding 
     473    const JSFUNCTION_decodeBody = 
    474474//_decode = function() { 
    475475// does the browser support String.replace where the 
     
    491491//}; 
    492492/* 
    493 '      if (!\'\'.replace(/^/, String)) { 
     493'    if (!\'\'.replace(/^/, String)) { 
    494494        // decode all the values we need 
    495495        while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count); 
     
    502502    }'; 
    503503*/ 
    504          
    505         // zero encoding 
    506         // characters: 0123456789 
    507         const JSFUNCTION_encode10 = 
     504     
     505    // zero encoding 
     506    // characters: 0123456789 
     507    const JSFUNCTION_encode10 = 
    508508'function($charCode) { 
    509509    return $charCode; 
    510510}';//;'; 
    511          
    512         // inherent base36 support 
    513         // characters: 0123456789abcdefghijklmnopqrstuvwxyz 
    514         const JSFUNCTION_encode36 = 
     511     
     512    // inherent base36 support 
     513    // characters: 0123456789abcdefghijklmnopqrstuvwxyz 
     514    const JSFUNCTION_encode36 = 
    515515'function($charCode) { 
    516516    return $charCode.toString(36); 
    517517}';//;'; 
    518          
    519        // hitch a ride on base36 and add the upper case alpha characters 
    520        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 
    521        const JSFUNCTION_encode62 = 
     518     
     519    // hitch a ride on base36 and add the upper case alpha characters 
     520    // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 
     521    const JSFUNCTION_encode62 = 
    522522'function($charCode) { 
    523523    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) + 
    524524    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36)); 
    525525}'; 
    526          
    527        // use high-ascii values 
    528        // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄà
     526     
     527    // use high-ascii values 
     528    // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄà
    529529ÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãÀåÊçÚéêëìíîïðñòóÎõö÷ÞùúûÌÜß 
    530        const JSFUNCTION_encode95 = 
     530    const JSFUNCTION_encode95 = 
    531531'function($charCode) { 
    532532    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) + 
    533533        String.fromCharCode($charCode % _encoding + 161); 
    534534}';  
    535          
     535     
    536536} 
    537537 
    538538 
    539539class ParseMaster { 
    540        public $ignoreCase = false; 
    541        public $escapeChar = ''; 
    542          
    543        // constants 
    544        const EXPRESSION = 0; 
    545        const REPLACEMENT = 1; 
    546        const LENGTH = 2; 
    547          
    548        // used to determine nesting levels 
    549        private $GROUPS = '/\\(/';//g 
    550        private $SUB_REPLACE = '/\\$\\d/'; 
    551        private $INDEXED = '/^\\$\\d+$/'; 
    552        private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/'; 
    553        private $ESCAPE = '/\\\./';//g 
    554        private $QUOTE = '/\'/'; 
    555        private $DELETED = '/\\x01[^\\x01]*\\x01/';//g 
    556          
    557        public function add($expression, $replacement = '') { 
    558                // count the number of sub-expressions 
    559                //  - add one because each pattern is itself a sub-expression&