[SPIP] ~maj v3.0.14-->v3.0.17
[ptitvelo/web/www.git] / www / plugins-dist / textwheel / engine / textwheel.php
1 <?php
2
3 /*
4 * TextWheel 0.1
5 *
6 * let's reinvent the wheel one last time
7 *
8 * This library of code is meant to be a fast and universal replacement
9 * for any and all text-processing systems written in PHP
10 *
11 * It is dual-licensed for any use under the GNU/GPL2 and MIT licenses,
12 * as suits you best
13 *
14 * (c) 2009 Fil - fil@rezo.net
15 * Documentation & http://zzz.rezo.net/-TextWheel-
16 *
17 * Usage: $wheel = new TextWheel(); echo $wheel->text($text);
18 *
19 */
20
21 if (!defined('_ECRIRE_INC_VERSION')) return;
22
23 require_once dirname(__FILE__)."/textwheelruleset.php";
24
25 class TextWheel {
26 protected $ruleset;
27 protected static $subwheel = array();
28
29 protected $compiled = array();
30
31 /**
32 * Constructor
33 * @param TextWheelRuleSet $ruleset
34 */
35 public function TextWheel($ruleset = null) {
36 $this->setRuleSet($ruleset);
37 }
38
39 /**
40 * Set RuleSet
41 * @param TextWheelRuleSet $ruleset
42 */
43 public function setRuleSet($ruleset){
44 if (!is_object($ruleset))
45 $ruleset = new TextWheelRuleSet ($ruleset);
46 $this->ruleset = $ruleset;
47 }
48
49 /**
50 * Apply all rules of RuleSet to a text
51 *
52 * @param string $t
53 * @return string
54 */
55 public function text($t) {
56 $rules = & $this->ruleset->getRules();
57 ## apply each in order
58 foreach ($rules as $name => $rule) #php4+php5
59 {
60 $this->apply($rules[$name], $t);
61 }
62 #foreach ($this->rules as &$rule) #smarter &reference, but php5 only
63 # $this->apply($rule, $t);
64 return $t;
65 }
66
67 private function export($x) {
68 return addcslashes(var_export($x, true), "\n\r\t");
69 }
70
71 public function compile($b = null) {
72 $rules = & $this->ruleset->getRules();
73
74 ## apply each in order
75 $pre = array();
76 $comp = array();
77
78 foreach ($rules as $name => $rule)
79 {
80 $rule->name = $name;
81 $this->initRule($rule);
82 if (is_string($rule->replace)
83 AND isset($this->compiled[$rule->replace])
84 AND $fun = $this->compiled[$rule->replace]) {
85 $pre[] = "\n###\n## $name\n###\n" . $fun;
86 preg_match(',function (\w+),', $fun, $r);
87 $rule->compilereplace = $r[1]; # ne pas modifier ->replace sinon on casse l'execution...
88 }
89
90 $r = "\t/* $name */\n";
91
92 if ($rule->require)
93 $r .= "\t".'require_once '.TextWheel::export($rule->require).';'."\n";
94 if ($rule->if_str)
95 $r .= "\t".'if (strpos($t, '.TextWheel::export($rule->if_str).') === false)'."\n";
96 if ($rule->if_stri)
97 $r .= "\t".'if (stripos($t, '.TextWheel::export($rule->if_stri).') === false)'."\n";
98 if ($rule->if_match)
99 $r .= "\t".'if (preg_match('.TextWheel::export($rule->if_match).', $t))'."\n";
100
101 if ($rule->func_replace !== 'replace_identity') {
102 $fun = 'TextWheel::'.$rule->func_replace;
103 switch($fun) {
104 case 'TextWheel::replace_all_cb':
105 $fun = $rule->replace; # trim()...
106 break;
107 case 'TextWheel::replace_preg':
108 $fun = 'preg_replace';
109 break;
110 case 'TextWheel::replace_str':
111 $fun = 'str_replace';
112 break;
113 case 'TextWheel::replace_preg_cb':
114 $fun = 'preg_replace_callback';
115 break;
116 default:
117 break;
118 }
119 $r .= "\t".'$t = '.$fun.'('.TextWheel::export($rule->match).', '.TextWheel::export($rule->replace).', $t);'."\n";
120 }
121
122 $comp[] = $r;
123 }
124 $code = join ("\n", $comp);
125 $code = 'function '.$b.'($t) {' . "\n". $code . "\n\treturn \$t;\n}\n\n";
126 $code = join ("\n", $pre) . $code;
127
128 return $code;
129 }
130
131
132 /**
133 * Get an internal global subwheel
134 * read acces for annymous function only
135 *
136 * @param int $n
137 * @return TextWheel
138 */
139 public static function &getSubWheel($n){
140 return TextWheel::$subwheel[$n];
141 }
142
143 /**
144 * Create SubWheel (can be overriden in debug class)
145 * @param TextWheelRuleset $rules
146 * @return TextWheel
147 */
148 protected function &createSubWheel(&$rules){
149 $tw = new TextWheel($rules);
150 return $tw;
151 }
152
153 /**
154 * Initializing a rule a first call
155 * including file, creating function or wheel
156 * optimizing tests
157 *
158 * @param TextWheelRule $rule
159 */
160 protected function initRule(&$rule){
161 # language specific
162 if ($rule->require){
163 require_once $rule->require;
164 }
165
166 # optimization: strpos or stripos?
167 if (isset($rule->if_str)) {
168 if (strtolower($rule->if_str) !== strtoupper($rule->if_str)) {
169 $rule->if_stri = $rule->if_str;
170 unset($rule->if_str);
171 }
172 }
173
174 if ($rule->create_replace){
175 $compile = $rule->replace.'($t)';
176 $rule->replace = create_function('$m', $rule->replace);
177 $this->compiled[$rule->replace] = $compile;
178 $rule->create_replace = false;
179 $rule->is_callback = true;
180 }
181 elseif ($rule->is_wheel){
182 $n = count(TextWheel::$subwheel);
183 TextWheel::$subwheel[] = $this->createSubWheel($rule->replace);
184 $var = '$m['.intval($rule->pick_match).']';
185 if ($rule->type=='all' OR $rule->type=='str' OR $rule->type=='split' OR !isset($rule->match))
186 $var = '$m';
187 $code = 'return TextWheel::getSubWheel('.$n.')->text('.$var.');';
188 $rule->replace = create_function('$m', $code);
189 $cname = 'compiled_'.str_replace('-','_', $rule->name);
190 $compile = TextWheel::getSubWheel($n)->compile($cname);
191 $this->compiled[$rule->replace] = $compile;
192 $rule->is_wheel = false;
193 $rule->is_callback = true;
194 }
195
196 # optimization
197 $rule->func_replace = '';
198 if (isset($rule->replace)) {
199 switch($rule->type) {
200 case 'all':
201 $rule->func_replace = 'replace_all';
202 break;
203 case 'str':
204 $rule->func_replace = 'replace_str';
205 // test if quicker strtr usable
206 if (!$rule->is_callback
207 AND is_array($rule->match) AND is_array($rule->replace)
208 AND $c = array_map('strlen',$rule->match)
209 AND $c = array_unique($c)
210 AND count($c)==1
211 AND reset($c)==1
212 AND $c = array_map('strlen',$rule->replace)
213 AND $c = array_unique($c)
214 AND count($c)==1
215 AND reset($c)==1
216 ){
217 $rule->match = implode('',$rule->match);
218 $rule->replace = implode('',$rule->replace);
219 $rule->func_replace = 'replace_strtr';
220 }
221 break;
222 case 'split':
223 $rule->func_replace = 'replace_split';
224 $rule->match = array($rule->match, is_null($rule->glue)?$rule->match:$rule->glue);
225 break;
226 case 'preg':
227 default:
228 $rule->func_replace = 'replace_preg';
229 break;
230 }
231 if ($rule->is_callback)
232 $rule->func_replace .= '_cb';
233 }
234 if (!method_exists("TextWheel", $rule->func_replace)){
235 $rule->disabled = true;
236 $rule->func_replace = 'replace_identity';
237 }
238 # /end
239 }
240
241 /**
242 * Apply a rule to a text
243 *
244 * @param TextWheelRule $rule
245 * @param string $t
246 * @param int $count
247 */
248 protected function apply(&$rule, &$t, &$count=null) {
249
250 if ($rule->disabled)
251 return;
252
253 if (isset($rule->if_chars) AND (strpbrk($t, $rule->if_chars) === false))
254 return;
255
256 if (isset($rule->if_match) AND !preg_match($rule->if_match, $t))
257 return;
258
259 // init rule before testing if_str / if_stri as they are optimized by initRule
260 if (!isset($rule->func_replace))
261 $this->initRule($rule);
262
263 if (isset($rule->if_str) AND strpos($t, $rule->if_str) === false)
264 return;
265
266 if (isset($rule->if_stri) AND stripos($t, $rule->if_stri) === false)
267 return;
268
269 $func = $rule->func_replace;
270 TextWheel::$func($rule->match,$rule->replace,$t,$count);
271 }
272
273 /**
274 * No Replacement function
275 * fall back in case of unknown method for replacing
276 * should be called max once per rule
277 *
278 * @param mixed $match
279 * @param mixed $replace
280 * @param string $t
281 * @param int $count
282 */
283 protected static function replace_identity(&$match,&$replace,&$t,&$count){
284 }
285
286 /**
287 * Static replacement of All text
288 * @param mixed $match
289 * @param mixed $replace
290 * @param string $t
291 * @param int $count
292 */
293 protected static function replace_all(&$match,&$replace,&$t,&$count){
294 # special case: replace $0 with $t
295 # replace: "A$0B" will surround the string with A..B
296 # replace: "$0$0" will repeat the string
297 if (strpos($replace, '$0')!==FALSE)
298 $t = str_replace('$0', $t, $replace);
299 else
300 $t = $replace;
301 }
302
303 /**
304 * Call back replacement of All text
305 * @param mixed $match
306 * @param mixed $replace
307 * @param string $t
308 * @param int $count
309 */
310 protected static function replace_all_cb(&$match,&$replace,&$t,&$count){
311 $t = $replace($t);
312 }
313
314 /**
315 * Static string replacement
316 *
317 * @param mixed $match
318 * @param mixed $replace
319 * @param string $t
320 * @param int $count
321 */
322 protected static function replace_str(&$match,&$replace,&$t,&$count){
323 if (!is_string($match) OR strpos($t,$match)!==FALSE)
324 $t = str_replace($match, $replace, $t, $count);
325 }
326
327 /**
328 * Fast Static string replacement one char to one char
329 *
330 * @param mixed $match
331 * @param mixed $replace
332 * @param string $t
333 * @param int $count
334 */
335 protected static function replace_strtr(&$match,&$replace,&$t,&$count){
336 $t = strtr( $t, $match, $replace);
337 }
338
339 /**
340 * Callback string replacement
341 *
342 * @param mixed $match
343 * @param mixed $replace
344 * @param string $t
345 * @param int $count
346 */
347 protected static function replace_str_cb(&$match,&$replace,&$t,&$count){
348 if (strpos($t,$match)!==FALSE)
349 if (count($b = explode($match, $t)) > 1)
350 $t = join($replace($match), $b);
351 }
352
353 /**
354 * Static Preg replacement
355 *
356 * @param mixed $match
357 * @param mixed $replace
358 * @param string $t
359 * @param int $count
360 */
361 protected static function replace_preg(&$match,&$replace,&$t,&$count){
362 $t = preg_replace($match, $replace, $t, -1, $count);
363 }
364
365 /**
366 * Callback Preg replacement
367 * @param mixed $match
368 * @param mixed $replace
369 * @param string $t
370 * @param int $count
371 */
372 protected static function replace_preg_cb(&$match,&$replace,&$t,&$count){
373 $t = preg_replace_callback($match, $replace, $t, -1, $count);
374 }
375
376
377 /**
378 * Static split replacement : invalid
379 * @param mixed $match
380 * @param mixed $replace
381 * @param string $t
382 * @param int $count
383 */
384 protected static function replace_split(&$match,&$replace,&$t,&$count){
385 throw new InvalidArgumentException('split rule always needs a callback function as replace');
386 }
387
388 /**
389 * Callback split replacement
390 * @param array $match
391 * @param mixed $replace
392 * @param string $t
393 * @param int $count
394 */
395 protected static function replace_split_cb(&$match,&$replace,&$t,&$count){
396 $a = explode($match[0], $t);
397 $t = join($match[1], array_map($replace,$a));
398 }
399 }
400
401 class TextWheelDebug extends TextWheel {
402 static protected $t; #tableaux des temps
403 static protected $tu; #tableaux des temps (rules utilises)
404 static protected $tnu; #tableaux des temps (rules non utilises)
405 static protected $u; #compteur des rules utiles
406 static protected $w; #compteur des rules appliques
407 static $total;
408
409 /**
410 * Timer for profiling
411 *
412 * @staticvar int $time
413 * @param string $t
414 * @param bool $raw
415 * @return int/strinf
416 */
417 protected function timer($t='rien', $raw = false) {
418 static $time;
419 $a=time(); $b=microtime();
420 // microtime peut contenir les microsecondes et le temps
421 $b=explode(' ',$b);
422 if (count($b)==2) $a = end($b); // plus precis !
423 $b = reset($b);
424 if (!isset($time[$t])) {
425 $time[$t] = $a + $b;
426 } else {
427 $p = ($a + $b - $time[$t]) * 1000;
428 unset($time[$t]);
429 if ($raw) return $p;
430 if ($p < 1000)
431 $s = '';
432 else {
433 $s = sprintf("%d ", $x = floor($p/1000));
434 $p -= ($x*1000);
435 }
436 return $s . sprintf("%.3f ms", $p);
437 }
438 }
439
440 /**
441 * Apply all rules of RuleSet to a text
442 *
443 * @param string $t
444 * @return string
445 */
446 public function text($t) {
447 $rules = & $this->ruleset->getRules();
448 ## apply each in order
449 foreach ($rules as $name => $rule) #php4+php5
450 {
451 if (is_int($name))
452 $name .= ' '.$rule->match;
453 $this->timer($name);
454 $b = $t;
455 $this->apply($rule, $t);
456 TextWheelDebug::$w[$name] ++; # nombre de fois appliquee
457 $v = $this->timer($name, true); # timer
458 TextWheelDebug::$t[$name] += $v;
459 if ($t !== $b) {
460 TextWheelDebug::$u[$name] ++; # nombre de fois utile
461 TextWheelDebug::$tu[$name] += $v;
462 } else {
463 TextWheelDebug::$tnu[$name] += $v;
464 }
465
466 }
467 #foreach ($this->rules as &$rule) #smarter &reference, but php5 only
468 # $this->apply($rule, $t);
469 return $t;
470 }
471
472 /**
473 * Ouputs data stored for profiling/debuging purposes
474 */
475 public static function outputDebug(){
476 if (isset(TextWheelDebug::$t)) {
477 $time = array_flip(array_map('strval', TextWheelDebug::$t));
478 krsort($time);
479 echo "
480 <div class='textwheeldebug'>
481 <style type='text/css'>
482 .textwheeldebug table { margin:1em 0; }
483 .textwheeldebug th,.textwheeldebug td { padding-left: 15px }
484 .textwheeldebug .prof-0 .number { padding-right: 60px }
485 .textwheeldebug .prof-1 .number { padding-right: 30px }
486 .textwheeldebug .prof-1 .name { padding-left: 30px }
487 .textwheeldebug .prof-2 .name { padding-left: 60px }
488 .textwheeldebug .zero { color:orange; }
489 .textwheeldebug .number { text-align:right; }
490 .textwheeldebug .strong { font-weight:bold; }
491 </style>
492 <table class='sortable'>
493 <caption>Temps par rule</caption>
494 <thead><tr><th>temps&nbsp;(ms)</th><th>rule</th><th>application</th><th>t/u&nbsp;(ms)</th><th>t/n-u&nbsp;(ms)</th></tr></thead>\n";
495 $total = 0;
496 foreach($time as $t => $r) {
497 $applications = intval(TextWheelDebug::$u[$r]);
498 $total += $t;
499 if(intval($t*10))
500 echo "<tr>
501 <td class='number strong'>".number_format(round($t*10)/10,1)."</td><td> ".spip_htmlspecialchars($r)."</td>
502 <td"
503 . (!$applications ? " class='zero'" : "")
504 .">".$applications."/".intval(TextWheelDebug::$w[$r])."</td>
505 <td class='number'>".($applications?number_format(round(TextWheelDebug::$tu[$r]/$applications*100)/100,2):"") ."</td>
506 <td class='number'>".(($nu = intval(TextWheelDebug::$w[$r])-$applications)?number_format(round(TextWheelDebug::$tnu[$r]/$nu*100)/100,2):"") ."</td>
507 </tr>";
508 }
509 echo "</table>\n";
510
511 echo "
512 <table>
513 <caption>Temps total par rule</caption>
514 <thead><tr><th>temps</th><th>rule</th></tr></thead>\n";
515 ksort($GLOBALS['totaux']);
516 TextWheelDebug::outputTotal($GLOBALS['totaux']);
517 echo "</table>";
518 # somme des temps des rules, ne tient pas compte des subwheels
519 echo "<p>temps total rules: ".round($total)."&nbsp;ms</p>\n";
520 echo "</div>\n";
521 }
522 }
523
524 public static function outputTotal($liste, $profondeur=0) {
525 ksort($liste);
526 foreach ($liste as $cause => $duree) {
527 if (is_array($duree)) {
528 TextWheelDebug::outputTotal($duree, $profondeur+1);
529 } else {
530 echo "<tr class='prof-$profondeur'>
531 <td class='number'><b>".intval($duree)."</b>&nbsp;ms</td>
532 <td class='name'>".spip_htmlspecialchars($cause)."</td>
533 </tr>\n";
534 }
535 }
536 }
537
538 /**
539 * Create SubWheel (can be overriden in debug class)
540 * @param TextWheelRuleset $rules
541 * @return TextWheel
542 */
543 protected function &createSubWheel(&$rules){
544 return new TextWheelDebug($rules);
545 }
546
547 }
548
549
550
551 /**
552 * stripos for php4
553 */
554 if (!function_exists('stripos')) {
555 function stripos($haystack, $needle) {
556 return strpos($haystack, stristr( $haystack, $needle ));
557 }
558 }
559
560 /**
561 * approximation of strpbrk for php4
562 * return false if no char of $char_list is in $haystack
563 */
564 if (!function_exists('strpbrk')) {
565 function strpbrk($haystack, $char_list) {
566 $result = strcspn($haystack, $char_list);
567 if ($result != strlen($haystack)) {
568 return $result;
569 }
570 return false;
571 }
572 }