PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins-dist/textwheel/engine/textwheel.php

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