source: trunk/admin/inc/Controller.class.php @ 7655

Revision 7655, 29.1 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Melhorias de performance no codigo do Expresso.

  • Property svn:executable set to *
Line 
1<?php
2                /***************************************************************************
3                * Expresso Livre                                                           *
4                * http://www.expressolivre.org                                             *
5                * --------------------------------------------                             *
6                *  This program is free software; you can redistribute it and/or modify it *
7                *  under the terms of the GNU General Public License as published by the   *
8                *  Free Software Foundation; either version 2 of the License, or (at your  *
9                *  option) any later version.                                              *
10                \**************************************************************************/
11               
12class Controller
13{
14        const __CONTROLLER_SECURITY__                           = 'controller-security';
15        const __CONTROLLER_CONTENTES__                          = 'controller-contentes';
16        const __CONTROLLER_SECTIONS__                           = 'controller-sections';
17
18        const __STRING_ACCESS__                                         = 'string-access';
19        const __STRING_DELIMITER__                                      = 'string-delimiter';
20
21        const __CONTROLLER_CONTENTES_ITEM__                     = 'item';
22        const __CONTROLLER_CONTENTES_ITEM_PARAM__       = 'param';
23        const __CONTROLLER_CONTENTES_ITEM_SECTION__     = 'section';
24
25        const __CONTROLLER_SECTIONS_ITEM__                      = 'item';
26
27        private $fatalError                                                     = false;
28
29        public function __construct()
30        {
31                try
32                {
33                        $controler_xml = dirname(__FILE__) . '/controller.xml';
34                        if ( !file_exists($controler_xml) )
35                                throw new Exception(__CLASS__ . ' [ ERROR ] :: the configuration file does not exist');
36
37                        $this->dom = new DOMDocument;
38                        $this->dom->preserveWhiteSpace = FALSE;
39                        $this->dom->load($controler_xml);
40
41                        unset($controler_xml);
42
43                        $this->controller_security = $this->dom->getElementsByTagName(self::__CONTROLLER_SECURITY__);
44                        if ( $this->controller_security->length === (int)0 )
45                                throw new Exception(__CLASS__ . ' [ ERROR #0 ] :: the tag "' . self::__CONTROLLER_SECURITY__ . '" does not exist');
46                        if ( $this->controller_security->length !== (int)1 )
47                                throw new Exception(__CLASS__ . ' [ ERROR #1 ] :: exists more of a tag "' . self::__CONTROLLER_SECURITY__ . '"');
48
49                        $this->controller_contentes = $this->dom->getElementsByTagName(self::__CONTROLLER_CONTENTES__);
50                        if ( $this->controller_contentes->length === (int)0 )
51                                throw new Exception(__CLASS__ . ' [ ERROR #2 ] :: the tag "' . self::__CONTROLLER_CONTENTES__ . '" does not exist');
52                        if ( $this->controller_contentes->length !== (int)1 )
53                                throw new Exception(__CLASS__ . ' [ ERROR #3 ] :: exists more of a tag "' . self::__CONTROLLER_CONTENTES__ . '"');
54                        $this->controller_contentes = $this->controller_contentes->item(0);
55
56                        $this->controller_sections = $this->dom->getElementsByTagName("controller-sections");
57                        if ( $this->controller_sections->length === (int)0 )
58                                throw new Exception(__CLASS__ . ' [ ERROR #4 ] :: the tag "' . self::__CONTROLLER_SECTIONS__ . '" does not exist');
59                        if ( $this->controller_sections->length !== (int)1 )
60                                throw new Exception(__CLASS__ . ' [ ERROR #5 ] :: exists more of a tag "' . self::__CONTROLLER_SECTIONS__ . '"');
61                        $this->controller_sections = $this->controller_sections->item(0);
62
63                        $this->string_access = $this->controller_security->item(0)->getElementsByTagName(self::__STRING_ACCESS__);
64                        if ( $this->string_access->length === (int)0 )
65                                throw new Exception(__CLASS__ . ' [ ERROR #6 ] :: the tag "' . self::__STRING_ACCESS__ . '" does not exist');
66                        if ( $this->string_access->length !== (int)1 )
67                                throw new Exception(__CLASS__ . ' [ ERROR #7 ] :: exists more of a tag "' . self::__STRING_ACCESS__ . '"');
68                        $this->string_access = $this->string_access->item(0)->nodeValue;
69
70                        $this->string_delimiter = $this->controller_security->item(0)->getElementsByTagName(self::__STRING_DELIMITER__);
71                        ( $this->string_delimiter->length === (int)0 )
72                                and die(__CLASS__ . ' [ ERROR #8 ] :: the tag "' . self::__STRING_DELIMITER__ . '" does not exist');
73                        if ( $this->string_delimiter->length !== (int)1 )
74                                throw new Exception(__CLASS__ . ' [ ERROR #9 ] :: exists more of a tag "' . self::__STRING_DELIMITER__ . '"');
75                        $this->string_delimiter = $this->string_delimiter->item(0)->nodeValue;
76                }
77                catch(Exception $e)
78                {
79                        $this->fatalError = true;
80                        return $e->getMessage();
81                }
82        }
83
84        public function __call($name, $arguments)
85        {
86                if ( !$this->fatalError )
87                        switch ( $name )
88                        {
89                                case 'exec' :
90                                        return $this->_exec($arguments[0]);
91                                break;
92                                default : return "Method not avaible";
93                        }
94        }
95
96        public function __toString()
97        {
98                return __CLASS__;
99        }
100
101        private final function _exec(array &$pRequest)
102        {
103                ( $pRequest[$this->string_access] )
104                        or die(__CLASS__ . ' [ ERROR #10 ] :: bad string action argument');
105
106                list($section_name, $ref, $alias) = explode($this->string_delimiter, $pRequest[$this->string_access]);
107                unset($pRequest[$this->string_access]);
108
109                $contents_itens = $this->controller_contentes->getElementsByTagName(self::__CONTROLLER_CONTENTES_ITEM__);
110
111                for ( $i = 0; $i < $contents_itens->length && $contents_itens->item($i)->getAttribute(self::__CONTROLLER_CONTENTES_ITEM_PARAM__) != $section_name; ++$i );
112                ( !($i < $contents_itens->length) )
113                        and die(__CLASS__ . ' [ ERROR #11 ] :: invalid section "' . $section_name . '"');
114
115                $section_name = $contents_itens->item($i)->getAttribute(self::__CONTROLLER_CONTENTES_ITEM_SECTION__);
116
117                $section = $this->controller_sections->getElementsByTagName($section_name);
118                ( $section->length === (int)0 )
119                        and die(__CLASS__ . ' [ ERROR #12 ] :: the tag "' . $section_name . '" does not exist');
120                ( $section->length === (int)1 )
121                        or die(__CLASS__ . ' [ ERROR #13 ] :: exists more of a tag "' . $section_name . '"');
122                $section = $section->item(0);
123
124                $section_itens = $section->getElementsByTagName(self::__CONTROLLER_SECTIONS_ITEM__);
125
126                if ( empty($alias) && $alias !== '0' )
127                        for ( $i = 0; $i < $section_itens->length && $section_itens->item($i)->getAttribute('ref') != $ref; ++$i );
128                else
129                        for ( $i = 0; $i < $section_itens->length && ( $section_itens->item($i)->getAttribute('ref') != $ref || $section_itens->item($i)->getAttribute('alias') !== $alias); ++$i );
130
131                ( !($i < $section_itens->length) )
132                        and die(__CLASS__ . ' [ ERROR #14 ] :: invalid reference "' . $ref . '"');
133
134                $path = $section_itens->item($i)->getAttribute('path')
135                        or $path = $section->getAttribute('path')
136                        or die(__CLASS__ . ' [ ERROR #15 ] :: bad path argument');
137
138                $prefix = $section_itens->item($i)->getAttribute('prefix')
139                        or $prefix = $section->getAttribute('prefix');
140
141                $suffix = $section_itens->item($i)->getAttribute('suffix')
142                        or $suffix = $section->getAttribute('suffix')
143                        or die(__CLASS__ . ' [ ERROR #16 ] :: bad suffix argument');
144
145                return $this->$section_name(array("pSectionItem" => $section_itens->item($i), "pPath" => $path, "pPrefix" => $prefix, "pSuffix" => $suffix, "pRequest" => $pRequest));
146        }
147
148        private final function php()
149        {
150                $params = func_get_args();
151                extract($params[0]);
152
153                $class = $pSectionItem->getAttribute('class')
154                        and $method = $pSectionItem->getAttribute('method')
155                        or die(__CLASS__ . ' [ ERROR #17 ] :: bad class or method argument');
156
157                $file = "{$pPath}/{$pPrefix}{$class}{$pSuffix}";
158
159                file_exists($file)
160                        or die(__CLASS__ . ' [ ERROR #18 ] :: the file that has the class was not opened');
161
162                require_once $file;
163
164                $obj = new ReflectionClass($class);
165
166                if ( $pRequest['classCostructor'] )
167                {
168                        $obj = $obj->newInstance($pRequest['classCostructor']);
169                        unset($pRequest['classCostructor']);
170                }
171                else
172                        $obj = $obj->newInstance();
173
174                $method = new ReflectionMethod($class, $method);
175                $result = $method->invoke($obj, $pRequest);
176
177                #$obj = new $class;
178
179                #if ( $pRequest )
180                #       $result = $obj -> $method($pRequest);
181                #else
182                #       $result = $obj -> $method();
183
184                return $result;
185        }
186
187        private final function js()
188        {
189                $params = func_get_args();
190                extract($params[0]);
191
192                $js = $pSectionItem->getAttribute('js')
193                        or die(__CLASS__ . ' [ ERROR #18 ] :: bad js argument');
194
195                $file = "{$pPath}/{$pPrefix}{$js}{$pSuffix}";
196
197                file_exists($file)
198                        or die(__CLASS__ . ' [ ERROR #19 ] :: the file that has the class was not opened');
199
200                $debug = $pSectionItem->parentNode->getAttribute('debug');
201                if ( $debug && ($debug === 'true') )
202                        return file_get_contents($file);
203
204                $packed_file = "{$pPath}/.packer.{$pPrefix}{$js}{$pSuffix}";
205
206                (
207                        file_exists($packed_file)
208                        and filemtime($packed_file) > filemtime($file)
209                        and $packed = file_get_contents($packed_file)
210                )
211                or
212                (
213                        $packer = new JavaScriptPacker(file_get_contents($file), 'High ASCII', true, true)
214                        and $packed = $packer->pack()
215                        and file_put_contents($packed_file, $packed)
216                );
217
218                return $packed;
219        }
220}
221
222class JavaScriptPacker
223{
224        const IGNORE = '$1';
225
226        private $_script = '';
227        private $_encoding = 62;
228        private $_fastDecode = true;
229        private $_specialChars = false;
230
231        private $_parsers = array();
232
233        private $LITERAL_ENCODING = array(
234                'None' => 0,
235                'Numeric' => 10,
236                'Normal' => 62,
237                'High ASCII' => 95
238        );
239
240        public function __construct ($pScript, $pEncoding = 62, $pFastDecode = true, $pSpecialChars = false)
241        {
242                $this->_script = $pScript . "\n";
243                if ( array_key_exists($pEncoding, $this->LITERAL_ENCODING) )
244                        $pEncoding = $this->LITERAL_ENCODING[$pEncoding];
245
246                $this->_encoding = min((int)$pEncoding, 95);
247                $this->_fastDecode = $pFastDecode;
248                $this->_specialChars = $pSpecialChars;
249        }
250
251        public function pack()
252        {
253                $this->_addParser('_basicCompression');
254
255                if ( $this->_specialChars )
256                        $this->_addParser('_encodeSpecialChars');
257
258                if ( $this->_encoding )
259                        $this->_addParser('_encodeKeywords');
260
261                return $this->_pack($this->_script);
262        }
263
264        private function _pack($script)
265        {
266                for ( $i = 0; isset($this->_parsers[$i]); ++$i )
267                        $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
268
269                return $script;
270        }
271
272        // keep a list of parsing functions, they'll be executed all at once
273        private function _addParser($parser)
274        {
275                $this->_parsers[] = $parser;
276        }
277
278        // zero encoding - just removal of white space and comments
279        private function _basicCompression($script)
280        {
281                $parser = new ParseMaster();
282                // make safe
283                $parser->escapeChar = '\\';
284                // protect strings
285                $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
286                $parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
287                // remove comments
288                $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
289                $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
290                // protect regular expressions
291                $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
292                $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
293                // remove: ;;; doSomething();
294                if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
295                // remove redundant semi-colons
296                $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
297                $parser->add('/;+\\s*([};])/', '$2');
298                // apply the above
299                $script = $parser->exec($script);
300
301                // remove white-space
302                $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
303                $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
304                $parser->add('/\\s+/', '');
305                // done
306                return $parser->exec($script);
307        }
308
309        private function _encodeSpecialChars($script)
310        {
311                $parser = new ParseMaster();
312                // replace: $name -> n, $$name -> na
313                $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
314                                         array('fn' => '_replace_name')
315                );
316                // replace: _name -> _0, double-underscore (__name) is ignored
317                $regexp = '/\\b_[A-Za-z\\d]\\w*/';
318                // build the word list
319                $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
320                // quick ref
321                $encoded = $keywords['encoded'];
322
323                $parser->add($regexp,
324                        array(
325                                'fn' => '_replace_encoded',
326                                'data' => $encoded
327                        )
328                );
329                return $parser->exec($script);
330        }
331
332        private function _encodeKeywords($script) {
333                // escape high-ascii values already in the script (i.e. in strings)
334                if ($this->_encoding > 62)
335                        $script = $this->_escape95($script);
336                // create the parser
337                $parser = new ParseMaster();
338                $encode = $this->_getEncoder($this->_encoding);
339                // for high-ascii, don't encode single character low-ascii
340                $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
341                // build the word list
342                $keywords = $this->_analyze($script, $regexp, $encode);
343                $encoded = $keywords['encoded'];
344
345                // encode
346                $parser->add($regexp,
347                        array(
348                                'fn' => '_replace_encoded',
349                                'data' => $encoded
350                        )
351                );
352                if (empty($script)) return $script;
353                else {
354                        //$res = $parser->exec($script);
355                        //$res = $this->_bootStrap($res, $keywords);
356                        //return $res;
357                        return $this->_bootStrap($parser->exec($script), $keywords);
358                }
359        }
360
361        private function _analyze($script, $regexp, $encode) {
362                // analyse
363                // retreive all words in the script
364                $all = array();
365                preg_match_all($regexp, $script, $all);
366                $_sorted = array(); // list of words sorted by frequency
367                $_encoded = array(); // dictionary of word->encoding
368                $_protected = array(); // instances of "protected" words
369                $all = $all[0]; // simulate the javascript comportement of global match
370                if (!empty($all)) {
371                        $unsorted = array(); // same list, not sorted
372                        $protected = array(); // "protected" words (dictionary of word->"word")
373                        $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
374                        $this->_count = array(); // word->count
375                        $i = count($all); $j = 0; //$word = null;
376                        // count the occurrences - used for sorting later
377                        do {
378                                --$i;
379                                $word = '$' . $all[$i];
380                                if (!isset($this->_count[$word])) {
381                                        $this->_count[$word] = 0;
382                                        $unsorted[$j] = $word;
383                                        // make a dictionary of all of the protected words in this script
384                                        //  these are words that might be mistaken for encoding
385                                        //if (is_string($encode) && method_exists($this, $encode))
386                                        $values[$j] = call_user_func(array(&$this, $encode), $j);
387                                        $protected['$' . $values[$j]] = $j++;
388                                }
389                                // increment the word counter
390                                $this->_count[$word]++;
391                        } while ($i > 0);
392                        // prepare to sort the word list, first we must protect
393                        //  words that are also used as codes. we assign them a code
394                        //  equivalent to the word itself.
395                        // e.g. if "do" falls within our encoding range
396                        //      then we store keywords["do"] = "do";
397                        // this avoids problems when decoding
398                        $i = count($unsorted);
399                        do {
400                                $word = $unsorted[--$i];
401                                if (isset($protected[$word]) /*!= null*/) {
402                                        $_sorted[$protected[$word]] = substr($word, 1);
403                                        $_protected[$protected[$word]] = true;
404                                        $this->_count[$word] = 0;
405                                }
406                        } while ($i);
407
408                        // sort the words by frequency
409                        // Note: the javascript and php version of sort can be different :
410                        // in php manual, usort :
411                        // " If two members compare as equal,
412                        // their order in the sorted array is undefined."
413                        // so the final packed script is different of the Dean's javascript version
414                        // but equivalent.
415                        // the ECMAscript standard does not guarantee this behaviour,
416                        // and thus not all browsers (e.g. Mozilla versions dating back to at
417                        // least 2003) respect this.
418                        usort($unsorted, array(&$this, '_sortWords'));
419                        $j = 0;
420                        // because there are "protected" words in the list
421                        //  we must add the sorted words around them
422                        do {
423                                if (!isset($_sorted[$i]))
424                                        $_sorted[$i] = substr($unsorted[$j++], 1);
425                                $_encoded[$_sorted[$i]] = $values[$i];
426                        } while (++$i < count($unsorted));
427                }
428                return array(
429                        'sorted'  => $_sorted,
430                        'encoded' => $_encoded,
431                        'protected' => $_protected);
432        }
433
434        private $_count = array();
435        private function _sortWords($match1, $match2) {
436                return $this->_count[$match2] - $this->_count[$match1];
437        }
438
439        // build the boot function used for loading and decoding
440        private function _bootStrap($packed, $keywords) {
441                $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
442
443                // $packed: the packed script
444                $packed = "'" . $this->_escape($packed) . "'";
445
446                // $ascii: base for encoding
447                $ascii = min(count($keywords['sorted']), $this->_encoding);
448                if ($ascii == 0) $ascii = 1;
449
450                // $count: number of words contained in the script
451                $count = count($keywords['sorted']);
452
453                // $keywords: list of words contained in the script
454                foreach ($keywords['protected'] as $i=>$value) {
455                        $keywords['sorted'][$i] = '';
456                }
457                // convert from a string to an array
458                ksort($keywords['sorted']);
459                $keywords = "'" . implode('|',$keywords['sorted']) . "'.preg_split('/|/')";
460
461                $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
462                $encode = $this->_getJSFunction($encode);
463                $encode = preg_replace('/_encoding/','$ascii', $encode);
464                $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
465                $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
466
467                // $decode: code snippet to speed up decoding
468                if ($this->_fastDecode) {
469                        // create the decoder
470                        $decode = $this->_getJSFunction('_decodeBody');
471                        if ($this->_encoding > 62)
472                                $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
473                        // perform the encoding inline for lower ascii values
474                        elseif ($ascii < 36)
475                                $decode = preg_replace($ENCODE, $inline, $decode);
476                        // special case: when $count==0 there are no keywords. I want to keep
477                        //  the basic shape of the unpacking funcion so i'll frig the code...
478                        if ($count == 0)
479                                $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
480                }
481
482                // boot function
483                $unpack = $this->_getJSFunction('_unpack');
484                if ($this->_fastDecode) {
485                        // insert the decoder
486                        $this->buffer = $decode;
487                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
488                }
489                $unpack = preg_replace('/"/', "'", $unpack);
490                if ($this->_encoding > 62) { // high-ascii
491                        // get rid of the word-boundaries for regexp matches
492                        $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
493                }
494                if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
495                        // insert the encode function
496                        $this->buffer = $encode;
497                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
498                } else {
499                        // perform the encoding inline
500                        $unpack = preg_replace($ENCODE, $inline, $unpack);
501                }
502                // pack the boot function too
503                $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
504                $unpack = $unpackPacker->pack();
505
506                // arguments
507                $params = array($packed, $ascii, $count, $keywords);
508                if ($this->_fastDecode) {
509                        $params[] = 0;
510                        $params[] = '{}';
511                }
512                $params = implode(',', $params);
513
514                // the whole thing
515                return 'eval(' . $unpack . '(' . $params . "))\n";
516        }
517
518        private $buffer;
519        private function _insertFastDecode($match) {
520                return '{' . $this->buffer . ';';
521        }
522        private function _insertFastEncode($match) {
523                return '{$encode=' . $this->buffer . ';';
524        }
525
526        // mmm.. ..which one do i need ??
527        private function _getEncoder($ascii) {
528                return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
529                       '_encode95' : '_encode62' : '_encode36' : '_encode10';
530        }
531
532        // zero encoding
533        // characters: 0123456789
534        private function _encode10($charCode) {
535                return $charCode;
536        }
537
538        // inherent base36 support
539        // characters: 0123456789abcdefghijklmnopqrstuvwxyz
540        private function _encode36($charCode) {
541                return base_convert($charCode, 10, 36);
542        }
543
544        // hitch a ride on base36 and add the upper case alpha characters
545        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
546        private function _encode62($charCode) {
547                $res = '';
548                if ($charCode >= $this->_encoding) {
549                        $res = $this->_encode62((int)($charCode / $this->_encoding));
550                }
551                $charCode = $charCode % $this->_encoding;
552
553                if ($charCode > 35)
554                        return $res . chr($charCode + 29);
555                else
556                        return $res . base_convert($charCode, 10, 36);
557        }
558
559        // use high-ascii values
560        // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãÀåÊçÚéêëìíîïðñòóÎõö÷ÞùúûÌÜß
561        private function _encode95($charCode) {
562                $res = '';
563                if ($charCode >= $this->_encoding)
564                        $res = $this->_encode95($charCode / $this->_encoding);
565
566                return $res . chr(($charCode % $this->_encoding) + 161);
567        }
568
569        private function _safeRegExp($string) {
570                return '/'.preg_replace('/\$/', '\\\$', $string).'/';
571        }
572
573        private function _encodePrivate($charCode) {
574                return "_" . $charCode;
575        }
576
577        // protect characters used by the parser
578        private function _escape($script) {
579                return preg_replace('/([\\\\\'])/', '\\\$1', $script);
580        }
581
582        // protect high-ascii characters already in the script
583        private function _escape95($script) {
584                return preg_replace_callback(
585                        '/[\\xa1-\\xff]/',
586                        array(&$this, '_escape95Bis'),
587                        $script
588                );
589        }
590        private function _escape95Bis($match) {
591                return '\x'.((string)dechex(ord($match)));
592        }
593
594
595        private function _getJSFunction($aName) {
596                if (defined('self::JSFUNCTION'.$aName))
597                        return constant('self::JSFUNCTION'.$aName);
598                else
599                        return '';
600        }
601
602        // JavaScript Functions used.
603        // Note : In Dean's version, these functions are converted
604        // with 'String(aFunctionName);'.
605        // This internal conversion complete the original code, ex :
606        // 'while (aBool) anAction();' is converted to
607        // 'while (aBool) { anAction(); }'.
608        // The JavaScript functions below are corrected.
609
610        // unpacking function - this is the boot strap function
611        //  data extracted from this packing routine is passed to
612        //  this function when decoded in the target
613        // NOTE ! : without the ';' final.
614        const JSFUNCTION_unpack =
615
616'function($packed, $ascii, $count, $keywords, $encode, $decode) {
617    while ($count--) {
618        if ($keywords[$count]) {
619            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
620        }
621    }
622    return $packed;
623}';
624/*
625'function($packed, $ascii, $count, $keywords, $encode, $decode) {
626    while ($count--)
627        if ($keywords[$count])
628            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
629    return $packed;
630}';
631*/
632
633        // code-snippet inserted into the unpacker to speed up decoding
634        const JSFUNCTION_decodeBody =
635//_decode = function() {
636// does the browser support String.replace where the
637//  replacement value is a function?
638
639'    if (!\'\'.replace(/^/, String)) {
640        // decode all the values we need
641        while ($count--) {
642            $decode[$encode($count)] = $keywords[$count] || $encode($count);
643        }
644        // global replacement function
645        $keywords = [function ($encoded) {return $decode[$encoded]}];
646        // generic match
647        $encode = function () {return \'\\\\w+\'};
648        // reset the loop counter -  we are now doing a global replace
649        $count = 1;
650    }
651';
652//};
653/*
654'       if (!\'\'.replace(/^/, String)) {
655        // decode all the values we need
656        while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
657        // global replacement function
658        $keywords = [function ($encoded) {return $decode[$encoded]}];
659        // generic match
660        $encode = function () {return\'\\\\w+\'};
661        // reset the loop counter -  we are now doing a global replace
662        $count = 1;
663    }';
664*/
665
666         // zero encoding
667         // characters: 0123456789
668         const JSFUNCTION_encode10 =
669'function($charCode) {
670    return $charCode;
671}';//;';
672
673         // inherent base36 support
674         // characters: 0123456789abcdefghijklmnopqrstuvwxyz
675         const JSFUNCTION_encode36 =
676'function($charCode) {
677    return $charCode.toString(36);
678}';//;';
679
680        // hitch a ride on base36 and add the upper case alpha characters
681        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
682        const JSFUNCTION_encode62 =
683'function($charCode) {
684    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
685    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
686}';
687
688        // use high-ascii values
689        // characters: ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãÀåÊçÚéêëìíîïðñòóÎõö÷ÞùúûÌÜß
690        const JSFUNCTION_encode95 =
691'function($charCode) {
692    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
693        String.fromCharCode($charCode % _encoding + 161);
694}';
695
696}
697
698
699class ParseMaster {
700        public $ignoreCase = false;
701        public $escapeChar = '';
702
703        // constants
704        const EXPRESSION = 0;
705        const REPLACEMENT = 1;
706        const LENGTH = 2;
707
708        // used to determine nesting levels
709        private $GROUPS = '/\\(/';//g
710        private $SUB_REPLACE = '/\\$\\d/';
711        private $INDEXED = '/^\\$\\d+$/';
712        private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
713        private $ESCAPE = '/\\\./';//g
714        private $QUOTE = '/\'/';
715        private $DELETED = '/\\x01[^\\x01]*\\x01/';//g
716
717        public function add($expression, $replacement = '') {
718                // count the number of sub-expressions
719                //  - add one because each pattern is itself a sub-expression
720                $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
721
722                // treat only strings $replacement
723                if (is_string($replacement)) {
724                        // does the pattern deal with sub-expressions?
725                        if (preg_match($this->SUB_REPLACE, $replacement)) {
726                                // a simple lookup? (e.g. "$2")
727                                if (preg_match($this->INDEXED, $replacement)) {
728                                        // store the index (used for fast retrieval of matched strings)
729                                        $replacement = (int)(substr($replacement, 1)) - 1;
730                                } else { // a complicated lookup (e.g. "Hello $2 $1")
731                                        // build a function to do the lookup
732                                        $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
733                                                 ? '"' : "'";
734                                        $replacement = array(
735                                                'fn' => '_backReferences',
736                                                'data' => array(
737                                                        'replacement' => $replacement,
738                                                        'length' => $length,
739                                                        'quote' => $quote
740                                                )
741                                        );
742                                }
743                        }
744                }
745                // pass the modified arguments
746                if (!empty($expression)) $this->_add($expression, $replacement, $length);
747                else $this->_add('/^$/', $replacement, $length);
748        }
749
750        public function exec($string) {
751                // execute the global replacement
752                $this->_escaped = array();
753
754                // simulate the _patterns.toSTring of Dean
755                $regexp = '/';
756                foreach ($this->_patterns as $reg) {
757                        $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|';
758                }
759                $regexp = substr($regexp, 0, -1) . '/';
760                $regexp .= ($this->ignoreCase) ? 'i' : '';
761
762                $string = $this->_escape($string, $this->escapeChar);
763                $string = preg_replace_callback(
764                        $regexp,
765                        array(
766                                &$this,
767                                '_replacement'
768                        ),
769                        $string
770                );
771                $string = $this->_unescape($string, $this->escapeChar);
772
773                return preg_replace($this->DELETED, '', $string);
774        }
775
776        public function reset() {
777                // clear the patterns collection so that this object may be re-used
778                $this->_patterns = array();
779        }
780
781        // private
782        private $_escaped = array();  // escaped characters
783        private $_patterns = array(); // patterns stored by index
784
785        // create and add a new pattern to the patterns collection
786        private function _add() {
787                $arguments = func_get_args();
788                $this->_patterns[] = $arguments;
789        }
790
791        // this is the global replace function (it's quite complicated)
792        private function _replacement($arguments) {
793                if (empty($arguments)) return '';
794
795                $i = 1; $j = 0;
796                // loop through the patterns
797                while (isset($this->_patterns[$j])) {
798                        $pattern = $this->_patterns[$j++];
799                        // do we have a result?
800                        if (isset($arguments[$i]) && ($arguments[$i] != '')) {
801                                $replacement = $pattern[self::REPLACEMENT];
802
803                                if (is_array($replacement) && isset($replacement['fn'])) {
804
805                                        if (isset($replacement['data'])) $this->buffer = $replacement['data'];
806                                        return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
807
808                                } elseif (is_int($replacement)) {
809                                        return $arguments[$replacement + $i];
810
811                                }
812                                $delete = ($this->escapeChar == '' ||
813                                           strpos($arguments[$i], $this->escapeChar) === false)
814                                        ? '' : "\x01" . $arguments[$i] . "\x01";
815                                return $delete . $replacement;
816
817                        // skip over references to sub-expressions
818                        } else {
819                                $i += $pattern[self::LENGTH];
820                        }
821                }
822        }
823
824        private function _backReferences($match, $offset) {
825                $replacement = $this->buffer['replacement'];
826                $quote = $this->buffer['quote'];
827                $i = $this->buffer['length'];
828                while ($i) {
829                        $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
830                }
831                return $replacement;
832        }
833
834        private function _replace_name($match, $offset){
835                $length = strlen($match[$offset + 2]);
836                $start = $length - max($length - strlen($match[$offset + 3]), 0);
837                return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
838        }
839
840        private function _replace_encoded($match, $offset) {
841                return $this->buffer[$match[$offset]];
842        }
843
844
845        // php : we cannot pass additional data to preg_replace_callback,
846        // and we cannot use &$this in create_function, so let's go to lower level
847        private $buffer;
848
849        // encode escaped characters
850        private function _escape($string, $escapeChar) {
851                if ($escapeChar) {
852                        $this->buffer = $escapeChar;
853                        return preg_replace_callback(
854                                '/\\' . $escapeChar . '(.)' .'/',
855                                array(&$this, '_escapeBis'),
856                                $string
857                        );
858
859                } else {
860                        return $string;
861                }
862        }
863        private function _escapeBis($match) {
864                $this->_escaped[] = $match[1];
865                return $this->buffer;
866        }
867
868        // decode escaped characters
869        private function _unescape($string, $escapeChar) {
870                if ($escapeChar) {
871                        $regexp = '/'.'\\'.$escapeChar.'/';
872                        $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
873                        return preg_replace_callback
874                        (
875                                $regexp,
876                                array(&$this, '_unescapeBis'),
877                                $string
878                        );
879
880                } else {
881                        return $string;
882                }
883        }
884        private function _unescapeBis() {
885                if (!empty($this->_escaped[$this->buffer['i']])) {
886                         $temp = $this->_escaped[$this->buffer['i']];
887                } else {
888                        $temp = '';
889                }
890                $this->buffer['i']++;
891                return $this->buffer['escapeChar'] . $temp;
892        }
893
894        private function _internalEscape($string) {
895                return preg_replace($this->ESCAPE, '', $string);
896        }
897}
898
899?>
Note: See TracBrowser for help on using the repository browser.