PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/libs/clearbricks/text.wiki2xhtml/class.wiki2xhtml.php

https://bitbucket.org/JcDenis/dotclear
PHP | 1378 lines | 970 code | 183 blank | 225 comment | 223 complexity | 29ee57bc61ffab48f30bd148b0ab7d06 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. ###########################################################
  3. # /!\ /!\ /!\ EDIT THIS FILE IN UTF8 /!\ /!\ /!\ #
  4. ###########################################################
  5. # ***** BEGIN LICENSE BLOCK *****
  6. # This file is part of Clearbricks.
  7. # Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
  8. # All rights reserved.
  9. #
  10. # Clearbricks is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Clearbricks is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with Clearbricks; if not, write to the Free Software
  22. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  23. #
  24. # ***** END LICENSE BLOCK *****
  25. #
  26. # Contributor(s):
  27. # Stephanie Booth
  28. # Mathieu Pillard
  29. # Christophe Bonijol
  30. # Jean-Charles Bagneris
  31. # Nicolas Chachereau
  32. # Jérôme Lipowicz
  33. # Franck Paul
  34. #
  35. # Version : 3.2.11
  36. # Release date : 2016-03-23
  37. # History :
  38. #
  39. # 3.2.11
  40. # Franck
  41. # => Added ) aside block support (HTML5 only)
  42. #
  43. # 3.2.10
  44. # Franck
  45. # => Added ""marked text"" support (HTML5 only)
  46. #
  47. # 3.2.9
  48. # Franck
  49. # => <a name="anchor"></a> est remplacé par <a id="anchor"></a> pour assurer la compatibilité avec HTML5
  50. #
  51. # 3.2.8
  52. # Franck
  53. # => <acronym> est remplacé par <abbr> pour assurer la compatibilité avec HTML5
  54. #
  55. # 3.2.7
  56. # Franck
  57. # => Les styles d'alignement des images sont modifiables via les options
  58. #
  59. # 3.2.6
  60. # => Added ``inline html`` support
  61. #
  62. # 3.2.5
  63. # => Changed longdesc by title in images
  64. #
  65. # 3.2.4
  66. # => Auto links
  67. # => Code cleanup
  68. #
  69. # 3.2.3
  70. # Olivier
  71. # => PHP5 Strict
  72. #
  73. # 3.2.2
  74. # Olivier
  75. # => Changement de la gestion des URL spéciales
  76. #
  77. # 3.2.1
  78. # Olivier
  79. # => Changement syntaxe des macros
  80. #
  81. # 3.2
  82. # Olivier
  83. # => Changement de fonctionnement des macros
  84. # => Passage de fonctions externes pour les macros et les mots wiki
  85. #
  86. # 3.1d
  87. # Jérôme Lipowicz
  88. # => antispam
  89. # Olivier
  90. # => centrage d'image
  91. #
  92. # 3.1c
  93. # Olivier
  94. # => Possibilité d'échaper les | dans les marqueurs avec \
  95. #
  96. # 3.1b
  97. # Nicolas Chachereau
  98. # => Changement de regexp pour la correction syntaxique
  99. #
  100. # 3.1a
  101. # Olivier
  102. # => Bug du Call-time pass-by-reference
  103. #
  104. # 3.1
  105. # Olivier
  106. # => Ajout des macros «««..»»»
  107. # => Ajout des blocs vides øøø
  108. # => Ajout du niveau de titre paramétrable
  109. # => Option de blocage du parseur dans les <pre>
  110. # => Titres au format setext (experimental, désactivé)
  111. #
  112. # 3.0
  113. # Olivier => Récriture du parseur inline, plus d'erreur XHTML
  114. # => Ajout d'une vérification d'intégrité pour les listes
  115. # => Les acronymes sont maintenant dans un fichier texte
  116. # => Ajout d'un tag images ((..)), del --..-- et ins ++..++
  117. # => Plus possible de faire des liens JS [lien|javascript:...]
  118. # => Ajout des notes de bas de page §§...§§
  119. # => Ajout des mots wiki
  120. #
  121. # 2.5
  122. # Olivier => Récriture du code, plus besoin du saut de ligne entre blocs !=
  123. #
  124. # 2.0
  125. # Stephanie => correction des PCRE et ajout de fonctionnalités
  126. # Mathieu => ajout du strip-tags, implementation des options, reconnaissance automatique d'url, etc.
  127. # Olivier => chagement de active_link en active_urls
  128. # => ajout des options pour les blocs
  129. # => intégration de l'aide dans le code, avec les options
  130. # => début de quelque chose pour la reconnaissance auto d'url (avec Mat)
  131. # TODO :
  132. # Mathieu => active_wiki_urls (modifier wikiParseUrl ?)
  133. # => active_auto_urls
  134. #
  135. # * => ajouter des options.
  136. # => trouver un meilleur nom pour active_link ? (pour le jour ou ca sera tellement une usine
  137. # a gaz que on generera des tags <link> :)
  138. #
  139. # Wiki2xhtml
  140. class wiki2xhtml
  141. {
  142. public $__version__ = '3.2.10';
  143. public $T;
  144. public $opt;
  145. public $line;
  146. public $acro_table;
  147. public $foot_notes;
  148. public $macros;
  149. public $functions;
  150. public $tags;
  151. public $open_tags;
  152. public $close_tags;
  153. public $custom_tags = array();
  154. public $all_tags;
  155. public $tag_pattern;
  156. public $escape_table;
  157. public $allowed_inline = array();
  158. function __construct()
  159. {
  160. # Mise en place des options
  161. $this->setOpt('active_title',1); # Activation des titres !!!
  162. $this->setOpt('active_setext_title',0); # Activation des titres setext (EXPERIMENTAL)
  163. $this->setOpt('active_hr',1); # Activation des <hr />
  164. $this->setOpt('active_lists',1); # Activation des listes
  165. $this->setOpt('active_quote',1); # Activation du <blockquote>
  166. $this->setOpt('active_pre',1); # Activation du <pre>
  167. $this->setOpt('active_empty',1); # Activation du bloc vide øøø
  168. $this->setOpt('active_auto_urls',0); # Activation de la reconnaissance d'url
  169. $this->setOpt('active_auto_br',0); # Activation du saut de ligne automatique (dans les paragraphes)
  170. $this->setOpt('active_antispam',1); # Activation de l'antispam pour les emails
  171. $this->setOpt('active_urls',1); # Activation des liens []
  172. $this->setOpt('active_auto_img',1); # Activation des images automatiques dans les liens []
  173. $this->setOpt('active_img',1); # Activation des images (())
  174. $this->setOpt('active_anchor',1); # Activation des ancres ~...~
  175. $this->setOpt('active_em',1); # Activation du <em> ''...''
  176. $this->setOpt('active_strong',1); # Activation du <strong> __...__
  177. $this->setOpt('active_br',1); # Activation du <br /> %%%
  178. $this->setOpt('active_q',1); # Activation du <q> {{...}}
  179. $this->setOpt('active_code',1); # Activation du <code> @@...@@
  180. $this->setOpt('active_acronym',1); # Activation des acronymes
  181. $this->setOpt('active_ins',1); # Activation des ins ++..++
  182. $this->setOpt('active_del',1); # Activation des del --..--
  183. $this->setOpt('active_inline_html',1); # Activation du HTML inline ``...``
  184. $this->setOpt('active_footnotes',1); # Activation des notes de bas de page
  185. $this->setOpt('active_wikiwords',0); # Activation des mots wiki
  186. $this->setOpt('active_macros',1); # Activation des macros /// ///
  187. $this->setOpt('active_mark',1); # Activation des mark ""..""
  188. $this->setOpt('active_aside',1); # Activation du <aside>
  189. $this->setOpt('parse_pre',1); # Parser l'intérieur de blocs <pre> ?
  190. $this->setOpt('active_fr_syntax',1); # Corrections syntaxe FR
  191. $this->setOpt('first_title_level',3); # Premier niveau de titre <h..>
  192. $this->setOpt('note_prefix','wiki-footnote');
  193. $this->setOpt('note_str','<div class="footnotes"><h4>Notes</h4>%s</div>');
  194. $this->setOpt('note_str_single','<div class="footnotes"><h4>Note</h4>%s</div>');
  195. $this->setOpt('words_pattern',
  196. '((?<![A-Za-z0-9])([A-Z][a-z]+){2,}(?![A-Za-z0-9]))');
  197. $this->setOpt('auto_url_pattern',
  198. '%(?<![\[\|])(http://|https://|ftp://|news:)([^"\s\)!]+)%msu');
  199. $this->setOpt('acronyms_file',dirname(__FILE__).'/acronyms.txt');
  200. $this->setOpt('img_style_left','float:left; margin: 0 1em 1em 0;');
  201. $this->setOpt('img_style_center','display:block; margin:0 auto;');
  202. $this->setOpt('img_style_right','float:right; margin: 0 0 1em 1em;');
  203. $this->acro_table = $this->__getAcronyms();
  204. $this->foot_notes = array();
  205. $this->functions = array();
  206. $this->macros = array();
  207. $this->registerFunction('macro:html',array($this,'__macroHTML'));
  208. }
  209. function setOpt($option, $value)
  210. {
  211. $this->opt[$option] = $value;
  212. }
  213. function setOpts($options)
  214. {
  215. if (!is_array($options)) {
  216. return;
  217. }
  218. foreach ($options as $k => $v) {
  219. $this->opt[$k] = $v;
  220. }
  221. }
  222. function getOpt($option)
  223. {
  224. return (!empty($this->opt[$option])) ? $this->opt[$option] : false;
  225. }
  226. function registerFunction($type,$name)
  227. {
  228. if (is_callable($name)) {
  229. $this->functions[$type] = $name;
  230. }
  231. }
  232. function transform($in)
  233. {
  234. # Initialisation des tags
  235. $this->__initTags();
  236. $this->foot_notes = array();
  237. # Récupération des macros
  238. if ($this->getOpt('active_macros')) {
  239. $in = preg_replace_callback('#^///(.*?)///($|\r)#ms',array($this,'__getMacro'),$in);
  240. }
  241. # Vérification du niveau de titre
  242. if ($this->getOpt('first_title_level') > 4) {
  243. $this->setOpt('first_title_level',4);
  244. }
  245. $res = str_replace("\r", '', $in);
  246. $escape_pattern = array();
  247. # traitement des titres à la setext
  248. if ($this->getOpt('active_setext_title') && $this->getOpt('active_title')) {
  249. $res = preg_replace('/^(.*)\n[=]{5,}$/m','!!!$1',$res);
  250. $res = preg_replace('/^(.*)\n[-]{5,}$/m','!!$1',$res);
  251. }
  252. # Transformation des mots Wiki
  253. if ($this->getOpt('active_wikiwords') && $this->getOpt('words_pattern')) {
  254. $res = preg_replace('/'.$this->getOpt('words_pattern').'/msu','¶¶¶$1¶¶¶',$res);
  255. }
  256. # Transformation des URLs automatiques
  257. if ($this->getOpt('active_auto_urls'))
  258. {
  259. $active_urls = $this->getOpt('active_urls');
  260. $this->setOpt('active_urls',1);
  261. $this->__initTags();
  262. # If urls are not active, escape URLs tags
  263. if (!$active_urls)
  264. {
  265. $res = preg_replace(
  266. '%(?<!\\\\)(['.preg_quote(implode('',$this->tags['a'])).'])%msU',
  267. '\\\$1',$res
  268. );
  269. }
  270. # Transforms urls while preserving tags.
  271. $tree = preg_split($this->tag_pattern,$res,-1,PREG_SPLIT_DELIM_CAPTURE);
  272. foreach ($tree as &$leaf) {
  273. $leaf = preg_replace($this->getOpt('auto_url_pattern'),'[$1$2]',$leaf);
  274. }
  275. unset($leaf);
  276. $res = implode($tree);
  277. }
  278. $this->T = explode("\n",$res);
  279. $this->T[] = '';
  280. # Parse les blocs
  281. $res = $this->__parseBlocks();
  282. # Line break
  283. if ($this->getOpt('active_br')) {
  284. $res = preg_replace('/(?<!\\\)%%%/', '<br />', $res);
  285. $escape_pattern[] = '%%%';
  286. }
  287. # Nettoyage des \s en trop
  288. $res = preg_replace('/([\s]+)(<\/p>|<\/li>|<\/pre>)/u', '$2', $res);
  289. $res = preg_replace('/(<li>)([\s]+)/u', '$1', $res);
  290. # On vire les escapes
  291. if (!empty($escape_pattern)) {
  292. $res = preg_replace('/\\\('.implode('|',$escape_pattern).')/','$1',$res);
  293. }
  294. # On vire les MotWiki qui sont resté (dans les url...)
  295. if ($this->getOpt('active_wikiwords') && $this->getOpt('words_pattern')) {
  296. $res = preg_replace('/¶¶¶'.$this->getOpt('words_pattern').'¶¶¶/msu','$1',$res);
  297. }
  298. # On remet les macros
  299. if ($this->getOpt('active_macros')) {
  300. $res = preg_replace_callback('/^##########MACRO#([0-9]+)#$/ms',array($this,'__putMacro'),$res);
  301. }
  302. # Auto line break dans les paragraphes
  303. if ($this->getOpt('active_auto_br')) {
  304. $res = preg_replace_callback('%(<p>)(.*?)(</p>)%msu',array($this,'__autoBR'),$res);
  305. }
  306. # On ajoute les notes
  307. if (count($this->foot_notes) > 0)
  308. {
  309. $res_notes = '';
  310. $i = 1;
  311. foreach ($this->foot_notes as $k => $v) {
  312. $res_notes .= "\n".'<p>[<a href="#rev-'.$k.'" id="'.$k.'">'.$i.'</a>] '.$v.'</p>';
  313. $i++;
  314. }
  315. $res .= sprintf("\n".(count($this->foot_notes) > 1 ? $this->getOpt('note_str') : $this->getOpt('note_str_single'))."\n",$res_notes);
  316. }
  317. return $res;
  318. }
  319. /* PRIVATE
  320. --------------------------------------------------- */
  321. function __initTags()
  322. {
  323. $tags = array(
  324. 'em' => array("''","''"),
  325. 'strong' => array('__','__'),
  326. 'abbr' => array('??','??'),
  327. 'a' => array('[',']'),
  328. 'img' => array('((','))'),
  329. 'q' => array('{{','}}'),
  330. 'code' => array('@@','@@'),
  331. 'anchor' => array('~','~'),
  332. 'del' => array('--','--'),
  333. 'ins' => array('++','++'),
  334. 'inline' => array('``','``'),
  335. 'note' => array('$$','$$'),
  336. 'word' => array('¶¶¶','¶¶¶'),
  337. 'mark' => array('""','""')
  338. );
  339. $this->linetags = array(
  340. 'empty' => 'øøø',
  341. 'title' => '([!]{1,4})',
  342. 'hr' => '[-]{4}[- ]',
  343. 'quote' => '(&gt;|;:)',
  344. 'lists' => '([*#]+)',
  345. 'pre' => '[ ]{1}',
  346. 'aside' => '[\)]{1}'
  347. );
  348. $this->tags = array_merge($tags,$this->custom_tags);
  349. # Suppression des tags selon les options
  350. if (!$this->getOpt('active_urls')) {
  351. unset($this->tags['a']);
  352. }
  353. if (!$this->getOpt('active_img')) {
  354. unset($this->tags['img']);
  355. }
  356. if (!$this->getOpt('active_anchor')) {
  357. unset($this->tags['anchor']);
  358. }
  359. if (!$this->getOpt('active_em')) {
  360. unset($this->tags['em']);
  361. }
  362. if (!$this->getOpt('active_strong')) {
  363. unset($this->tags['strong']);
  364. }
  365. if (!$this->getOpt('active_q')) {
  366. unset($this->tags['q']);
  367. }
  368. if (!$this->getOpt('active_code')) {
  369. unset($this->tags['code']);
  370. }
  371. if (!$this->getOpt('active_acronym')) {
  372. unset($this->tags['abbr']);
  373. }
  374. if (!$this->getOpt('active_ins')) {
  375. unset($this->tags['ins']);
  376. }
  377. if (!$this->getOpt('active_del')) {
  378. unset($this->tags['del']);
  379. }
  380. if (!$this->getOpt('active_inline_html')) {
  381. unset($this->tags['inline']);
  382. }
  383. if (!$this->getOpt('active_footnotes')) {
  384. unset($this->tags['note']);
  385. }
  386. if (!$this->getOpt('active_wikiwords')) {
  387. unset($this->tags['word']);
  388. }
  389. if (!$this->getOpt('active_mark')) {
  390. unset($this->tags['mark']);
  391. }
  392. # Suppression des tags de début de ligne selon les options
  393. if (!$this->getOpt('active_empty')) {
  394. unset($this->linetags['empty']);
  395. }
  396. if (!$this->getOpt('active_title')) {
  397. unset($this->linetags['title']);
  398. }
  399. if (!$this->getOpt('active_hr')) {
  400. unset($this->linetags['hr']);
  401. }
  402. if (!$this->getOpt('active_quote')) {
  403. unset($this->linetags['quote']);
  404. }
  405. if (!$this->getOpt('active_lists')) {
  406. unset($this->linetags['lists']);
  407. }
  408. if (!$this->getOpt('active_pre')) {
  409. unset($this->linetags['pre']);
  410. }
  411. if (!$this->getOpt('active_aside')) {
  412. unset($this->linetags['aside']);
  413. }
  414. $this->open_tags = $this->__getTags();
  415. $this->close_tags = $this->__getTags(false);
  416. $this->all_tags = $this->__getAllTags();
  417. $this->tag_pattern = $this->__getTagsPattern();
  418. $this->escape_table = $this->all_tags;
  419. array_walk($this->escape_table,create_function('&$a','$a = \'\\\\\'.$a;'));
  420. }
  421. function __getTags($open=true)
  422. {
  423. $res = array();
  424. foreach ($this->tags as $k => $v) {
  425. $res[$k] = ($open) ? $v[0] : $v[1];
  426. }
  427. return $res;
  428. }
  429. function __getAllTags()
  430. {
  431. $res = array();
  432. foreach ($this->tags as $v) {
  433. $res[] = $v[0];
  434. $res[] = $v[1];
  435. }
  436. return array_values(array_unique($res));
  437. }
  438. function __getTagsPattern($escape=false)
  439. {
  440. $res = $this->all_tags;
  441. array_walk($res,create_function('&$a','$a = preg_quote($a,"/");'));
  442. if (!$escape) {
  443. return '/(?<!\\\)('.implode('|',$res).')/';
  444. } else {
  445. return '('.implode('|',$res).')';
  446. }
  447. }
  448. /* Blocs
  449. --------------------------------------------------- */
  450. function __parseBlocks()
  451. {
  452. $mode = $type = null;
  453. $res = '';
  454. $max = count($this->T);
  455. for ($i=0; $i<$max; $i++)
  456. {
  457. $pre_mode = $mode;
  458. $pre_type = $type;
  459. $end = ($i+1 == $max);
  460. $line = $this->__getLine($i,$type,$mode);
  461. if ($type != 'pre' || $this->getOpt('parse_pre')) {
  462. $line = $this->__inlineWalk($line);
  463. }
  464. $res .= $this->__closeLine($type,$mode,$pre_type,$pre_mode);
  465. $res .= $this->__openLine($type,$mode,$pre_type,$pre_mode);
  466. # P dans les blockquotes et les asides
  467. if (($type == 'blockquote' || $type == 'aside') && trim($line) == '' && $pre_type == $type) {
  468. $res .= "</p>\n<p>";
  469. }
  470. # Correction de la syntaxe FR dans tous sauf pre et hr
  471. # Sur idée de Christophe Bonijol
  472. # Changement de regex (Nicolas Chachereau)
  473. if ($this->getOpt('active_fr_syntax') && $type != null && $type != 'pre' && $type != 'hr') {
  474. $line = preg_replace('%[ ]+([:?!;\x{00BB}](\s|$))%u','&nbsp;$1',$line);
  475. $line = preg_replace('%(\x{00AB})[ ]+%u','$1&nbsp;',$line);
  476. }
  477. $res .= $line;
  478. }
  479. return trim($res);
  480. }
  481. function __getLine($i,&$type,&$mode)
  482. {
  483. $pre_type = $type;
  484. $pre_mode = $mode;
  485. $type = $mode = null;
  486. if (empty($this->T[$i])) {
  487. return false;
  488. }
  489. $line = htmlspecialchars($this->T[$i],ENT_NOQUOTES);
  490. # Ligne vide
  491. if (empty($line))
  492. {
  493. $type = null;
  494. }
  495. elseif ($this->getOpt('active_empty') && preg_match('/^øøø(.*)$/',$line,$cap))
  496. {
  497. $type = null;
  498. $line = trim($cap[1]);
  499. }
  500. # Titre
  501. elseif ($this->getOpt('active_title') && preg_match('/^([!]{1,4})(.*)$/',$line,$cap))
  502. {
  503. $type = 'title';
  504. $mode = strlen($cap[1]);
  505. $line = trim($cap[2]);
  506. }
  507. # Ligne HR
  508. elseif ($this->getOpt('active_hr') && preg_match('/^[-]{4}[- ]*$/',$line))
  509. {
  510. $type = 'hr';
  511. $line = null;
  512. }
  513. # Blockquote
  514. elseif ($this->getOpt('active_quote') && preg_match('/^(&gt;|;:)(.*)$/',$line,$cap))
  515. {
  516. $type = 'blockquote';
  517. $line = trim($cap[2]);
  518. }
  519. # Liste
  520. elseif ($this->getOpt('active_lists') && preg_match('/^([*#]+)(.*)$/',$line,$cap))
  521. {
  522. $type = 'list';
  523. $mode = $cap[1];
  524. $valid = true;
  525. # Vérification d'intégrité
  526. $dl = ($type != $pre_type) ? 0 : strlen($pre_mode);
  527. $d = strlen($mode);
  528. $delta = $d-$dl;
  529. if ($delta < 0 && strpos($pre_mode,$mode) !== 0) {
  530. $valid = false;
  531. }
  532. if ($delta > 0 && $type == $pre_type && strpos($mode,$pre_mode) !== 0) {
  533. $valid = false;
  534. }
  535. if ($delta == 0 && $mode != $pre_mode) {
  536. $valid = false;
  537. }
  538. if ($delta > 1) {
  539. $valid = false;
  540. }
  541. if (!$valid) {
  542. $type = 'p';
  543. $mode = null;
  544. $line = '<br />'.$line;
  545. } else {
  546. $line = trim($cap[2]);
  547. }
  548. }
  549. # Préformaté
  550. elseif ($this->getOpt('active_pre') && preg_match('/^[ ]{1}(.*)$/',$line,$cap))
  551. {
  552. $type = 'pre';
  553. $line = $cap[1];
  554. }
  555. # Aside
  556. elseif ($this->getOpt('active_aside') && preg_match('/^[\)]{1}(.*)$/',$line,$cap))
  557. {
  558. $type = 'aside';
  559. $line = trim($cap[1]);
  560. }
  561. # Paragraphe
  562. else {
  563. $type = 'p';
  564. if (preg_match('/^\\\((?:('.implode('|',$this->linetags).')).*)$/',$line,$cap)) {
  565. $line = $cap[1];
  566. }
  567. $line = trim($line);
  568. }
  569. return $line;
  570. }
  571. function __openLine($type,$mode,$pre_type,$pre_mode)
  572. {
  573. $open = ($type != $pre_type);
  574. if ($open && $type == 'p')
  575. {
  576. return "\n<p>";
  577. }
  578. elseif ($open && $type == 'blockquote')
  579. {
  580. return "\n<blockquote><p>";
  581. }
  582. elseif (($open || $mode != $pre_mode) && $type == 'title')
  583. {
  584. $fl = $this->getOpt('first_title_level');
  585. $fl = $fl+3;
  586. $l = $fl-$mode;
  587. return "\n<h".($l).'>';
  588. }
  589. elseif ($open && $type == 'pre')
  590. {
  591. return "\n<pre>";
  592. }
  593. elseif ($open && $type == 'aside')
  594. {
  595. return "\n<aside><p>";
  596. }
  597. elseif ($open && $type == 'hr')
  598. {
  599. return "\n<hr />";
  600. }
  601. elseif ($type == 'list')
  602. {
  603. $dl = ($open) ? 0 : strlen($pre_mode);
  604. $d = strlen($mode);
  605. $delta = $d-$dl;
  606. $res = '';
  607. if($delta > 0) {
  608. if(substr($mode, -1, 1) == '*') {
  609. $res .= "<ul>\n";
  610. } else {
  611. $res .= "<ol>\n";
  612. }
  613. } elseif ($delta < 0) {
  614. $res .= "</li>\n";
  615. for($j = 0; $j < abs($delta); $j++) {
  616. if (substr($pre_mode,(0 - $j - 1), 1) == '*') {
  617. $res .= "</ul>\n</li>\n";
  618. } else {
  619. $res .= "</ol>\n</li>\n";
  620. }
  621. }
  622. } else {
  623. $res .= "</li>\n";
  624. }
  625. return $res."<li>";
  626. }
  627. else
  628. {
  629. return null;
  630. }
  631. }
  632. function __closeLine($type,$mode,$pre_type,$pre_mode)
  633. {
  634. $close = ($type != $pre_type);
  635. if ($close && $pre_type == 'p')
  636. {
  637. return "</p>\n";
  638. }
  639. elseif ($close && $pre_type == 'blockquote')
  640. {
  641. return "</p></blockquote>\n";
  642. }
  643. elseif (($close || $mode != $pre_mode) && $pre_type == 'title')
  644. {
  645. $fl = $this->getOpt('first_title_level');
  646. $fl = $fl+3;
  647. $l = $fl-$pre_mode;
  648. return '</h'.($l).">\n";
  649. }
  650. elseif ($close && $pre_type == 'pre')
  651. {
  652. return "</pre>\n";
  653. }
  654. elseif ($close && $pre_type == 'aside')
  655. {
  656. return "</p></aside>\n";
  657. }
  658. elseif ($close && $pre_type == 'list')
  659. {
  660. $res = '';
  661. for($j = 0; $j < strlen($pre_mode); $j++) {
  662. if(substr($pre_mode,(0 - $j - 1), 1) == '*') {
  663. $res .= "</li>\n</ul>\n";
  664. } else {
  665. $res .= "</li>\n</ol>\n";
  666. }
  667. }
  668. return $res;
  669. }
  670. else
  671. {
  672. return "\n";
  673. }
  674. }
  675. /* Inline
  676. --------------------------------------------------- */
  677. function __inlineWalk($str,$allow_only=null)
  678. {
  679. $tree = preg_split($this->tag_pattern,$str,-1,PREG_SPLIT_DELIM_CAPTURE);
  680. $res = '';
  681. for ($i=0; $i<count($tree); $i++)
  682. {
  683. $attr = '';
  684. if (in_array($tree[$i],array_values($this->open_tags)) &&
  685. ($allow_only == null || in_array(array_search($tree[$i],$this->open_tags),$allow_only)))
  686. {
  687. $tag = array_search($tree[$i],$this->open_tags);
  688. $tag_type = 'open';
  689. if (($tidy = $this->__makeTag($tree,$tag,$i,$i,$attr,$tag_type)) !== false)
  690. {
  691. if ($tag != '') {
  692. $res .= '<'.$tag.$attr;
  693. $res .= ($tag_type == 'open') ? '>' : ' />';
  694. }
  695. $res .= $tidy;
  696. }
  697. else
  698. {
  699. $res .= $tree[$i];
  700. }
  701. }
  702. else
  703. {
  704. $res .= $tree[$i];
  705. }
  706. }
  707. # Suppression des echappements
  708. $res = str_replace($this->escape_table,$this->all_tags,$res);
  709. return $res;
  710. }
  711. function __makeTag(&$tree,&$tag,$position,&$j,&$attr,&$type)
  712. {
  713. $res = '';
  714. $closed = false;
  715. $itag = $this->close_tags[$tag];
  716. # Recherche fermeture
  717. for ($i=$position+1;$i<count($tree);$i++)
  718. {
  719. if ($tree[$i] == $itag)
  720. {
  721. $closed = true;
  722. break;
  723. }
  724. }
  725. # Résultat
  726. if ($closed)
  727. {
  728. for ($i=$position+1;$i<count($tree);$i++)
  729. {
  730. if ($tree[$i] != $itag)
  731. {
  732. $res .= $tree[$i];
  733. }
  734. else
  735. {
  736. switch ($tag)
  737. {
  738. case 'a':
  739. $res = $this->__parseLink($res,$tag,$attr,$type);
  740. break;
  741. case 'img':
  742. $type = 'close';
  743. if (($res = $this->__parseImg($res,$attr,$tag))!==null) {
  744. $type = 'open';
  745. }
  746. break;
  747. case 'abbr':
  748. $res = $this->__parseAcronym($res,$attr);
  749. break;
  750. case 'q':
  751. $res = $this->__parseQ($res,$attr);
  752. break;
  753. case 'anchor':
  754. $tag = 'a';
  755. $res = $this->__parseAnchor($res,$attr);
  756. break;
  757. case 'note':
  758. $tag = '';
  759. $res = $this->__parseNote($res);
  760. break;
  761. case 'inline':
  762. $tag = '';
  763. $res = $this->__parseInlineHTML($res);
  764. break;
  765. case 'word':
  766. $res = $this->parseWikiWord($res,$tag,$attr,$type);
  767. break;
  768. default :
  769. $res = $this->__inlineWalk($res);
  770. break;
  771. }
  772. if ($type == 'open' && $tag != '') {
  773. $res .= '</'.$tag.'>';
  774. }
  775. $j = $i;
  776. break;
  777. }
  778. }
  779. return $res;
  780. }
  781. else
  782. {
  783. return false;
  784. }
  785. }
  786. function __splitTagsAttr($str)
  787. {
  788. $res = preg_split('/(?<!\\\)\|/',$str);
  789. foreach ($res as $k => $v) {
  790. $res[$k] = str_replace("\|",'|',$v);
  791. }
  792. return $res;
  793. }
  794. # Antispam (Jérôme Lipowicz)
  795. function __antiSpam($str)
  796. {
  797. $encoded = bin2hex($str);
  798. $encoded = chunk_split($encoded, 2, '%');
  799. $encoded = '%'.substr($encoded, 0, strlen($encoded) - 1);
  800. return $encoded;
  801. }
  802. function __parseLink($str,&$tag,&$attr,&$type)
  803. {
  804. $n_str = $this->__inlineWalk($str,array('abbr','img','em','strong'));
  805. $data = $this->__splitTagsAttr($n_str);
  806. $no_image = false;
  807. # Only URL in data
  808. if (count($data) == 1)
  809. {
  810. $url = trim($str);
  811. $content = strlen($url) > 35 ? substr($url,0,35).'...' : $url;
  812. $lang = '';
  813. $title = $url;
  814. }
  815. elseif (count($data) > 1)
  816. {
  817. $url = trim($data[1]);
  818. $content = $data[0];
  819. $lang = (!empty($data[2])) ? $this->protectAttr($data[2],true) : '';
  820. $title = (!empty($data[3])) ? $data[3] : '';
  821. $no_image = (!empty($data[4])) ? (boolean) $data[4] : false;
  822. }
  823. # Remplacement si URL spéciale
  824. $this->__specialUrls($url,$content,$lang,$title);
  825. # On vire les &nbsp; dans l'url
  826. $url = str_replace('&nbsp;',' ',$url);
  827. if (preg_match('/^(.+)[.](gif|jpg|jpeg|png)$/', $url) && !$no_image && $this->getOpt('active_auto_img'))
  828. {
  829. # On ajoute les dimensions de l'image si locale
  830. # Idée de Stephanie
  831. $img_size = null;
  832. if (!preg_match('#[a-zA-Z]+://#', $url)) {
  833. if (preg_match('#^/#',$url)) {
  834. $path_img = $_SERVER['DOCUMENT_ROOT'] . $url;
  835. } else {
  836. $path_img = $url;
  837. }
  838. $img_size = @getimagesize($path_img);
  839. }
  840. $attr = ' src="'.$this->protectAttr($this->protectUrls($url)).'"'.
  841. $attr .= (count($data) > 1) ? ' alt="'.$this->protectAttr($content).'"' : ' alt=""';
  842. $attr .= ($lang) ? ' lang="'.$lang.'"' : '';
  843. $attr .= ($title) ? ' title="'.$this->protectAttr($title).'"' : '';
  844. $attr .= (is_array($img_size)) ? ' '.$img_size[3] : '';
  845. $tag = 'img';
  846. $type = 'close';
  847. return null;
  848. }
  849. else
  850. {
  851. if ($this->getOpt('active_antispam') && preg_match('/^mailto:/',$url)) {
  852. $content = $content == $url ? preg_replace('%^mailto:%','',$content) : $content;
  853. $url = 'mailto:'.$this->__antiSpam(substr($url,7));
  854. }
  855. $attr = ' href="'.$this->protectAttr($this->protectUrls($url)).'"';
  856. $attr .= ($lang) ? ' hreflang="'.$lang.'"' : '';
  857. $attr .= ($title) ? ' title="'.$this->protectAttr($title).'"' : '';
  858. return $content;
  859. }
  860. }
  861. function __specialUrls(&$url,&$content,&$lang,&$title)
  862. {
  863. foreach ($this->functions as $k => $v)
  864. {
  865. if (strpos($k,'url:') === 0 && strpos($url,substr($k,4)) === 0)
  866. {
  867. $res = call_user_func($v,$url,$content);
  868. $url = isset($res['url']) ? $res['url'] : $url;
  869. $content = isset($res['content']) ? $res['content'] : $content;
  870. $lang = isset($res['lang']) ? $res['lang'] : $lang;
  871. $title = isset($res['title']) ? $res['title'] : $title;
  872. break;
  873. }
  874. }
  875. }
  876. function __parseImg($str,&$attr,&$tag)
  877. {
  878. $data = $this->__splitTagsAttr($str);
  879. $alt = '';
  880. $attr = '';
  881. $align_attr = '';
  882. $url = $data[0];
  883. if (!empty($data[1])) {
  884. $alt = $data[1];
  885. }
  886. $attr = ' src="'.$this->protectAttr($this->protectUrls($url)).'"';
  887. $attr .= ' alt="'.$this->protectAttr($alt).'"';
  888. if (!empty($data[2])) {
  889. $data[2] = strtoupper($data[2]);
  890. $style = '';
  891. if ($data[2] == 'G' || $data[2] == 'L') {
  892. $style = $this->getOpt('img_style_left');
  893. } elseif ($data[2] == 'D' || $data[2] == 'R') {
  894. $style = $this->getOpt('img_style_right');
  895. } elseif ($data[2] == 'C') {
  896. $style = $this->getOpt('img_style_center');
  897. }
  898. if ($style != '') {
  899. $align_attr = ' style="'.$style.'"';
  900. }
  901. }
  902. if (empty($data[4])) {
  903. $attr .= $align_attr;
  904. }
  905. if (!empty($data[3])) {
  906. $attr .= ' title="'.$this->protectAttr($data[3]).'"';
  907. }
  908. if (!empty($data[4])) {
  909. $tag = 'figure';
  910. $img = '<img'.$attr.' />';
  911. $img .= '<figcaption>'.$this->protectAttr($data[4]).'</figcaption>';
  912. $attr = $align_attr;
  913. return $img;
  914. }
  915. return null;
  916. }
  917. function __parseQ($str,&$attr)
  918. {
  919. $str = $this->__inlineWalk($str);
  920. $data = $this->__splitTagsAttr($str);
  921. $content = $data[0];
  922. $lang = (!empty($data[1])) ? $this->protectAttr($data[1],true) : '';
  923. $attr .= (!empty($lang)) ? ' lang="'.$lang.'"' : '';
  924. $attr .= (!empty($data[2])) ? ' cite="'.$this->protectAttr($this->protectUrls($data[2])).'"' : '';
  925. return $content;
  926. }
  927. function __parseAnchor($str,&$attr)
  928. {
  929. $name = $this->protectAttr($str,true);
  930. if ($name != '') {
  931. $attr = ' id="'.$name.'"';
  932. }
  933. return null;
  934. }
  935. function __parseNote($str)
  936. {
  937. $i = count($this->foot_notes)+1;
  938. $id = $this->getOpt('note_prefix').'-'.$i;
  939. $this->foot_notes[$id] = $this->__inlineWalk($str);
  940. return '<sup>\[<a href="#'.$id.'" id="rev-'.$id.'">'.$i.'</a>\]</sup>';
  941. }
  942. function __parseInlineHTML($str)
  943. {
  944. return str_replace(array('&gt;','&lt;'),array('>','<'),$str);
  945. }
  946. # Obtenir un acronyme
  947. function __parseAcronym($str,&$attr)
  948. {
  949. $data = $this->__splitTagsAttr($str);
  950. $acronym = $data[0];
  951. $title = $lang = '';
  952. if (count($data) > 1)
  953. {
  954. $title = $data[1];
  955. $lang = (!empty($data[2])) ? $this->protectAttr($data[2],true) : '';
  956. }
  957. if ($title == '' && !empty($this->acro_table[$acronym])) {
  958. $title = $this->acro_table[$acronym];
  959. }
  960. $attr = ($title) ? ' title="'.$this->protectAttr($title).'"' : '';
  961. $attr .= ($lang) ? ' lang="'.$lang.'"' : '';
  962. return $acronym;
  963. }
  964. # Définition des acronymes, dans le fichier acronyms.txt
  965. function __getAcronyms()
  966. {
  967. $file = $this->getOpt('acronyms_file');
  968. $res = array();
  969. if (file_exists($file))
  970. {
  971. if (($fc = @file($file)) !== false)
  972. {
  973. foreach ($fc as $v)
  974. {
  975. $v = trim($v);
  976. if ($v != '')
  977. {
  978. $p = strpos($v,':');
  979. $K = (string) trim(substr($v,0,$p));
  980. $V = (string) trim(substr($v,($p+1)));
  981. if ($K) {
  982. $res[$K] = $V;
  983. }
  984. }
  985. }
  986. }
  987. }
  988. return $res;
  989. }
  990. # Mots wiki (pour héritage)
  991. function parseWikiWord($str,&$tag,&$attr,&$type)
  992. {
  993. $tag = $attr = '';
  994. if (isset($this->functions['wikiword'])) {
  995. return call_user_func($this->functions['wikiword'],$str);
  996. }
  997. return $str;
  998. }
  999. /* Protection des attributs */
  1000. function protectAttr($str,$name=false)
  1001. {
  1002. if ($name && !preg_match('/^[A-Za-z][A-Za-z0-9_:.-]*$/',$str)) {
  1003. return '';
  1004. }
  1005. return str_replace(array("'",'"'),array('&#039;','&quot;'),$str);
  1006. }
  1007. /* Protection des urls */
  1008. function protectUrls($str)
  1009. {
  1010. if (preg_match('/^javascript:/',$str)) {
  1011. $str = '#';
  1012. }
  1013. return $str;
  1014. }
  1015. /* Auto BR */
  1016. function __autoBR($m)
  1017. {
  1018. return $m[1].str_replace("\n","<br />\n",$m[2]).$m[3];
  1019. }
  1020. /* Macro
  1021. --------------------------------------------------- */
  1022. function __getMacro($s)
  1023. {
  1024. $s = is_array($s) ? $s[1] : $s;
  1025. $this->macros[] = str_replace('\"','"',$s);
  1026. return 'øøø##########MACRO#'.(count($this->macros)-1).'#';
  1027. }
  1028. function __putMacro($id)
  1029. {
  1030. $id = is_array($id) ? (integer) $id[1] : (integer) $id;
  1031. if (isset($this->macros[$id]))
  1032. {
  1033. $content = str_replace("\r",'',$this->macros[$id]);
  1034. $c = explode("\n",$content);
  1035. # première ligne, premier mot
  1036. $fl = trim($c[0]);
  1037. $fw = $fl;
  1038. if ($fl) {
  1039. if (strpos($fl,' ') !== false) {
  1040. $fw = substr($fl,0,strpos($fl,' '));
  1041. }
  1042. $content = implode("\n",array_slice($c,1));
  1043. }
  1044. if ($c[0] == "\n") {
  1045. $content = implode("\n",array_slice($c,1));
  1046. }
  1047. if ($fw)
  1048. {
  1049. if (isset($this->functions['macro:'.$fw]))
  1050. {
  1051. return call_user_func($this->functions['macro:'.$fw],$content,$fl);
  1052. }
  1053. }
  1054. # Si on n'a rien pu faire, on retourne le tout sous
  1055. # forme de <pre>
  1056. return '<pre>'.htmlspecialchars($this->macros[$id]).'</pre>';
  1057. }
  1058. return null;
  1059. }
  1060. function __macroHTML($s)
  1061. {
  1062. return $s;
  1063. }
  1064. /* Aide et debug
  1065. --------------------------------------------------- */
  1066. function help()
  1067. {
  1068. $help['b'] = array();
  1069. $help['i'] = array();
  1070. $help['b'][] = 'Laisser une ligne vide entre chaque bloc <em>de même nature</em>.';
  1071. $help['b'][] = '<strong>Paragraphe</strong> : du texte et une ligne vide';
  1072. if ($this->getOpt('active_title')) {
  1073. $help['b'][] = '<strong>Titre</strong> : <code>!!!</code>, <code>!!</code>, '.
  1074. '<code>!</code> pour des titres plus ou moins importants';
  1075. }
  1076. if ($this->getOpt('active_hr')) {
  1077. $help['b'][] = '<strong>Trait horizontal</strong> : <code>----</code>';
  1078. }
  1079. if ($this->getOpt('active_lists')) {
  1080. $help['b'][] = '<strong>Liste</strong> : ligne débutant par <code>*</code> ou '.
  1081. '<code>#</code>. Il est possible de mélanger les listes '.
  1082. '(<code>*#*</code>) pour faire des listes de plusieurs niveaux. '.
  1083. 'Respecter le style de chaque niveau';
  1084. }
  1085. if ($this->getOpt('active_pre')) {
  1086. $help['b'][] = '<strong>Texte préformaté</strong> : espace devant chaque ligne de texte';
  1087. }
  1088. if ($this->getOpt('active_quote')) {
  1089. $help['b'][] = '<strong>Bloc de citation</strong> : <code>&gt;</code> ou '.
  1090. '<code>;:</code> devant chaque ligne de texte';
  1091. }
  1092. if ($this->getOpt('active_fr_syntax')) {
  1093. $help['i'][] = 'La correction de ponctuation est active. Un espace '.
  1094. 'insécable remplacera automatiquement tout espace '.
  1095. 'précédant les marques ";","?",":" et "!".';
  1096. }
  1097. if ($this->getOpt('active_em')) {
  1098. $help['i'][] = '<strong>Emphase</strong> : deux apostrophes <code>\'\'texte\'\'</code>';
  1099. }
  1100. if ($this->getOpt('active_strong')) {
  1101. $help['i'][] = '<strong>Forte emphase</strong> : deux soulignés <code>__texte__</code>';
  1102. }
  1103. if ($this->getOpt('active_br')) {
  1104. $help['i'][] = '<strong>Retour forcé à la ligne</strong> : <code>%%%</code>';
  1105. }
  1106. if ($this->getOpt('active_ins')) {
  1107. $help['i'][] = '<strong>Insertion</strong> : deux plus <code>++texte++</code>';
  1108. }
  1109. if ($this->getOpt('active_del')) {
  1110. $help['i'][] = '<strong>Suppression</strong> : deux moins <code>--texte--</code>';
  1111. }
  1112. if ($this->getOpt('active_mark')) {
  1113. $help['i'][] = '<mark>Texte marqué</mark> : deux guillemets <code>""texte""</code>';
  1114. }
  1115. if ($this->getOpt('active_urls')) {
  1116. $help['i'][] = '<strong>Lien</strong> : <code>[url]</code>, <code>[nom|url]</code>, '.
  1117. '<code>[nom|url|langue]</code> ou <code>[nom|url|langue|titre]</code>.';
  1118. $help['i'][] = '<strong>Image</strong> : comme un lien mais avec une extension d\'image.'.
  1119. '<br />Pour désactiver la reconnaissance d\'image mettez 0 dans un dernier '.
  1120. 'argument. Par exemple <code>[image|image.gif||0]</code> fera un lien vers l\'image au '.
  1121. 'lieu de l\'afficher.'.
  1122. '<br />Il est conseillé d\'utiliser la nouvelle syntaxe.';
  1123. }
  1124. if ($this->getOpt('active_img')) {
  1125. $help['i'][] = '<strong>Image</strong> (nouvelle syntaxe) : '.
  1126. '<code>((url|texte alternatif))</code>, '.
  1127. '<code>((url|texte alternatif|position))</code> ou '.
  1128. '<code>((url|texte alternatif|position|description longue))</code>. '.
  1129. '<br />La position peut prendre les valeur L ou G (gauche), R ou D (droite) ou C (centré).';
  1130. }
  1131. if ($this->getOpt('active_anchor')) {
  1132. $help['i'][] = '<strong>Ancre</strong> : <code>~ancre~</code>';
  1133. }
  1134. if ($this->getOpt('active_acronym')) {
  1135. $help['i'][] = '<strong>Acronyme</strong> : <code>??acronyme??</code> ou '.
  1136. '<code>??acronyme|titre??</code>';
  1137. }
  1138. if ($this->getOpt('active_q')) {
  1139. $help['i'][] = '<strong>Citation</strong> : <code>{{citation}}</code>, '.
  1140. '<code>{{citation|langue}}</code> ou <code>{{citation|langue|url}}</code>';
  1141. }
  1142. if ($this->getOpt('active_code')) {
  1143. $help['i'][] = '<strong>Code</strong> : <code>@@code ici@@</code>';
  1144. }
  1145. if ($this->getOpt('active_footnotes')) {
  1146. $help['i'][] = '<strong>Note de bas de page</strong> : <code>$$Corps de la note$$</code>';
  1147. }
  1148. $res = '<dl class="wikiHelp">';
  1149. $res .= '<dt>Blocs</dt><dd>';
  1150. if (count($help['b']) > 0)
  1151. {
  1152. $res .= '<ul><li>';
  1153. $res .= implode('&nbsp;;</li><li>', $help['b']);
  1154. $res .= '.</li></ul>';
  1155. }
  1156. $res .= '</dd>';
  1157. $res .= '<dt>Éléments en ligne</dt><dd>';
  1158. if (count($help['i']) > 0)
  1159. {
  1160. $res .= '<ul><li>';
  1161. $res .= implode('&nbsp;;</li><li>', $help['i']);
  1162. $res .= '.</li></ul>';
  1163. }
  1164. $res .= '</dd>';
  1165. $res .= '</dl>';
  1166. return $res;
  1167. }
  1168. /*
  1169. function debug()
  1170. {
  1171. $mode = $type = null;
  1172. $max = count($this->T);
  1173. $res =
  1174. '<table border="1">'.
  1175. '<tr><th>p-mode</th><th>p-type</th><th>mode</th><th>type</th><th>chaine</th></tr>';
  1176. for ($i=0; $i<$max; $i++)
  1177. {
  1178. $pre_mode = $mode;
  1179. $pre_type = $type;
  1180. $line = $this->__getLine($i,$type,$mode);
  1181. $res .=
  1182. '<tr><td>'.$pre_mode.'</td><td>'.$pre_type.'</td>'.
  1183. '<td>'.$mode.'</td><td>'.$type.'</td><td>'.$line.'</td></tr>';
  1184. }
  1185. $res .= '</table>';
  1186. return $res;
  1187. }
  1188. //*/
  1189. }