8421860631fba20e0f851b0c8d038ed00e19816a
6 * let's reinvent the wheel one last time
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
11 * It is dual-licensed for any use under the GNU/GPL2 and MIT licenses,
14 * (c) 2009 Fil - fil@rezo.net
15 * Documentation & http://zzz.rezo.net/-TextWheel-
17 * Usage: $wheel = new TextWheel(); echo $wheel->text($text);
21 if (!defined('_ECRIRE_INC_VERSION')) return;
23 require_once dirname(__FILE__
)."/textwheelruleset.php";
27 protected static $subwheel = array();
29 protected $compiled = array();
33 * @param TextWheelRuleSet $ruleset
35 public function TextWheel($ruleset = null) {
36 $this->setRuleSet($ruleset);
41 * @param TextWheelRuleSet $ruleset
43 public function setRuleSet($ruleset){
44 if (!is_object($ruleset))
45 $ruleset = new TextWheelRuleSet ($ruleset);
46 $this->ruleset
= $ruleset;
50 * Apply all rules of RuleSet to a text
55 public function text($t) {
56 $rules = & $this->ruleset
->getRules();
57 ## apply each in order
58 foreach ($rules as $name => $rule) #php4+php5
60 $this->apply($rules[$name], $t);
62 #foreach ($this->rules as &$rule) #smarter &reference, but php5 only
63 # $this->apply($rule, $t);
67 private function export($x) {
68 return addcslashes(var_export($x, true), "\n\r\t");
71 public function compile($b = null) {
72 $rules = & $this->ruleset
->getRules();
74 ## apply each in order
78 foreach ($rules as $name => $rule)
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...
90 $r = "\t/* $name */\n";
93 $r .= "\t".'require_once '.TextWheel
::export($rule->require).';'."\n";
95 $r .= "\t".'if (strpos($t, '.TextWheel
::export($rule->if_str
).') === false)'."\n";
97 $r .= "\t".'if (stripos($t, '.TextWheel
::export($rule->if_stri
).') === false)'."\n";
99 $r .= "\t".'if (preg_match('.TextWheel
::export($rule->if_match
).', $t))'."\n";
101 if ($rule->func_replace
!== 'replace_identity') {
102 $fun = 'TextWheel::'.$rule->func_replace
;
104 case 'TextWheel::replace_all_cb':
105 $fun = $rule->replace
; # trim()...
107 case 'TextWheel::replace_preg':
108 $fun = 'preg_replace';
110 case 'TextWheel::replace_str':
111 $fun = 'str_replace';
113 case 'TextWheel::replace_preg_cb':
114 $fun = 'preg_replace_callback';
119 $r .= "\t".'$t = '.$fun.'('.TextWheel
::export($rule->match
).', '.TextWheel
::export($rule->replace
).', $t);'."\n";
124 $code = join ("\n", $comp);
125 $code = 'function '.$b.'($t) {' . "\n". $code . "\n\treturn \$t;\n}\n\n";
126 $code = join ("\n", $pre) . $code;
133 * Get an internal global subwheel
134 * read acces for annymous function only
139 public static function &getSubWheel($n){
140 return TextWheel
::$subwheel[$n];
144 * Create SubWheel (can be overriden in debug class)
145 * @param TextWheelRuleset $rules
148 protected function &createSubWheel(&$rules){
149 $tw = new TextWheel($rules);
154 * Initializing a rule a first call
155 * including file, creating function or wheel
158 * @param TextWheelRule $rule
160 protected function initRule(&$rule){
163 require_once $rule->require;
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
);
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;
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
))
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;
197 $rule->func_replace
= '';
198 if (isset($rule->replace
)) {
199 switch($rule->type
) {
201 $rule->func_replace
= 'replace_all';
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)
212 AND $c = array_map('strlen',$rule->replace
)
213 AND $c = array_unique($c)
217 $rule->match
= implode('',$rule->match
);
218 $rule->replace
= implode('',$rule->replace
);
219 $rule->func_replace
= 'replace_strtr';
223 $rule->func_replace
= 'replace_split';
224 $rule->match
= array($rule->match
, is_null($rule->glue
)?
$rule->match
:$rule->glue
);
228 $rule->func_replace
= 'replace_preg';
231 if ($rule->is_callback
)
232 $rule->func_replace
.= '_cb';
234 if (!method_exists("TextWheel", $rule->func_replace
)){
235 $rule->disabled
= true;
236 $rule->func_replace
= 'replace_identity';
242 * Apply a rule to a text
244 * @param TextWheelRule $rule
248 protected function apply(&$rule, &$t, &$count=null) {
253 if (isset($rule->if_chars
) AND (strpbrk($t, $rule->if_chars
) === false))
256 if (isset($rule->if_str
) AND strpos($t, $rule->if_str
) === false)
259 if (isset($rule->if_stri
) AND stripos($t, $rule->if_str
) === false)
262 if (isset($rule->if_match
) AND !preg_match($rule->if_match
, $t))
265 if (!isset($rule->func_replace
))
266 $this->initRule($rule);
268 $func = $rule->func_replace
;
269 TextWheel
::$func($rule->match
,$rule->replace
,$t,$count);
273 * No Replacement function
274 * fall back in case of unknown method for replacing
275 * should be called max once per rule
277 * @param mixed $match
278 * @param mixed $replace
282 protected static function replace_identity(&$match,&$replace,&$t,&$count){
286 * Static replacement of All text
287 * @param mixed $match
288 * @param mixed $replace
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);
303 * Call back replacement of All text
304 * @param mixed $match
305 * @param mixed $replace
309 protected static function replace_all_cb(&$match,&$replace,&$t,&$count){
314 * Static string replacement
316 * @param mixed $match
317 * @param mixed $replace
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);
327 * Fast Static string replacement one char to one char
329 * @param mixed $match
330 * @param mixed $replace
334 protected static function replace_strtr(&$match,&$replace,&$t,&$count){
335 $t = strtr( $t, $match, $replace);
339 * Callback string replacement
341 * @param mixed $match
342 * @param mixed $replace
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);
353 * Static Preg replacement
355 * @param mixed $match
356 * @param mixed $replace
360 protected static function replace_preg(&$match,&$replace,&$t,&$count){
361 $t = preg_replace($match, $replace, $t, -1, $count);
365 * Callback Preg replacement
366 * @param mixed $match
367 * @param mixed $replace
371 protected static function replace_preg_cb(&$match,&$replace,&$t,&$count){
372 $t = preg_replace_callback($match, $replace, $t, -1, $count);
377 * Static split replacement : invalid
378 * @param mixed $match
379 * @param mixed $replace
383 protected static function replace_split(&$match,&$replace,&$t,&$count){
384 throw new InvalidArgumentException('split rule always needs a callback function as replace');
388 * Callback split replacement
389 * @param array $match
390 * @param mixed $replace
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));
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
409 * Timer for profiling
411 * @staticvar int $time
416 protected function timer($t='rien', $raw = false) {
418 $a=time(); $b=microtime();
419 // microtime peut contenir les microsecondes et le temps
421 if (count($b)==2) $a = end($b); // plus precis !
423 if (!isset($time[$t])) {
426 $p = ($a +
$b - $time[$t]) * 1000;
432 $s = sprintf("%d ", $x = floor($p/1000));
435 return $s . sprintf("%.3f ms", $p);
440 * Apply all rules of RuleSet to a text
445 public function text($t) {
446 $rules = & $this->ruleset
->getRules();
447 ## apply each in order
448 foreach ($rules as $name => $rule) #php4+php5
451 $name .= ' '.$rule->match
;
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;
459 TextWheelDebug
::$u[$name] ++
; # nombre de fois utile
460 TextWheelDebug
::$tu[$name] +
= $v;
462 TextWheelDebug
::$tnu[$name] +
= $v;
466 #foreach ($this->rules as &$rule) #smarter &reference, but php5 only
467 # $this->apply($rule, $t);
472 * Ouputs data stored for profiling/debuging purposes
474 public static function outputDebug(){
475 if (isset(TextWheelDebug
::$t)) {
476 $time = array_flip(array_map('strval', TextWheelDebug
::$t));
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; }
491 <table class='sortable'>
492 <caption>Temps par rule</caption>
493 <thead><tr><th>temps (ms)</th><th>rule</th><th>application</th><th>t/u (ms)</th><th>t/n-u (ms)</th></tr></thead>\n";
495 foreach($time as $t => $r) {
496 $applications = intval(TextWheelDebug
::$u[$r]);
500 <td class='number strong'>".number_format(round($t*10)/10,1)."</td><td> ".htmlspecialchars($r)."</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>
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']);
517 # somme des temps des rules, ne tient pas compte des subwheels
518 echo "<p>temps total rules: ".round($total)." ms</p>\n";
523 public static function outputTotal($liste, $profondeur=0) {
525 foreach ($liste as $cause => $duree) {
526 if (is_array($duree)) {
527 TextWheelDebug
::outputTotal($duree, $profondeur+
1);
529 echo "<tr class='prof-$profondeur'>
530 <td class='number'><b>".intval($duree)."</b> ms</td>
531 <td class='name'>".htmlspecialchars($cause)."</td>
538 * Create SubWheel (can be overriden in debug class)
539 * @param TextWheelRuleset $rules
542 protected function &createSubWheel(&$rules){
543 return new TextWheelDebug($rules);
553 if (!function_exists('stripos')) {
554 function stripos($haystack, $needle) {
555 return strpos($haystack, stristr( $haystack, $needle ));
560 * approximation of strpbrk for php4
561 * return false if no char of $char_list is in $haystack
563 if (!function_exists('strpbrk')) {
564 function strpbrk($haystack, $char_list) {
565 $result = strcspn($haystack, $char_list);
566 if ($result != strlen($haystack)) {