3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2020 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Fonctions utilitaires du plugin révisions
16 * @package SPIP\Revisions\Diff
19 if (!defined("_ECRIRE_INC_VERSION")) {
24 // LCS (Longest Common Subsequence) en deux versions
25 // (ref: http://web.archive.org/web/20071206224029/http://www2.toki.or.id/book/AlgDesignManual/BOOK/BOOK5/NODE208.HTM#SECTION03178000000000000000)
28 * Calcule un LCS (Longest Common Subsequence) simplifié
30 * Chaque chaîne est une permutation de l'autre et on passe en paramètre
31 * un des deux tableaux de correspondances
37 function lcs_opt($s) {
43 $paths_ymin = array();
46 // Insertion des points
49 foreach ($s as $y => $c) {
52 } # eviter l'explosion memoire des tres gros diff
53 for ($len = $max_len; $len > 0; $len--) {
54 if ($paths_ymin[$len] < $y) {
55 $paths_ymin[$len +
1] = $y;
56 $paths[$len +
1] = $paths[$len];
57 $paths[$len +
1][$y] = $c;
63 $paths[1] = array($y => $c);
65 if ($len +
1 > $max_len) {
70 return $paths[$max_len];
74 * Calcule un LCS (Longest Common Subsequence)
76 * Les deux chaînes n'ont pas été traitées au préalable par la fonction d'appariement
83 function lcs($s, $t) {
87 return array(0 => array(), 1 => array());
90 $paths_ymin = array();
92 $s_pos = $t_pos = array();
94 // Insertion des points
95 foreach ($t as $y => $c) {
96 $t_pos[trim($c)][] = $y;
99 foreach ($s as $x => $c) {
101 if (!isset($t_pos[$c])) {
105 foreach ($t_pos[$c] as $y) {
106 for ($len = $max_len; $len > 0; $len--) {
107 if ($paths_ymin[$len] < $y) {
108 $paths_ymin[$len +
1] = $y;
109 // On construit le resultat sous forme de chaine d'abord,
110 // car les tableaux de PHP sont dispendieux en taille memoire
111 $paths[$len +
1] = $paths[$len] . " $x,$y";
115 if ($len +
1 > $max_len) {
124 if (isset($paths[$max_len]) and $paths[$max_len]) {
125 $path = explode(" ", $paths[$max_len]);
127 foreach ($path as $p) {
128 list($x, $y) = explode(",", $p);
133 return array($u, $v);
136 return array(0 => array(), 1 => array());
140 * Génération de diff a plusieurs étages
142 * @package SPIP\Revisions\Diff
146 * Objet DiffX d'un texte ou partie de texte
148 * @var Object Objet Diff* (DiffTexte, DiffPara, DiffPhrase)
156 * @param Object $diff Objet Diff* d'un texte ou morceau de texte
158 public function __construct($diff) {
163 // https://code.spip.net/@comparer
164 public function comparer($new, $old) {
165 $paras = $this->diff
->segmenter($new);
166 $paras_old = $this->diff
->segmenter($old);
167 if ($this->diff
->fuzzy()) {
168 list($trans_rev, $trans) = apparier_paras($paras_old, $paras);
169 $lcs = lcs_opt($trans);
170 $lcs_rev = array_flip($lcs);
172 list($trans_rev, $trans) = lcs($paras_old, $paras);
174 $lcs_rev = $trans_rev;
182 foreach ($paras as $i => $p) {
183 if (!isset($trans[$i])) {
185 $this->diff
->ajouter($p);
189 if (!isset($lcs[$i])) {
190 // Paragraphe deplace
191 $this->diff
->deplacer($p, $paras_old[$j]);
195 // Paragraphes supprimes jusqu'au paragraphe courant
196 if (!isset($i_old)) {
197 $i_old = key($paras_old);
198 $p_old = current($paras_old);
204 while (!$fin_old && $i_old < $j) {
205 if (!isset($trans_rev[$i_old])) {
206 $this->diff
->supprimer($p_old);
209 $i_old = key($paras_old);
210 $p_old = current($paras_old);
217 // Paragraphe n'ayant pas change de place
218 $this->diff
->comparer($p, $paras_old[$j]);
220 // Paragraphes supprimes a la fin du texte
222 if (!isset($i_old)) {
223 $i_old = key($paras_old);
224 $p_old = current($paras_old);
226 if (!strlen($p_old)) {
231 if (!isset($trans_rev[$i_old])) {
232 $this->diff
->supprimer($p_old);
234 $i_old = key($paras_old);
235 $p_old = current($paras_old);
243 if (!isset($trans_rev[$i_old])) {
244 $this->diff
->supprimer($p_old);
248 return $this->diff
->resultat();
253 * Génération de diff sur un Texte
255 * @package SPIP\Revisions\Diff
263 public function __construct() {
267 // https://code.spip.net/@_diff
268 public function _diff($p, $p_old) {
269 $diff = new Diff(new DiffPara
);
271 return $diff->comparer($p, $p_old);
274 // https://code.spip.net/@fuzzy
275 public function fuzzy() {
280 * Découper les paragraphes d'un texte en fragments
282 * @param string $texte Texte à fragmenter
283 * @return string[] Tableau de fragments (paragraphes)
285 public function segmenter($texte) {
286 return separer_paras($texte);
289 // NB : rem=\"diff-\" est un signal pour la fonction "afficher_para_modifies"
290 // https://code.spip.net/@ajouter
291 public function ajouter($p) {
293 $this->r
.= "\n\n\n<span class=\"diff-para-ajoute\" title=\"" . _T('revisions:diff_para_ajoute') . "\">" . $p . "</span rem=\"diff-\">";
296 // https://code.spip.net/@supprimer
297 public function supprimer($p_old) {
298 $p_old = trim($p_old);
299 $this->r
.= "\n\n\n<span class=\"diff-para-supprime\" title=\"" . _T('revisions:diff_para_supprime') . "\">" . $p_old . "</span rem=\"diff-\">";
302 // https://code.spip.net/@deplacer
303 public function deplacer($p, $p_old) {
304 $this->r
.= "\n\n\n<span class=\"diff-para-deplace\" title=\"" . _T('revisions:diff_para_deplace') . "\">";
305 $this->r
.= trim($this->_diff($p, $p_old));
306 $this->r
.= "</span rem=\"diff-\">";
309 // https://code.spip.net/@comparer
310 public function comparer($p, $p_old) {
311 $this->r
.= "\n\n\n" . $this->_diff($p, $p_old);
314 // https://code.spip.net/@resultat
315 public function resultat() {
321 * Génération de diff sur un paragraphe
323 * @package SPIP\Revisions\Diff
329 public function __construct() {
333 // https://code.spip.net/@_diff
334 public function _diff($p, $p_old) {
335 $diff = new Diff(new DiffPhrase
);
337 return $diff->comparer($p, $p_old);
340 // https://code.spip.net/@fuzzy
341 public function fuzzy() {
345 // https://code.spip.net/@segmenter
346 public function segmenter($texte) {
348 $texte = trim($texte);
349 while (preg_match('/[\.!\?\]]+\s*/u', $texte, $regs)) {
350 $p = strpos($texte, $regs[0]) +
strlen($regs[0]);
351 $paras[] = substr($texte, 0, $p);
352 $texte = substr($texte, $p);
361 // https://code.spip.net/@ajouter
362 public function ajouter($p) {
363 $this->r
.= "<span class=\"diff-ajoute\" title=\"" . _T('revisions:diff_texte_ajoute') . "\">" . $p . "</span rem=\"diff-\">";
366 // https://code.spip.net/@supprimer
367 public function supprimer($p_old) {
368 $this->r
.= "<span class=\"diff-supprime\" title=\"" . _T('revisions:diff_texte_supprime') . "\">" . $p_old . "</span rem=\"diff-\">";
371 // https://code.spip.net/@deplacer
372 public function deplacer($p, $p_old) {
373 $this->r
.= "<span class=\"diff-deplace\" title=\"" . _T('revisions:diff_texte_deplace') . "\">" . $this->_diff($p,
374 $p_old) . "</span rem=\"diff-\">";
377 // https://code.spip.net/@comparer
378 public function comparer($p, $p_old) {
379 $this->r
.= $this->_diff($p, $p_old);
382 // https://code.spip.net/@resultat
383 public function resultat() {
389 * Génération de diff sur une phrase
391 * @package SPIP\Revisions\Diff
397 public function __construct() {
401 // https://code.spip.net/@fuzzy
402 public function fuzzy() {
406 // https://code.spip.net/@segmenter
407 public function segmenter($texte) {
409 if (test_pcre_unicode()) {
410 $punct = '([[:punct:]]|' . plage_punct_unicode() . ')';
413 // Plages de poncutation pour preg_match bugge (ha ha)
414 $punct = '([^\w\s\x80-\xFF]|' . plage_punct_unicode() . ')';
417 $preg = '/(' . $punct . '+)(\s+|$)|(\s+)(' . $punct . '*)/' . $mode;
418 while (preg_match($preg, $texte, $regs)) {
419 $p = strpos($texte, $regs[0]);
420 $l = strlen($regs[0]);
421 $punct = $regs[1] ?
$regs[1] : $regs[6];
425 if ($punct == '[[') {
426 $avant = substr($texte, 0, $p) . $regs[5] . $punct;
427 $texte = $regs[4] . substr($texte, $p +
$l);
429 if ($punct == ']]') {
430 $avant = substr($texte, 0, $p) . $regs[5] . $punct;
431 $texte = substr($texte, $p +
$l);
432 } // Attacher les raccourcis fermants au mot precedent
434 if (preg_match(',^[\]}]+$,', $punct)) {
435 $avant = substr($texte, 0, $p) . (isset($regs[5]) ?
$regs[5] : '') . $punct;
436 $texte = $regs[4] . substr($texte, $p +
$l);
437 } // Attacher les raccourcis ouvrants au mot suivant
439 if (isset($regs[5]) && $regs[5] && preg_match(',^[\[{]+$,', $punct)) {
440 $avant = substr($texte, 0, $p) . $regs[5];
441 $texte = $punct . substr($texte, $p +
$l);
442 } // Les autres signes de ponctuation sont des mots a part entiere
444 $avant = substr($texte, 0, $p);
446 $texte = substr($texte, $p +
$l);
452 $avant = substr($texte, 0, $p +
$l);
453 $texte = substr($texte, $p +
$l);
469 // https://code.spip.net/@ajouter
470 public function ajouter($p) {
471 $this->r
.= "<span class=\"diff-ajoute\" title=\"" . _T('revisions:diff_texte_ajoute') . "\">" . $p . "</span rem=\"diff-\"> ";
474 // https://code.spip.net/@supprimer
475 public function supprimer($p_old) {
476 $this->r
.= "<span class=\"diff-supprime\" title=\"" . _T('revisions:diff_texte_supprime') . "\">" . $p_old . "</span rem=\"diff-\"> ";
479 // https://code.spip.net/@comparer
480 public function comparer($p, $p_old) {
484 // https://code.spip.net/@resultat
485 public function resultat() {
491 // https://code.spip.net/@preparer_diff
492 function preparer_diff($texte) {
493 include_spip('inc/charsets');
495 $charset = $GLOBALS['meta']['charset'];
496 if ($charset == 'utf-8') {
497 return unicode_to_utf_8(html2unicode($texte));
500 return unicode_to_utf_8(html2unicode(charset2unicode($texte, $charset, true)));
503 // https://code.spip.net/@afficher_diff
504 function afficher_diff($texte) {
505 $charset = $GLOBALS['meta']['charset'];
506 if ($charset == 'utf-8') {
510 return charset2unicode($texte, 'utf-8');