[SPIP] ~2.1.12 -->2.1.25
[velocampus/web/www.git] / www / extensions / compresseur / lib / JavascriptPacker / class.JavaScriptPacker.php
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
72 class 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 // http://doc.spip.org/@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 // http://doc.spip.org/@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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_addParser
123 function _addParser($parser) {
124 $this->_parsers[] = $parser;
125 }
126
127 // zero encoding - just removal of white space and comments
128 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_insertFastDecode
378 function _insertFastDecode($match) {
379 return '{' . $this->buffer . ';';
380 }
381 // http://doc.spip.org/@_insertFastEncode
382 function _insertFastEncode($match) {
383 return '{$encode=' . $this->buffer . ';';
384 }
385
386 // mmm.. ..which one do i need ??
387 // http://doc.spip.org/@_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 // http://doc.spip.org/@_encode10
396 function _encode10($charCode) {
397 return $charCode;
398 }
399
400 // inherent base36 support
401 // characters: 0123456789abcdefghijklmnopqrstuvwxyz
402 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_safeRegExp
435 function _safeRegExp($string) {
436 return '/'.preg_replace('/\$/', '\\\$', $string).'/';
437 }
438
439 // http://doc.spip.org/@_encodePrivate
440 function _encodePrivate($charCode) {
441 return "_" . $charCode;
442 }
443
444 // protect characters used by the parser
445 // http://doc.spip.org/@_escape
446 function _escape($script) {
447 return preg_replace('/([\\\\\'])/', '\\\$1', $script);
448 }
449
450 // protect high-ascii characters already in the script
451 // http://doc.spip.org/@_escape95
452 function _escape95($script) {
453 return preg_replace_callback(
454 '/[\\xa1-\\xff]/',
455 array(&$this, '_escape95Bis'),
456 $script
457 );
458 }
459 // http://doc.spip.org/@_escape95Bis
460 function _escape95Bis($match) {
461 return '\x'.((string)dechex(ord($match)));
462 }
463
464
465 // http://doc.spip.org/@_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
561 class 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 // http://doc.spip.org/@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 // http://doc.spip.org/@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 // http://doc.spip.org/@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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_escapeBis
735 function _escapeBis($match) {
736 $this->_escaped[] = $match[1];
737 return $this->buffer;
738 }
739
740 // decode escaped characters
741 // http://doc.spip.org/@_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 // http://doc.spip.org/@_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 // http://doc.spip.org/@_internalEscape
769 function _internalEscape($string) {
770 return preg_replace($this->ESCAPE, '', $string);
771 }
772 }
773 ?>