source: spip-zone/_core_/branches/spip-2.1/plugins/compresseur/lib/JavascriptPacker/class.JavaScriptPacker.php @ 104360

Last change on this file since 104360 was 104360, checked in by spip.franck@…, 3 years ago

code.spip est maintenant en https, donc j'ajoute le "s" à http

File size: 24.2 KB
Line 
1<?php
2/* 7 December 2006. version 1.0
3 *
4 * This is the php version of the Dean Edwards JavaScript 's Packer,
5 * Based on :
6 *
7 * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
8 * a multi-pattern parser.
9 * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
10 * value that is a function
11 *
12 * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
13 *
14 * License: http://creativecommons.org/licenses/LGPL/2.1/
15 *
16 * Ported to PHP by Nicolas Martin.
17 * modified by Mark Fabrizio Jr. to work with php 4
18 *
19 * ----------------------------------------------------------------------
20 *
21 * examples of usage :
22 * $myPacker = new JavaScriptPacker($script, 62, true, false);
23 * $packed = $myPacker->pack();
24 *
25 * or
26 *
27 * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
28 * $packed = $myPacker->pack();
29 *
30 * or (default values)
31 *
32 * $myPacker = new JavaScriptPacker($script);
33 * $packed = $myPacker->pack();
34 *
35 *
36 * params of the constructor :
37 * $script:       the JavaScript to pack, string.
38 * $encoding:     level of encoding, int or string :
39 *                0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
40 *                default: 62.
41 * $fastDecode:   include the fast decoder in the packed result, boolean.
42 *                default : true.
43 * $specialChars: if you are flagged your private and local variables
44 *                in the script, boolean.
45 *                default: false.
46 *
47 * The pack() method return the compressed JavasScript, as a string.
48 *
49 * see http://dean.edwards.name/packer/usage/ for more information.
50 *
51 * Notes :
52 * # [del]need PHP 5 . Tested with PHP 5.1.2[/del]
53 *   this is a modified version for PHP 4
54 *
55 * # The packed result may be different than with the Dean Edwards
56 *   version, but with the same length. The reason is that the PHP
57 *   function usort to sort array don't necessarily preserve the
58 *   original order of two equal member. The Javascript sort function
59 *   in fact preserve this order (but that's not require by the
60 *   ECMAScript standard). So the encoded keywords order can be
61 *   different in the two results.
62 *
63 * # Be careful with the 'High ASCII' Level encoding if you use
64 *   UTF-8 in your files...
65 */
66 
67 /*
68 * modified by Mark Fabrizio Jr. to work with php 4
69 */
70
71
72class JavaScriptPacker {
73        var $IGNORE = '$1';
74
75        // validate parameters
76        var $_script = '';
77        var $_encoding = 62;
78        var $_fastDecode = true;
79        var $_specialChars = false;
80       
81        var $LITERAL_ENCODING = array(
82                'None' => 0,
83                'Numeric' => 10,
84                'Normal' => 62,
85                'High ASCII' => 95
86        );
87       
88// https://code.spip.net/@JavaScriptPacker
89        function JavaScriptPacker($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
90        {
91                $this->_script = $_script . "\n";
92                if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
93                        $_encoding = $this->LITERAL_ENCODING[$_encoding];
94                $this->_encoding = min((int)$_encoding, 95);
95                $this->_fastDecode = $_fastDecode;     
96                $this->_specialChars = $_specialChars;
97        }
98       
99// https://code.spip.net/@pack
100        function pack() {
101                $this->_addParser('_basicCompression');
102                if ($this->_specialChars)
103                        $this->_addParser('_encodeSpecialChars');
104                if ($this->_encoding)
105                        $this->_addParser('_encodeKeywords');
106               
107                // go!
108                return $this->_pack($this->_script);
109        }
110       
111        // apply all parsing routines
112// https://code.spip.net/@_pack
113        function _pack($script) {
114                for ($i = 0; isset($this->_parsers[$i]); $i++) {
115                        $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
116                }
117                return $script;
118        }
119       
120        // keep a list of parsing functions, they'll be executed all at once
121        var $_parsers = array();
122// https://code.spip.net/@_addParser
123        function _addParser($parser) {
124                $this->_parsers[] = $parser;
125        }
126       
127        // zero encoding - just removal of white space and comments
128// https://code.spip.net/@_basicCompression
129        function _basicCompression($script) {
130                $parser = new ParseMaster();
131                // make safe
132                $parser->escapeChar = '\\';
133                // protect strings
134                $parser->add('/\'[^\'\\n\\r]*\'/',$this->IGNORE);
135                $parser->add('/"[^"\\n\\r]*"/', $this->IGNORE);
136                // remove comments
137                $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', "\n");
138                $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
139                // protect regular expressions
140                $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
141                $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', $this->IGNORE);
142                // remove: ;;; doSomething();
143                if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
144                // remove redundant semi-colons
145                $parser->add('/\\(;;\\)/', $this->IGNORE); // protect for (;;) loops
146                $parser->add('/;+\\s*([};])/', '$2');
147                // apply the above
148                $script = $parser->exec($script);
149
150                // remove white-space
151#               $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
152#               $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
153#               $parser->add('/\\s+/', '');
154# Modif fil@rezo.net pour conserver les \n
155                $parser->add('/(\\b|\\x24)[\\t ]+(\\b|\\x24)/', '$2 $3');
156                $parser->add('/([+\\-])[\\t ]+([+\\-])/', '$2 $3');
157                $parser->add('/[\\t ]+/', '');
158                $parser->add('/\\s+/', "\n");
159                // done
160                return $parser->exec($script);
161        }
162       
163// https://code.spip.net/@_encodeSpecialChars
164        function _encodeSpecialChars($script) {
165                $parser = new ParseMaster();
166                // replace: $name -> n, $$name -> na
167                $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
168                                         array('fn' => '_replace_name')
169                );
170                // replace: _name -> _0, double-underscore (__name) is ignored
171                $regexp = '/\\b_[A-Za-z\\d]\\w*/';
172                // build the word list
173                $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
174                // quick ref
175                $encoded = $keywords['encoded'];
176               
177                $parser->add($regexp,
178                        array(
179                                'fn' => '_replace_encoded',
180                                'data' => $encoded
181                        )
182                );
183                return $parser->exec($script);
184        }
185       
186// https://code.spip.net/@_encodeKeywords
187        function _encodeKeywords($script) {
188                // escape high-ascii values already in the script (i.e. in strings)
189                if ($this->_encoding > 62)
190                        $script = $this->_escape95($script);
191                // create the parser
192                $parser = new ParseMaster();
193                $encode = $this->_getEncoder($this->_encoding);
194                // for high-ascii, don't encode single character low-ascii
195                $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
196                // build the word list
197                $keywords = $this->_analyze($script, $regexp, $encode);
198                $encoded = $keywords['encoded'];
199               
200                // encode
201                $parser->add($regexp,
202                        array(
203                                'fn' => '_replace_encoded',
204                                'data' => $encoded
205                        )
206                );
207                if (empty($script)) return $script;
208                else {
209                        //$res = $parser->exec($script);
210                        //$res = $this->_bootStrap($res, $keywords);
211                        //return $res;
212                        return $this->_bootStrap($parser->exec($script), $keywords);
213                }
214        }
215       
216// https://code.spip.net/@_analyze
217        function _analyze($script, $regexp, $encode) {
218                // analyse
219                // retreive all words in the script
220                $all = array();
221                preg_match_all($regexp, $script, $all);
222                $_sorted = array(); // list of words sorted by frequency
223                $_encoded = array(); // dictionary of word->encoding
224                $_protected = array(); // instances of "protected" words
225                $all = $all[0]; // simulate the javascript comportement of global match
226                if (!empty($all)) {
227                        $unsorted = array(); // same list, not sorted
228                        $protected = array(); // "protected" words (dictionary of word->"word")
229                        $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
230                        $this->_count = array(); // word->count
231                        $i = count($all); $j = 0; //$word = null;
232                        // count the occurrences - used for sorting later
233                        do {
234                                --$i;
235                                $word = '$' . $all[$i];
236                                if (!isset($this->_count[$word])) {
237                                        $this->_count[$word] = 0;
238                                        $unsorted[$j] = $word;
239                                        // make a dictionary of all of the protected words in this script
240                                        //  these are words that might be mistaken for encoding
241                                        //if (is_string($encode) && method_exists($this, $encode))
242                                        $values[$j] = call_user_func(array(&$this, $encode), $j);
243                                        $protected['$' . $values[$j]] = $j++;
244                                }
245                                // increment the word counter
246                                $this->_count[$word]++;
247                        } while ($i > 0);
248                        // prepare to sort the word list, first we must protect
249                        //  words that are also used as codes. we assign them a code
250                        //  equivalent to the word itself.
251                        // e.g. if "do" falls within our encoding range
252                        //      then we store keywords["do"] = "do";
253                        // this avoids problems when decoding
254                        $i = count($unsorted);
255                        do {
256                                $word = $unsorted[--$i];
257                                if (isset($protected[$word]) /*!= null*/) {
258                                        $_sorted[$protected[$word]] = substr($word, 1);
259                                        $_protected[$protected[$word]] = true;
260                                        $this->_count[$word] = 0;
261                                }
262                        } while ($i);
263                       
264                        // sort the words by frequency
265                        // Note: the javascript and php version of sort can be different :
266                        // in php manual, usort :
267                        // " If two members compare as equal,
268                        // their order in the sorted array is undefined."
269                        // so the final packed script is different of the Dean's javascript version
270                        // but equivalent.
271                        // the ECMAscript standard does not guarantee this behaviour,
272                        // and thus not all browsers (e.g. Mozilla versions dating back to at
273                        // least 2003) respect this.
274                        usort($unsorted, array(&$this, '_sortWords'));
275                        $j = 0;
276                        // because there are "protected" words in the list
277                        //  we must add the sorted words around them
278                        do {
279                                if (!isset($_sorted[$i]))
280                                        $_sorted[$i] = substr($unsorted[$j++], 1);
281                                $_encoded[$_sorted[$i]] = $values[$i];
282                        } while (++$i < count($unsorted));
283                }
284                return array(
285                        'sorted'  => $_sorted,
286                        'encoded' => $_encoded,
287                        'protected' => $_protected);
288        }
289       
290        var $_count = array();
291// https://code.spip.net/@_sortWords
292        function _sortWords($match1, $match2) {
293                return $this->_count[$match2] - $this->_count[$match1];
294        }
295       
296        // build the boot function used for loading and decoding
297// https://code.spip.net/@_bootStrap
298        function _bootStrap($packed, $keywords) {
299                $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
300
301                // $packed: the packed script
302                $packed = "'" . $this->_escape($packed) . "'";
303
304                // $ascii: base for encoding
305                $ascii = min(count($keywords['sorted']), $this->_encoding);
306                if ($ascii == 0) $ascii = 1;
307
308                // $count: number of words contained in the script
309                $count = count($keywords['sorted']);
310
311                // $keywords: list of words contained in the script
312                foreach ($keywords['protected'] as $i=>$value) {
313                        $keywords['sorted'][$i] = '';
314                }
315                // convert from a string to an array
316                ksort($keywords['sorted']);
317                $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
318
319                $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
320                $encode = $this->_getJSFunction($encode);
321                $encode = preg_replace('/_encoding/','$ascii', $encode);
322                $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
323                $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
324
325                // $decode: code snippet to speed up decoding
326                if ($this->_fastDecode) {
327                        // create the decoder
328                        $decode = $this->_getJSFunction('_decodeBody');
329                        if ($this->_encoding > 62)
330                                $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
331                        // perform the encoding inline for lower ascii values
332                        elseif ($ascii < 36)
333                                $decode = preg_replace($ENCODE, $inline, $decode);
334                        // special case: when $count==0 there are no keywords. I want to keep
335                        //  the basic shape of the unpacking funcion so i'll frig the code...
336                        if ($count == 0)
337                                $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
338                }
339
340                // boot function
341                $unpack = $this->_getJSFunction('_unpack');
342                if ($this->_fastDecode) {
343                        // insert the decoder
344                        $this->buffer = $decode;
345                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
346                }
347                $unpack = preg_replace('/"/', "'", $unpack);
348                if ($this->_encoding > 62) { // high-ascii
349                        // get rid of the word-boundaries for regexp matches
350                        $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
351                }
352                if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
353                        // insert the encode function
354                        $this->buffer = $encode;
355                        $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
356                } else {
357                        // perform the encoding inline
358                        $unpack = preg_replace($ENCODE, $inline, $unpack);
359                }
360                // pack the boot function too
361                $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
362                $unpack = $unpackPacker->pack();
363               
364                // arguments
365                $params = array($packed, $ascii, $count, $keywords);
366                if ($this->_fastDecode) {
367                        $params[] = 0;
368                        $params[] = '{}';
369                }
370                $params = implode(',', $params);
371               
372                // the whole thing
373                return 'eval(' . $unpack . '(' . $params . "))\n";
374        }
375       
376        var $buffer;
377// https://code.spip.net/@_insertFastDecode
378        function _insertFastDecode($match) {
379                return '{' . $this->buffer . ';';
380        }
381// https://code.spip.net/@_insertFastEncode
382        function _insertFastEncode($match) {
383                return '{$encode=' . $this->buffer . ';';
384        }
385       
386        // mmm.. ..which one do i need ??
387// https://code.spip.net/@_getEncoder
388        function _getEncoder($ascii) {
389                return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
390                       '_encode95' : '_encode62' : '_encode36' : '_encode10';
391        }
392       
393        // zero encoding
394        // characters: 0123456789
395// https://code.spip.net/@_encode10
396        function _encode10($charCode) {
397                return $charCode;
398        }
399       
400        // inherent base36 support
401        // characters: 0123456789abcdefghijklmnopqrstuvwxyz
402// https://code.spip.net/@_encode36
403        function _encode36($charCode) {
404                return base_convert($charCode, 10, 36);
405        }
406       
407        // hitch a ride on base36 and add the upper case alpha characters
408        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
409// https://code.spip.net/@_encode62
410        function _encode62($charCode) {
411                $res = '';
412                if ($charCode >= $this->_encoding) {
413                        $res = $this->_encode62((int)($charCode / $this->_encoding));
414                }
415                $charCode = $charCode % $this->_encoding;
416               
417                if ($charCode > 35)
418                        return $res . chr($charCode + 29);
419                else
420                        return $res . base_convert($charCode, 10, 36);
421        }
422       
423        // use high-ascii values
424        // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
425// https://code.spip.net/@_encode95
426        function _encode95($charCode) {
427                $res = '';
428                if ($charCode >= $this->_encoding)
429                        $res = $this->_encode95($charCode / $this->_encoding);
430               
431                return $res . chr(($charCode % $this->_encoding) + 161);
432        }
433       
434// https://code.spip.net/@_safeRegExp
435        function _safeRegExp($string) {
436                return '/'.preg_replace('/\$/', '\\\$', $string).'/';
437        }
438       
439// https://code.spip.net/@_encodePrivate
440        function _encodePrivate($charCode) {
441                return "_" . $charCode;
442        }
443       
444        // protect characters used by the parser
445// https://code.spip.net/@_escape
446        function _escape($script) {
447                return preg_replace('/([\\\\\'])/', '\\\$1', $script);
448        }
449       
450        // protect high-ascii characters already in the script
451// https://code.spip.net/@_escape95
452        function _escape95($script) {
453                return preg_replace_callback(
454                        '/[\\xa1-\\xff]/',
455                        array(&$this, '_escape95Bis'),
456                        $script
457                );
458        }
459// https://code.spip.net/@_escape95Bis
460        function _escape95Bis($match) {
461                return '\x'.((string)dechex(ord($match)));
462        }
463       
464       
465// https://code.spip.net/@_getJSFunction
466        function _getJSFunction($aName) {
467                $func = 'JSFUNCTION'.$aName;
468                if (isset($this->$func)){
469                        return $this->$func;
470                }
471                else 
472                        return '';
473        }
474       
475        // JavaScript Functions used.
476        // Note : In Dean's version, these functions are converted
477        // with 'String(aFunctionName);'.
478        // This internal conversion complete the original code, ex :
479        // 'while (aBool) anAction();' is converted to
480        // 'while (aBool) { anAction(); }'.
481        // The JavaScript functions below are corrected.
482       
483        // unpacking function - this is the boot strap function
484        //  data extracted from this packing routine is passed to
485        //  this function when decoded in the target
486        // NOTE ! : without the ';' final.
487        var $JSFUNCTION_unpack = 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
488    while ($count--) {
489        if ($keywords[$count]) {
490            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
491        }
492    }
493    return $packed;
494}';
495/*
496'function($packed, $ascii, $count, $keywords, $encode, $decode) {
497    while ($count--)
498        if ($keywords[$count])
499            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
500    return $packed;
501}';
502*/
503       
504        // code-snippet inserted into the unpacker to speed up decoding
505        var $JSFUNCTION_decodeBody = '    if (!\'\'.replace(/^/, String)) {
506        // decode all the values we need
507        while ($count--) {
508            $decode[$encode($count)] = $keywords[$count] || $encode($count);
509        }
510        // global replacement function
511        $keywords = [function ($encoded) {return $decode[$encoded]}];
512        // generic match
513        $encode = function () {return \'\\\\w+\'};
514        // reset the loop counter -  we are now doing a global replace
515        $count = 1;
516    }
517';
518//};
519/*
520'       if (!\'\'.replace(/^/, String)) {
521        // decode all the values we need
522        while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
523        // global replacement function
524        $keywords = [function ($encoded) {return $decode[$encoded]}];
525        // generic match
526        $encode = function () {return\'\\\\w+\'};
527        // reset the loop counter -  we are now doing a global replace
528        $count = 1;
529    }';
530*/
531       
532         // zero encoding
533         // characters: 0123456789
534         var $JSFUNCTION_encode10 = 'function($charCode) {
535    return $charCode;
536}';//;';
537       
538         // inherent base36 support
539         // characters: 0123456789abcdefghijklmnopqrstuvwxyz
540         var $JSFUNCTION_encode36 = 'function($charCode) {
541    return $charCode.toString(36);
542}';//;';
543       
544        // hitch a ride on base36 and add the upper case alpha characters
545        // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
546        var $JSFUNCTION_encode62 = 'function($charCode) {
547    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
548    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
549}';
550       
551        // use high-ascii values
552        // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿À�?ÂÃÄÅÆÇÈÉÊËÌ�?Î�?�?ÑÒÓÔÕÖ×ØÙÚÛÜ�?Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
553        var $JSFUNCTION_encode95 = 'function($charCode) {
554    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
555        String.fromCharCode($charCode % _encoding + 161);
556}'; 
557       
558}
559
560
561class ParseMaster {
562        var $ignoreCase = false;
563        var $escapeChar = '';
564       
565        // constants
566        var $EXPRESSION = 0;
567        var $REPLACEMENT = 1;
568        var $LENGTH = 2;
569       
570        // used to determine nesting levels
571        var $GROUPS = '/\\(/';//g
572        var $SUB_REPLACE = '/\\$\\d/';
573        var $INDEXED = '/^\\$\\d+$/';
574        var $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
575        var $ESCAPE = '/\\\./';//g
576        var $QUOTE = '/\'/';
577        var $DELETED = '/\\x01[^\\x01]*\\x01/';//g
578       
579// https://code.spip.net/@add
580        function add($expression, $replacement = '') {
581                // count the number of sub-expressions
582                //  - add one because each pattern is itself a sub-expression
583                $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
584               
585                // treat only strings $replacement
586                if (is_string($replacement)) {
587                        // does the pattern deal with sub-expressions?
588                        if (preg_match($this->SUB_REPLACE, $replacement)) {
589                                // a simple lookup? (e.g. "$2")
590                                if (preg_match($this->INDEXED, $replacement)) {
591                                        // store the index (used for fast retrieval of matched strings)
592                                        $replacement = (int)(substr($replacement, 1)) - 1;
593                                } else { // a complicated lookup (e.g. "Hello $2 $1")
594                                        // build a function to do the lookup
595                                        $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
596                                                 ? '"' : "'";
597                                        $replacement = array(
598                                                'fn' => '_backReferences',
599                                                'data' => array(
600                                                        'replacement' => $replacement,
601                                                        'length' => $length,
602                                                        'quote' => $quote
603                                                )
604                                        );
605                                }
606                        }
607                }
608                // pass the modified arguments
609                if (!empty($expression)) $this->_add($expression, $replacement, $length);
610                else $this->_add('/^$/', $replacement, $length);
611        }
612       
613// https://code.spip.net/@exec
614        function exec($string) {
615                // execute the global replacement
616                $this->_escaped = array();
617               
618                // simulate the _patterns.toSTring of Dean
619                $regexp = '/';
620                foreach ($this->_patterns as $reg) {
621                        $regexp .= '(' . substr($reg[$this->EXPRESSION], 1, -1) . ')|';
622                }
623                $regexp = substr($regexp, 0, -1) . '/';
624                $regexp .= ($this->ignoreCase) ? 'i' : '';
625               
626                $string = $this->_escape($string, $this->escapeChar);
627                $string = preg_replace_callback(
628                        $regexp,
629                        array(
630                                &$this,
631                                '_replacement'
632                        ),
633                        $string
634                );
635                $string = $this->_unescape($string, $this->escapeChar);
636               
637                return preg_replace($this->DELETED, '', $string);
638        }
639               
640// https://code.spip.net/@reset
641        function reset() {
642                // clear the patterns collection so that this object may be re-used
643                $this->_patterns = array();
644        }
645
646        // private
647        var $_escaped = array();  // escaped characters
648        var $_patterns = array(); // patterns stored by index
649       
650        // create and add a new pattern to the patterns collection
651// https://code.spip.net/@_add
652        function _add() {
653                $arguments = func_get_args();
654                $this->_patterns[] = $arguments;
655        }
656       
657        // this is the global replace function (it's quite complicated)
658// https://code.spip.net/@_replacement
659        function _replacement($arguments) {
660                if (empty($arguments)) return '';
661               
662                $i = 1; $j = 0;
663                // loop through the patterns
664                while (isset($this->_patterns[$j])) {
665                        $pattern = $this->_patterns[$j++];
666                        // do we have a result?
667                        if (isset($arguments[$i]) && ($arguments[$i] != '')) {
668                                $replacement = $pattern[$this->REPLACEMENT];
669                               
670                                if (is_array($replacement) && isset($replacement['fn'])) {
671                                       
672                                        if (isset($replacement['data'])) $this->buffer = $replacement['data'];
673                                        return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
674                                       
675                                } elseif (is_int($replacement)) {
676                                        return $arguments[$replacement + $i];
677                               
678                                }
679                                $delete = ($this->escapeChar == '' ||
680                                           strpos($arguments[$i], $this->escapeChar) === false)
681                                        ? '' : "\x01" . $arguments[$i] . "\x01";
682                                return $delete . $replacement;
683                       
684                        // skip over references to sub-expressions
685                        } else {
686                                $i += $pattern[$this->LENGTH];
687                        }
688                }
689        }
690       
691// https://code.spip.net/@_backReferences
692        function _backReferences($match, $offset) {
693                $replacement = $this->buffer['replacement'];
694                $quote = $this->buffer['quote'];
695                $i = $this->buffer['length'];
696                while ($i) {
697                        $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
698                }
699                return $replacement;
700        }
701       
702// https://code.spip.net/@_replace_name
703        function _replace_name($match, $offset){
704                $length = strlen($match[$offset + 2]);
705                $start = $length - max($length - strlen($match[$offset + 3]), 0);
706                return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
707        }
708       
709// https://code.spip.net/@_replace_encoded
710        function _replace_encoded($match, $offset) {
711                return $this->buffer[$match[$offset]];
712        }
713       
714       
715        // php : we cannot pass additional data to preg_replace_callback,
716        // and we cannot use &$this in create_function, so let's go to lower level
717        var $buffer;
718       
719        // encode escaped characters
720// https://code.spip.net/@_escape
721        function _escape($string, $escapeChar) {
722                if ($escapeChar) {
723                        $this->buffer = $escapeChar;
724                        return preg_replace_callback(
725                                '/\\' . $escapeChar . '(.)' .'/',
726                                array(&$this, '_escapeBis'),
727                                $string
728                        );
729                       
730                } else {
731                        return $string;
732                }
733        }
734// https://code.spip.net/@_escapeBis
735        function _escapeBis($match) {
736                $this->_escaped[] = $match[1];
737                return $this->buffer;
738        }
739       
740        // decode escaped characters
741// https://code.spip.net/@_unescape
742        function _unescape($string, $escapeChar) {
743                if ($escapeChar) {
744                        $regexp = '/'.'\\'.$escapeChar.'/';
745                        $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
746                        return preg_replace_callback
747                        (
748                                $regexp,
749                                array(&$this, '_unescapeBis'),
750                                $string
751                        );
752                       
753                } else {
754                        return $string;
755                }
756        }
757// https://code.spip.net/@_unescapeBis
758        function _unescapeBis() {
759                if (!empty($this->_escaped[$this->buffer['i']])) {
760                         $temp = $this->_escaped[$this->buffer['i']];
761                } else {
762                        $temp = '';
763                }
764                $this->buffer['i']++;
765                return $this->buffer['escapeChar'] . $temp;
766        }
767       
768// https://code.spip.net/@_internalEscape
769        function _internalEscape($string) {
770                return preg_replace($this->ESCAPE, '', $string);
771        }
772}
773?>
Note: See TracBrowser for help on using the repository browser.