[SPIP] ~maj 3.0.10 --> 3.0.14
[lhc/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->strpos)) {
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_str) AND strpos($t, $rule->if_str) === false)
257 return;
258
259 if (isset($rule->if_stri) AND stripos($t, $rule->if_str) === false)
260 return;
261
262 if (isset($rule->if_match) AND !preg_match($rule->if_match, $t))
263 return;
264
265 if (!isset($rule->func_replace))
266 $this->initRule($rule);
267
268 $func = $rule->func_replace;
269 TextWheel::$func($rule->match,$rule->replace,$t,$count);
270 }
271
272 /**
273 * No Replacement function
274 * fall back in case of unknown method for replacing
275 * should be called max once per rule
276 *
277 * @param mixed $match
278 * @param mixed $replace
279 * @param string $t
280 * @param int $count
281 */
282 protected static function replace_identity(&$match,&$replace,&$t,&$count){
283 }
284
285 /**
286 * Static replacement of All text
287 * @param mixed $match
288 * @param mixed $replace
289 * @param string $t
290 * @param int $count
291 */
292 protected static function replace_all(&$match,&$replace,&$t,&$count){
293 # special case: replace $0 with $t
294 # replace: "A$0B" will surround the string with A..B
295 # replace: "$0$0" will repeat the string
296 if (strpos($replace, '$0')!==FALSE)
297 $t = str_replace('$0', $t, $replace);
298 else
299 $t = $replace;
300 }
301
302 /**
303 * Call back replacement of All text
304 * @param mixed $match
305 * @param mixed $replace
306 * @param string $t
307 * @param int $count
308 */
309 protected static function replace_all_cb(&$match,&$replace,&$t,&$count){
310 $t = $replace($t);
311 }
312
313 /**
314 * Static string replacement
315 *
316 * @param mixed $match
317 * @param mixed $replace
318 * @param string $t
319 * @param int $count
320 */
321 protected static function replace_str(&$match,&$replace,&$t,&$count){
322 if (!is_string($match) OR strpos($t,$match)!==FALSE)
323 $t = str_replace($match, $replace, $t, $count);
324 }
325
326 /**
327 * Fast Static string replacement one char to one char
328 *
329 * @param mixed $match
330 * @param mixed $replace
331 * @param string $t
332 * @param int $count
333 */
334 protected static function replace_strtr(&$match,&$replace,&$t,&$count){
335 $t = strtr( $t, $match, $replace);
336 }
337
338 /**
339 * Callback string replacement
340 *
341 * @param mixed $match
342 * @param mixed $replace
343 * @param string $t
344 * @param int $count
345 */
346 protected static function replace_str_cb(&$match,&$replace,&$t,&$count){
347 if (strpos($t,$match)!==FALSE)
348 if (count($b = explode($match, $t)) > 1)
349 $t = join($replace($match), $b);
350 }
351
352 /**
353 * Static Preg replacement
354 *
355 * @param mixed $match
356 * @param mixed $replace
357 * @param string $t
358 * @param int $count
359 */
360 protected static function replace_preg(&$match,&$replace,&$t,&$count){
361 $t = preg_replace($match, $replace, $t, -1, $count);
362 }
363
364 /**
365 * Callback Preg replacement
366 * @param mixed $match
367 * @param mixed $replace
368 * @param string $t
369 * @param int $count
370 */
371 protected static function replace_preg_cb(&$match,&$replace,&$t,&$count){
372 $t = preg_replace_callback($match, $replace, $t, -1, $count);
373 }
374
375
376 /**
377 * Static split replacement : invalid
378 * @param mixed $match
379 * @param mixed $replace
380 * @param string $t
381 * @param int $count
382 */
383 protected static function replace_split(&$match,&$replace,&$t,&$count){
384 throw new InvalidArgumentException('split rule always needs a callback function as replace');
385 }
386
387 /**
388 * Callback split replacement
389 * @param array $match
390 * @param mixed $replace
391 * @param string $t
392 * @param int $count
393 */
394 protected static function replace_split_cb(&$match,&$replace,&$t,&$count){
395 $a = explode($match[0], $t);
396 $t = join($match[1], array_map($replace,$a));
397 }
398 }
399
400 class TextWheelDebug extends TextWheel {
401 static protected $t; #tableaux des temps
402 static protected $tu; #tableaux des temps (rules utilises)
403 static protected $tnu; #tableaux des temps (rules non utilises)
404 static protected $u; #compteur des rules utiles
405 static protected $w; #compteur des rules appliques
406 static $total;
407
408 /**
409 * Timer for profiling
410 *
411 * @staticvar int $time
412 * @param string $t
413 * @param bool $raw
414 * @return int/strinf
415 */
416 protected function timer($t='rien', $raw = false) {
417 static $time;
418 $a=time(); $b=microtime();
419 // microtime peut contenir les microsecondes et le temps
420 $b=explode(' ',$b);
421 if (count($b)==2) $a = end($b); // plus precis !
422 $b = reset($b);
423 if (!isset($time[$t])) {
424 $time[$t] = $a + $b;
425 } else {
426 $p = ($a + $b - $time[$t]) * 1000;
427 unset($time[$t]);
428 if ($raw) return $p;
429 if ($p < 1000)
430 $s = '';
431 else {
432 $s = sprintf("%d ", $x = floor($p/1000));
433 $p -= ($x*1000);
434 }
435 return $s . sprintf("%.3f ms", $p);
436 }
437 }
438
439 /**
440 * Apply all rules of RuleSet to a text
441 *
442 * @param string $t
443 * @return string
444 */
445 public function text($t) {
446 $rules = & $this->ruleset->getRules();
447 ## apply each in order
448 foreach ($rules as $name => $rule) #php4+php5
449 {
450 if (is_int($name))
451 $name .= ' '.$rule->match;
452 $this->timer($name);
453 $b = $t;
454 $this->apply($rule, $t);
455 TextWheelDebug::$w[$name] ++; # nombre de fois appliquee
456 $v = $this->timer($name, true); # timer
457 TextWheelDebug::$t[$name] += $v;
458 if ($t !== $b) {
459 TextWheelDebug::$u[$name] ++; # nombre de fois utile
460 TextWheelDebug::$tu[$name] += $v;
461 } else {
462 TextWheelDebug::$tnu[$name] += $v;
463 }
464
465 }
466 #foreach ($this->rules as &$rule) #smarter &reference, but php5 only
467 # $this->apply($rule, $t);
468 return $t;
469 }
470
471 /**
472 * Ouputs data stored for profiling/debuging purposes
473 */
474 public static function outputDebug(){
475 if (isset(TextWheelDebug::$t)) {
476 $time = array_flip(array_map('strval', TextWheelDebug::$t));
477 krsort($time);
478 echo "
479 <div class='textwheeldebug'>
480 <style type='text/css'>
481 .textwheeldebug table { margin:1em 0; }
482 .textwheeldebug th,.textwheeldebug td { padding-left: 15px }
483 .textwheeldebug .prof-0 .number { padding-right: 60px }
484 .textwheeldebug .prof-1 .number { padding-right: 30px }
485 .textwheeldebug .prof-1 .name { padding-left: 30px }
486 .textwheeldebug .prof-2 .name { padding-left: 60px }
487 .textwheeldebug .zero { color:orange; }
488 .textwheeldebug .number { text-align:right; }
489 .textwheeldebug .strong { font-weight:bold; }
490 </style>
491 <table class='sortable'>
492 <caption>Temps par rule</caption>
493 <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";
494 $total = 0;
495 foreach($time as $t => $r) {
496 $applications = intval(TextWheelDebug::$u[$r]);
497 $total += $t;
498 if(intval($t*10))
499 echo "<tr>
500 <td class='number strong'>".number_format(round($t*10)/10,1)."</td><td> ".spip_htmlspecialchars($r)."</td>
501 <td"
502 . (!$applications ? " class='zero'" : "")
503 .">".$applications."/".intval(TextWheelDebug::$w[$r])."</td>
504 <td class='number'>".($applications?number_format(round(TextWheelDebug::$tu[$r]/$applications*100)/100,2):"") ."</td>
505 <td class='number'>".(($nu = intval(TextWheelDebug::$w[$r])-$applications)?number_format(round(TextWheelDebug::$tnu[$r]/$nu*100)/100,2):"") ."</td>
506 </tr>";
507 }
508 echo "</table>\n";
509
510 echo "
511 <table>
512 <caption>Temps total par rule</caption>
513 <thead><tr><th>temps</th><th>rule</th></tr></thead>\n";
514 ksort($GLOBALS['totaux']);
515 TextWheelDebug::outputTotal($GLOBALS['totaux']);
516 echo "</table>";
517 # somme des temps des rules, ne tient pas compte des subwheels
518 echo "<p>temps total rules: ".round($total)."&nbsp;ms</p>\n";
519 echo "</div>\n";
520 }
521 }
522
523 public static function outputTotal($liste, $profondeur=0) {
524 ksort($liste);
525 foreach ($liste as $cause => $duree) {
526 if (is_array($duree)) {
527 TextWheelDebug::outputTotal($duree, $profondeur+1);
528 } else {
529 echo "<tr class='prof-$profondeur'>
530 <td class='number'><b>".intval($duree)."</b>&nbsp;ms</td>
531 <td class='name'>".spip_htmlspecialchars($cause)."</td>
532 </tr>\n";
533 }
534 }
535 }
536
537 /**
538 * Create SubWheel (can be overriden in debug class)
539 * @param TextWheelRuleset $rules
540 * @return TextWheel
541 */
542 protected function &createSubWheel(&$rules){
543 return new TextWheelDebug($rules);
544 }
545
546 }
547
548
549
550 /**
551 * stripos for php4
552 */
553 if (!function_exists('stripos')) {
554 function stripos($haystack, $needle) {
555 return strpos($haystack, stristr( $haystack, $needle ));
556 }
557 }
558
559 /**
560 * approximation of strpbrk for php4
561 * return false if no char of $char_list is in $haystack
562 */
563 if (!function_exists('strpbrk')) {
564 function strpbrk($haystack, $char_list) {
565 $result = strcspn($haystack, $char_list);
566 if ($result != strlen($haystack)) {
567 return $result;
568 }
569 return false;
570 }
571 }