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