source: trunk/instant_messenger/inc/Controller.class.php @ 287

Revision 287, 28.3 KB checked in by niltonneto, 16 years ago (diff)

Verificar Wiki/Trac? do módulo.

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