text($text); * */ if (!defined('_ECRIRE_INC_VERSION')) { return; } require_once dirname(__FILE__) . "/textwheelruleset.php"; class TextWheel { protected $ruleset; protected static $subwheel = array(); // Experimental : projet de compilation PHP d'une wheel // pour generation d'un fichier php execute a la place de ->text() protected $compiled = array(); /** * Constructor * * @param TextWheelRuleSet $ruleset */ public function __construct($ruleset = null) { $this->setRuleSet($ruleset); } /** * Set RuleSet * * @param TextWheelRuleSet $ruleset */ public function setRuleSet($ruleset) { if (!is_object($ruleset)) { $ruleset = new TextWheelRuleSet($ruleset); } $this->ruleset = $ruleset; } /** * Apply all rules of RuleSet to a text * * @param string $t * @return string */ public function text($t) { $rules = &$this->ruleset->getRules(); ## apply each in order foreach ($rules as $name => $rule) #php4+php5 { $this->apply($rules[$name], $t); } #foreach ($this->rules as &$rule) #smarter &reference, but php5 only # $this->apply($rule, $t); return $t; } private function export($x) { return addcslashes(var_export($x, true), "\n\r\t"); } public function compile($b = null) { $rules = &$this->ruleset->getRules(); ## apply each in order $pre = array(); $comp = array(); foreach ($rules as $name => $rule) { $rule->name = $name; $this->initRule($rule); if ($rule->replace and $compiledEntry = $this->ruleCompiledEntryName($rule->replace) and isset($this->compiled[$compiledEntry]) and $fun = $this->compiled[$compiledEntry] ) { $pre[] = "\n###\n## $name\n###\n" . $fun; preg_match(',function (\w+), ', $fun, $r); $rule->compilereplace = "'".$r[1]."'"; # ne pas modifier ->replace sinon on casse l'execution... } $r = "\t/* $name */\n"; if ($rule->require) { $r .= "\t" . 'require_once ' . TextWheel::export($rule->require) . ';' . "\n"; } if ($rule->if_str) { $r .= "\t" . 'if (strpos($t, ' . TextWheel::export($rule->if_str) . ') === false)' . "\n"; } if ($rule->if_stri) { $r .= "\t" . 'if (stripos($t, ' . TextWheel::export($rule->if_stri) . ') === false)' . "\n"; } if ($rule->if_match) { $r .= "\t" . 'if (preg_match(' . TextWheel::export($rule->if_match) . ', $t))' . "\n"; } if ($rule->func_replace !== 'replace_identity') { $fun = 'TextWheel::' . $rule->func_replace; $call = ''; switch ($fun) { case 'TextWheel::replace_all_cb': if (is_string($rule->replace)) { $fun = $rule->replace; } elseif ($rule->compilereplace) { $fun = trim($rule->compilereplace, "'"); }; if ($fun) { $call = "\$t = $fun(\$t);"; } break; case 'TextWheel::replace_preg': $fun = 'preg_replace'; break; case 'TextWheel::replace_str': $fun = 'str_replace'; break; case 'TextWheel::replace_preg_cb': $fun = 'preg_replace_callback'; break; default: break; } if (!$call) { if (empty($rule->compilereplace)) { $rule->compilereplace = TextWheel::export($rule->replace); } $call = '$t = ' . $fun . '(' . TextWheel::export($rule->match) . ', ' . $rule->compilereplace . ', $t);'; } $r .= "\t$call\n"; } $comp[] = $r; } $code = join("\n", $comp); $code = 'function ' . $b . '($t) {' . "\n" . $code . "\n\treturn \$t;\n}\n\n"; $code = join("\n", $pre) . $code; return $code; } /** * Get an internal global subwheel * read acces for annymous function only * * @param int $n * @return TextWheel */ public static function &getSubWheel($n) { return TextWheel::$subwheel[$n]; } /** * Create SubWheel (can be overriden in debug class) * * @param TextWheelRuleset $rules * @return TextWheel */ protected function &createSubWheel(&$rules) { $tw = new TextWheel($rules); return $tw; } /** * @param $replace * @return string */ protected function ruleCompiledEntryName($replace) { if (is_array($replace)) { return serialize($replace); } elseif (is_object($replace)) { return get_class($replace) . ':' . spl_object_hash($replace); } return $replace; } /** * Initializing a rule a first call * including file, creating function or wheel * optimizing tests * * @param TextWheelRule $rule */ protected function initRule(&$rule) { # language specific if ($rule->require) { require_once $rule->require; } # optimization: strpos or stripos? if (isset($rule->if_str)) { if (strtolower($rule->if_str) !== strtoupper($rule->if_str)) { $rule->if_stri = $rule->if_str; $rule->if_str = null; } } if ($rule->create_replace) { // DEPRECATED : rule->create_replace, on ne peut rien faire de mieux ici // mais c'est voue a disparaitre $compile = $rule->replace . '($t)'; $rule->replace = create_function('$m', $rule->replace); $this->compiled[$this->ruleCompiledEntryName($rule->replace)] = $compile; $rule->create_replace = false; $rule->is_callback = true; } elseif ($rule->is_wheel) { $rule_number = count(TextWheel::$subwheel); TextWheel::$subwheel[] = $this->createSubWheel($rule->replace); $cname = 'compiled_' . str_replace('-', '_', $rule->name) . '_' . substr(md5(spl_object_hash($rule)),0,7); if ($rule->type == 'all' or $rule->type == 'str' or $rule->type == 'split' or !isset($rule->match)) { $rule->replace = function ($m) use ($rule_number) { return TextWheel::getSubWheel($rule_number)->text($m); }; $rule->compilereplace = "'$cname'"; } else { $pick_match = intval($rule->pick_match); $rule->replace = function ($m) use ($rule_number, $pick_match) { return TextWheel::getSubWheel($rule_number)->text($m[$pick_match]); }; $rule->compilereplace = 'function ($m) { return '.$cname.'($m['.$pick_match.']) }'; } $rule->is_wheel = false; $rule->is_callback = true; $compile = TextWheel::getSubWheel($rule_number)->compile($cname); $this->compiled[$this->ruleCompiledEntryName($rule->replace)] = $compile; } # optimization $rule->func_replace = ''; if (isset($rule->replace)) { switch ($rule->type) { case 'all': $rule->func_replace = 'replace_all'; break; case 'str': $rule->func_replace = 'replace_str'; // test if quicker strtr usable if (!$rule->is_callback and is_array($rule->match) and is_array($rule->replace) and $c = array_map('strlen', $rule->match) and $c = array_unique($c) and count($c) == 1 and reset($c) == 1 and $c = array_map('strlen', $rule->replace) and $c = array_unique($c) and count($c) == 1 and reset($c) == 1 ) { $rule->match = implode('', $rule->match); $rule->replace = implode('', $rule->replace); $rule->func_replace = 'replace_strtr'; } break; case 'split': $rule->func_replace = 'replace_split'; $rule->match = array($rule->match, is_null($rule->glue) ? $rule->match : $rule->glue); break; case 'preg': default: $rule->func_replace = 'replace_preg'; break; } if ($rule->is_callback) { $rule->func_replace .= '_cb'; } } if (!method_exists("TextWheel", $rule->func_replace)) { $rule->disabled = true; $rule->func_replace = 'replace_identity'; } # /end } /** * Apply a rule to a text * * @param TextWheelRule $rule * @param string $t * @param int $count */ protected function apply(&$rule, &$t, &$count = null) { if ($rule->disabled) { return; } if (isset($rule->if_chars) and (strpbrk($t, $rule->if_chars) === false)) { return; } if (isset($rule->if_match) and !preg_match($rule->if_match, $t)) { return; } // init rule before testing if_str / if_stri as they are optimized by initRule if (!isset($rule->func_replace)) { $this->initRule($rule); } if (isset($rule->if_str) and strpos($t, $rule->if_str) === false) { return; } if (isset($rule->if_stri) and stripos($t, $rule->if_stri) === false) { return; } $func = $rule->func_replace; TextWheel::$func($rule->match, $rule->replace, $t, $count); } /** * No Replacement function * fall back in case of unknown method for replacing * should be called max once per rule * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_identity(&$match, &$replace, &$t, &$count) { } /** * Static replacement of All text * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_all(&$match, &$replace, &$t, &$count) { # special case: replace $0 with $t # replace: "A$0B" will surround the string with A..B # replace: "$0$0" will repeat the string if (strpos($replace, '$0') !== false) { $t = str_replace('$0', $t, $replace); } else { $t = $replace; } } /** * Call back replacement of All text * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_all_cb(&$match, &$replace, &$t, &$count) { $t = $replace($t); } /** * Static string replacement * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_str(&$match, &$replace, &$t, &$count) { if (!is_string($match) or strpos($t, $match) !== false) { $t = str_replace($match, $replace, $t, $count); } } /** * Fast Static string replacement one char to one char * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_strtr(&$match, &$replace, &$t, &$count) { $t = strtr($t, $match, $replace); } /** * Callback string replacement * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_str_cb(&$match, &$replace, &$t, &$count) { if (strpos($t, $match) !== false) { if (count($b = explode($match, $t)) > 1) { $t = join($replace($match), $b); } } } /** * Static Preg replacement * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count * @throws Exception */ protected static function replace_preg(&$match, &$replace, &$t, &$count) { $t = preg_replace($match, $replace, $t, -1, $count); if (is_null($t)) { throw new Exception('Memory error, increase pcre.backtrack_limit in php.ini'); } } /** * Callback Preg replacement * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count * @throws Exception */ protected static function replace_preg_cb(&$match, &$replace, &$t, &$count) { $t = preg_replace_callback($match, $replace, $t, -1, $count); if (is_null($t)) { throw new Exception('Memory error, increase pcre.backtrack_limit in php.ini'); } } /** * Static split replacement : invalid * * @param mixed $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_split(&$match, &$replace, &$t, &$count) { throw new InvalidArgumentException('split rule always needs a callback function as replace'); } /** * Callback split replacement * * @param array $match * @param mixed $replace * @param string $t * @param int $count */ protected static function replace_split_cb(&$match, &$replace, &$t, &$count) { $a = explode($match[0], $t); $t = join($match[1], array_map($replace, $a)); } } class TextWheelDebug extends TextWheel { protected static $t; #tableaux des temps protected static $tu; #tableaux des temps (rules utilises) protected static $tnu; #tableaux des temps (rules non utilises) protected static $u; #compteur des rules utiles protected static $w; #compteur des rules appliques public static $total; /** * Timer for profiling * * @staticvar int $time * @param string $t * @param bool $raw * @return int/strinf */ protected function timer($t = 'rien', $raw = false) { static $time; $a = time(); $b = microtime(); // microtime peut contenir les microsecondes et le temps $b = explode(' ', $b); if (count($b) == 2) { $a = end($b); } // plus precis ! $b = reset($b); if (!isset($time[$t])) { $time[$t] = $a + $b; } else { $p = ($a + $b - $time[$t]) * 1000; unset($time[$t]); if ($raw) { return $p; } if ($p < 1000) { $s = ''; } else { $s = sprintf("%d ", $x = floor($p / 1000)); $p -= ($x * 1000); } return $s . sprintf("%.3f ms", $p); } } /** * Apply all rules of RuleSet to a text * * @param string $t * @return string */ public function text($t) { $rules = &$this->ruleset->getRules(); ## apply each in order foreach ($rules as $name => $rule) #php4+php5 { if (is_int($name)) { $name .= ' ' . $rule->match; } $this->timer($name); $b = $t; $this->apply($rule, $t); TextWheelDebug::$w[$name]++; # nombre de fois appliquee $v = $this->timer($name, true); # timer TextWheelDebug::$t[$name] += $v; if ($t !== $b) { TextWheelDebug::$u[$name]++; # nombre de fois utile TextWheelDebug::$tu[$name] += $v; } else { TextWheelDebug::$tnu[$name] += $v; } } #foreach ($this->rules as &$rule) #smarter &reference, but php5 only # $this->apply($rule, $t); return $t; } /** * Ouputs data stored for profiling/debuging purposes */ public static function outputDebug() { if (isset(TextWheelDebug::$t)) { $time = array_flip(array_map('strval', TextWheelDebug::$t)); krsort($time); echo "
\n"; $total = 0; foreach ($time as $t => $r) { $applications = intval(TextWheelDebug::$u[$r]); $total += $t; if (intval($t * 10)) { echo "" . $applications . "/" . intval(TextWheelDebug::$w[$r]) . " "; } } echo "
Temps par rule
temps (ms)ruleapplicationt/u (ms)t/n-u (ms)
" . number_format(round($t * 10) / 10, 1) . " " . spip_htmlspecialchars($r) . " " . ($applications ? number_format(round(TextWheelDebug::$tu[$r] / $applications * 100) / 100, 2) : "") . " " . (($nu = intval(TextWheelDebug::$w[$r]) - $applications) ? number_format(round(TextWheelDebug::$tnu[$r] / $nu * 100) / 100, 2) : "") . "
\n"; echo " \n"; ksort($GLOBALS['totaux']); TextWheelDebug::outputTotal($GLOBALS['totaux']); echo "
Temps total par rule
tempsrule
"; # somme des temps des rules, ne tient pas compte des subwheels echo "

temps total rules: " . round($total) . " ms

\n"; echo "
\n"; } } public static function outputTotal($liste, $profondeur = 0) { ksort($liste); foreach ($liste as $cause => $duree) { if (is_array($duree)) { TextWheelDebug::outputTotal($duree, $profondeur + 1); } else { echo " " . intval($duree) . " ms " . spip_htmlspecialchars($cause) . " \n"; } } } /** * Create SubWheel (can be overriden in debug class) * * @param TextWheelRuleset $rules * @return TextWheel */ protected function &createSubWheel(&$rules) { return new TextWheelDebug($rules); } } /** * stripos for php4 */ if (!function_exists('stripos')) { function stripos($haystack, $needle) { return strpos($haystack, stristr($haystack, $needle)); } } /** * approximation of strpbrk for php4 * return false if no char of $char_list is in $haystack */ if (!function_exists('strpbrk')) { function strpbrk($haystack, $char_list) { $result = strcspn($haystack, $char_list); if ($result != strlen($haystack)) { return $result; } return false; } }