source: spip-zone/_core_/plugins/textwheel/engine/textwheel.php

Last change on this file was 111509, checked in by root, 9 months ago

create_function est deprecie en PHP 7.2, on reecrit donc l'optimisation des subwheels via une fonction anonyme de type Closure
Cela a pas mal d'impact sur la fonction compile() qu'on revise en consequence, meme si elle ne sert pas en vrai

Il reste le cas de la rule de type create_function qu'on ne peut pas traiter autrement, mais qui du coup devient depreciee aussi et disparaitra prochainement
(Elle n'est toutefois utilisee nulle part sur la zone)

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