Rev 4 | Blame | Last modification | View Log | RSS feed
<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2005 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
// Ce fichier ne sera execute qu'une fois
if (defined("_ECRIRE_INC_DIFF")) return;
define("_ECRIRE_INC_DIFF", "1");
//
// LCS (Longest Common Subsequence) en deux versions
// (ref: http://www2.toki.or.id/book/AlgDesignManual/BOOK/BOOK5/NODE208.HTM)
// Version ultra-simplifiee : chaque chaine est une permutation de l'autre
// et on passe en parametre un des deux tableaux de correspondances
function lcs_opt($s) {
$n = count($s);
if (!$n) return array();
$paths = array();
$paths_ymin = array();
$max_len = 0;
// Insertion des points
asort($s);
foreach ($s as $y => $c) {
for ($len = $max_len; $len > 0; $len--) {
if ($paths_ymin[$len] < $y) {
$paths_ymin[$len + 1] = $y;
$paths[$len + 1] = $paths[$len];
$paths[$len + 1][$y] = $c;
break;
}
}
if ($len == 0) {
$paths_ymin[1] = $y;
$paths[1] = array($y => $c);
}
if ($len + 1 > $max_len) $max_len = $len + 1;
}
return $paths[$max_len];
}
// Version normale : les deux chaines n'ont pas ete traitees au prealable
// par la fonction d'appariement
function lcs($s, $t) {
$n = count($s);
$p = count($t);
if (!$n || !$p) return array(0 => array(), 1 => array());
$paths = array();
$paths_ymin = array();
$max_len = 0;
$s_pos = $t_pos = array();
// Insertion des points
foreach ($t as $y => $c) $t_pos[trim($c)][] = $y;
foreach ($s as $x => $c) {
$c = trim($c);
if (!$t_pos[$c]) continue;
krsort($t_pos[$c]);
foreach ($t_pos[$c] as $y) {
for ($len = $max_len; $len > 0; $len--) {
if ($paths_ymin[$len] < $y) {
$paths_ymin[$len + 1] = $y;
// On construit le resultat sous forme de chaine d'abord,
// car les tableaux de PHP sont dispendieux en taille memoire
$paths[$len + 1] = $paths[$len]." $x,$y";
break;
}
}
if ($len + 1 > $max_len) $max_len = $len + 1;
if ($len == 0) {
$paths_ymin[1] = $y;
$paths[1] = "$x,$y";
}
}
}
if ($paths[$max_len]) {
$path = explode(" ", $paths[$max_len]);
$u = $v = array();
foreach ($path as $p) {
list($x, $y) = explode(",", $p);
$u[$x] = $y;
$v[$y] = $x;
}
return array($u, $v);
}
return array(0 => array(), 1 => array());
}
function test_lcs($a, $b) {
$s = explode(" ", $a);
$t = explode(" ", $b);
$t0 = explode(" ", microtime());
list($r1, $r2) = lcs($s, $t);
$t1 = explode(" ", microtime());
$dt = $t1[0] + $t1[1] - $t0[0] - $t0[1];
echo join(" ", $r1)."<br />";
echo join(" ", $r2)."<p>";
echo "<div style='font-weight: bold; color: red;'>$dt s.</div>";
}
function test_lcs_opt($s) {
$s = preg_split(',\s+,', $s);
$t0 = explode(" ", microtime());
$t = lcs_opt($s);
$t1 = explode(" ", microtime());
$dt = $t1[0] + $t1[1] - $t0[0] - $t0[1];
echo join(" ", $s)."<br />";
echo join(" ", $t)."<p>";
echo "<div style='font-weight: bold; color: red;'>$dt s.</div>";
}
//
// Generation de diff a plusieurs etages
//
class Diff {
var $diff;
var $fuzzy;
function Diff($diff) {
$this->diff = $diff;
$this->fuzzy = true;
}
function comparer($new, $old) {
$paras = $this->diff->segmenter($new);
$paras_old = $this->diff->segmenter($old);
if ($this->diff->fuzzy()) {
list($trans_rev, $trans) = apparier_paras($paras_old, $paras);
$lcs = lcs_opt($trans);
$lcs_rev = array_flip($lcs);
}
else {
list($trans_rev, $trans) = lcs($paras_old, $paras);
$lcs = $trans;
$lcs_rev = $trans_rev;
}
reset($paras_old);
reset($paras);
reset($lcs);
unset($i_old);
$fin_old = false;
foreach ($paras as $i => $p) {
if (!isset($trans[$i])) {
// Paragraphe ajoute
$this->diff->ajouter($p);
continue;
}
$j = $trans[$i];
if (!isset($lcs[$i])) {
// Paragraphe deplace
$this->diff->deplacer($p, $paras_old[$j]);
continue;
}
if (!$fin_old) {
// Paragraphes supprimes jusqu'au paragraphe courant
if (!isset($i_old)) {
list($i_old, $p_old) = each($paras_old);
if (!$p_old) $fin_old = true;
}
while (!$fin_old && $i_old < $j) {
if (!isset($trans_rev[$i_old])) {
$this->diff->supprimer($p_old);
}
unset($i_old);
list($i_old, $p_old) = each($paras_old);
if (!$p_old) $fin_old = true;
}
}
// Paragraphe n'ayant pas change de place
$this->diff->comparer($p, $paras_old[$j]);
}
// Paragraphes supprimes a la fin du texte
if (!$fin_old) {
if (!isset($i_old)) {
list($i_old, $p_old) = each($paras_old);
if (!strlen($p_old)) $fin_old = true;
}
while (!$fin_old) {
if (!isset($trans_rev[$i_old])) {
$this->diff->supprimer($p_old);
}
list($i_old, $p_old) = each($paras_old);
if (!$p_old) $fin_old = true;
}
}
if (isset($i_old)) {
if (!isset($trans_rev[$i_old])) {
$this->diff->supprimer($p_old);
}
}
return $this->diff->resultat();
}
}
class DiffTexte {
var $r;
function DiffTexte() {
$this->r = "";
}
function _diff($p, $p_old) {
$diff = new Diff(new DiffPara);
return $diff->comparer($p, $p_old);
}
function fuzzy() {
return true;
}
function segmenter($texte) {
return separer_paras($texte);
}
// NB : rem=\"diff-\" est un signal pour la fonction "afficher_para_modifies"
function ajouter($p) {
$p = trim($p);
$this->r .= "\n\n\n<div class=\"diff-para-ajoute\" title=\""._T('diff_para_ajoute')."\">".$p."</div rem=\"diff-\">";
}
function supprimer($p_old) {
$p_old = trim($p_old);
$this->r .= "\n\n\n<div class=\"diff-para-supprime\" title=\""._T('diff_para_supprime')."\">".$p_old."</div rem=\"diff-\">";
}
function deplacer($p, $p_old) {
$this->r .= "\n\n\n<div class=\"diff-para-deplace\" title=\""._T('diff_para_deplace')."\">";
$this->r .= trim($this->_diff($p, $p_old));
$this->r .= "</div rem=\"diff-\">";
}
function comparer($p, $p_old) {
$this->r .= "\n\n\n".$this->_diff($p, $p_old);
}
function resultat() {
return $this->r;
}
}
class DiffPara {
var $r;
function DiffPara() {
$this->r = "";
}
function _diff($p, $p_old) {
$diff = new Diff(new DiffPhrase);
return $diff->comparer($p, $p_old);
}
function fuzzy() {
return true;
}
function segmenter($texte) {
$paras = array();
$texte = trim($texte);
while (preg_match('/[\.!\?]+\s*/u', $texte, $regs)) {
$p = strpos($texte, $regs[0]) + strlen($regs[0]);
$paras[] = substr($texte, 0, $p);
$texte = substr($texte, $p);
}
if ($texte) $paras[] = $texte;
return $paras;
}
function ajouter($p) {
$this->r .= "<span class=\"diff-ajoute\" title=\""._T('diff_texte_ajoute')."\">".$p."</span rem=\"diff-\">";
}
function supprimer($p_old) {
$this->r .= "<span class=\"diff-supprime\" title=\""._T('diff_texte_supprime')."\">".$p_old."</span rem=\"diff-\">";
}
function deplacer($p, $p_old) {
$this->r .= "<span class=\"diff-deplace\" title=\""._T('diff_texte_deplace')."\">".$this->_diff($p, $p_old)."</span rem=\"diff-\">";
}
function comparer($p, $p_old) {
$this->r .= $this->_diff($p, $p_old);
}
function resultat() {
return $this->r;
}
}
class DiffPhrase {
var $r;
function DiffPhrase() {
$this->r = "";
}
function fuzzy() {
return false;
}
function segmenter($texte) {
$paras = array();
if (test_pcre_unicode()) {
$punct = '([[:punct:]]|'.plage_punct_unicode().')';
$mode = 'u';
}
else {
// Plages de poncutation pour preg_match bugge (ha ha)
$punct = '([^\w\s\x80-\xFF]|'.plage_punct_unicode().')';
$mode = '';
}
$preg = '/('.$punct.'+)(\s+|$)|(\s+)('.$punct.'*)/'.$mode;
while (preg_match($preg, $texte, $regs)) {
$p = strpos($texte, $regs[0]);
$l = strlen($regs[0]);
$punct = $regs[1] ? $regs[1] : $regs[6];
$milieu = "";
if ($punct) {
// Attacher les raccourcis fermants au mot precedent
if (preg_match(',^[\]}]+$,', $punct)) {
$avant = substr($texte, 0, $p) . $regs[5] . $punct;
$texte = $regs[4] . substr($texte, $p + $l);
}
// Attacher les raccourcis ouvrants au mot suivant
else if ($regs[5] && preg_match(',^[\[{]+$,', $punct)) {
$avant = substr($texte, 0, $p) . $regs[5];
$texte = $punct . substr($texte, $p + $l);
}
// Les autres signes de ponctuation sont des mots a part entiere
else {
$avant = substr($texte, 0, $p);
$milieu = $regs[0];
$texte = substr($texte, $p + $l);
}
}
else {
$avant = substr($texte, 0, $p + $l);
$texte = substr($texte, $p + $l);
}
if ($avant) $paras[] = $avant;
if ($milieu) $paras[] = $milieu;
}
if ($texte) $paras[] = $texte;
return $paras;
}
function ajouter($p) {
$this->r .= "<span class=\"diff-ajoute\" title=\""._T('diff_texte_ajoute')."\">".$p."</span rem=\"diff-\"> ";
}
function supprimer($p_old) {
$this->r .= "<span class=\"diff-supprime\" title=\""._T('diff_texte_supprime')."\">".$p_old."</span rem=\"diff-\"> ";
}
function comparer($p, $p_old) {
$this->r .= $p;
}
function resultat() {
return $this->r;
}
}
function preparer_diff($texte) {
include_spip("charsets.php");
$charset = lire_meta('charset');
if ($charset == 'utf-8')
return unicode_to_utf_8(html2unicode($texte));
return unicode_to_utf_8(html2unicode(charset2unicode($texte, $charset, true)));
}
function afficher_diff($texte) {
$charset = lire_meta('charset');
if ($charset == 'utf-8') return $texte;
return charset2unicode($texte, 'utf-8');
}
?>