web-dev-qa-db-fra.com

Souligner la différence entre deux chaînes dans PHP

Quel est le moyen le plus simple de mettre en évidence la différence entre deux chaînes en PHP?

Je pense à la page d’historique d’édition de Stack Overflow, où le nouveau texte est en vert et le texte supprimé en rouge. S'il y a des fonctions ou des classes pré-écrites disponibles, ce serait l'idéal.

130
Philip Morton

Vous pouvez utiliser le package PHP Horde_Text_Diff . Cela répond à vos besoins et est tout à fait personnalisable.

Il est également sous licence GPL, alors profitez-en!

42
M.N

Vient d’écrire une classe pour calculer le plus petit nombre (à ne pas prendre littéralement) de modifications pour transformer une chaîne en une autre:

http://www.raymondhill.net/finediff/

Il a une fonction statique pour rendre une version HTML du diff.

C'est une première version, qui devrait être améliorée, mais elle fonctionne très bien dès maintenant. Je la lance donc au cas où quelqu'un aurait besoin de générer un diff compact de manière efficace, comme j'avais besoin.

Edit: C’est maintenant sur Github: https://github.com/gorhill/PHP-FineDiff

71
R. Hill

Si vous voulez une bibliothèque robuste, Text_Diff (un paquet PEAR) a l’air d’être plutôt bon. Il a quelques fonctionnalités assez cool.

25
Wickethewok

C’est aussi un bel exemple http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Résoudre le problème n’est pas aussi simple qu’il semble, et le problème m’a dérangé pendant environ un an avant que je ne le voie. J'ai réussi à écrire mon algorithme en PHP, en 18 lignes de code. Ce n'est pas le moyen le plus efficace de faire un diff, mais c'est probablement le plus facile à comprendre.

Cela fonctionne en recherchant la plus longue séquence de mots commune aux deux chaînes et en recherchant récursivement les plus longues séquences des restes de la chaîne jusqu'à ce que les sous-chaînes n'aient plus de mots en commun. À ce stade, il ajoute les nouveaux mots restants sous forme d'insertion et les anciens mots restants sous forme de suppression.

Vous pouvez télécharger la source ici: PHP SimpleDiff ...

23
Softy

Voici une courte fonction que vous pouvez utiliser pour différencier deux tableaux. Il implémente l'algorithme LCS :

function computeDiff($from, $to)
{
    $diffValues = array();
    $diffMask = array();

    $dm = array();
    $n1 = count($from);
    $n2 = count($to);

    for ($j = -1; $j < $n2; $j++) $dm[-1][$j] = 0;
    for ($i = -1; $i < $n1; $i++) $dm[$i][-1] = 0;
    for ($i = 0; $i < $n1; $i++)
    {
        for ($j = 0; $j < $n2; $j++)
        {
            if ($from[$i] == $to[$j])
            {
                $ad = $dm[$i - 1][$j - 1];
                $dm[$i][$j] = $ad + 1;
            }
            else
            {
                $a1 = $dm[$i - 1][$j];
                $a2 = $dm[$i][$j - 1];
                $dm[$i][$j] = max($a1, $a2);
            }
        }
    }

    $i = $n1 - 1;
    $j = $n2 - 1;
    while (($i > -1) || ($j > -1))
    {
        if ($j > -1)
        {
            if ($dm[$i][$j - 1] == $dm[$i][$j])
            {
                $diffValues[] = $to[$j];
                $diffMask[] = 1;
                $j--;  
                continue;              
            }
        }
        if ($i > -1)
        {
            if ($dm[$i - 1][$j] == $dm[$i][$j])
            {
                $diffValues[] = $from[$i];
                $diffMask[] = -1;
                $i--;
                continue;              
            }
        }
        {
            $diffValues[] = $from[$i];
            $diffMask[] = 0;
            $i--;
            $j--;
        }
    }    

    $diffValues = array_reverse($diffValues);
    $diffMask = array_reverse($diffMask);

    return array('values' => $diffValues, 'mask' => $diffMask);
}

Il génère deux tableaux: 

  • valeurs array: une liste d'éléments tels qu'ils apparaissent dans le diff.
  • tableau de masques: contient des nombres. 0: inchangé, -1: supprimé, 1: ajouté.

Si vous remplissez un tableau avec des caractères, vous pouvez l'utiliser pour calculer la différence en ligne. Maintenant, juste une étape pour souligner les différences:

function diffline($line1, $line2)
{
    $diff = computeDiff(str_split($line1), str_split($line2));
    $diffval = $diff['values'];
    $diffmask = $diff['mask'];

    $n = count($diffval);
    $pmc = 0;
    $result = '';
    for ($i = 0; $i < $n; $i++)
    {
        $mc = $diffmask[$i];
        if ($mc != $pmc)
        {
            switch ($pmc)
            {
                case -1: $result .= '</del>'; break;
                case 1: $result .= '</ins>'; break;
            }
            switch ($mc)
            {
                case -1: $result .= '<del>'; break;
                case 1: $result .= '<ins>'; break;
            }
        }
        $result .= $diffval[$i];

        $pmc = $mc;
    }
    switch ($pmc)
    {
        case -1: $result .= '</del>'; break;
        case 1: $result .= '</ins>'; break;
    }

    return $result;
}

Par exemple.:

echo diffline('StackOverflow', 'ServerFault')

Est-ce que la sortie:

S<del>tackO</del><ins>er</ins>ver<del>f</del><ins>Fau</ins>l<del>ow</del><ins>t</ins> 

StackOerverfFaulow

Notes complémentaires:

  • La matrice diff nécessite (m + 1) * (n + 1) éléments. Vous pouvez donc rencontrer des erreurs de mémoire insuffisante si vous essayez de différer de longues séquences. Dans ce cas, différenciez d'abord les plus gros morceaux (par exemple, les lignes), puis différez leur contenu lors d'une seconde passe.
  • L'algorithme peut être amélioré si vous ajustez les éléments correspondants du début et de la fin, puis exécutez l'algorithme uniquement sur le milieu différent. Une version/ dernière (plus gonflée) contient également ces modifications.
12
Calmarius

Il existe également une extension PECL pour xdiff:

En particulier:

Exemple tiré du manuel PHP:

<?php
$old_article = file_get_contents('./old_article.txt');
$new_article = $_POST['article'];

$diff = xdiff_string_diff($old_article, $new_article, 1);
if (is_string($diff)) {
    echo "Differences between two articles:\n";
    echo $diff;
}
6
Gordon
4
Andy

J'ai eu de gros problèmes avec les alternatives présentées, à la fois simples et basées sur PEAR. Voici donc une solution qui exploite la commande Unix diff (vous devez évidemment être sur un système Unix ou disposer d’une commande Windows diff qui fonctionne pour que cela fonctionne). Choisissez votre répertoire temporaire favori et modifiez les exceptions pour renvoyer les codes si vous préférez.

/**
 * @brief Find the difference between two strings, lines assumed to be separated by "\n|
 * @param $new string The new string
 * @param $old string The old string
 * @return string Human-readable output as produced by the Unix diff command,
 * or "No changes" if the strings are the same.
 * @throws Exception
 */
public static function diff($new, $old) {
  $tempdir = '/var/somewhere/tmp'; // Your favourite temporary directory
  $oldfile = tempnam($tempdir,'OLD');
  $newfile = tempnam($tempdir,'NEW');
  if (!@file_put_contents($oldfile,$old)) {
    throw new Exception('diff failed to write temporary file: ' . 
         print_r(error_get_last(),true));
  }
  if (!@file_put_contents($newfile,$new)) {
    throw new Exception('diff failed to write temporary file: ' . 
         print_r(error_get_last(),true));
  }
  $answer = array();
  $cmd = "diff $newfile $oldfile";
  exec($cmd, $answer, $retcode);
  unlink($newfile);
  unlink($oldfile);
  if ($retcode != 1) {
    throw new Exception('diff failed with return code ' . $retcode);
  }
  if (empty($answer)) {
    return 'No changes';
  } else {
    return implode("\n", $answer);
  }
}
4
xgretsch

Ce que vous recherchez est un "algorithme de diff". Une recherche rapide sur Google m'a conduit à cette solution . Je ne l'ai pas testé, mais peut-être qu'il fera ce dont vous avez besoin.

3
Peter Bailey
2
hakre

Je recommanderais de regarder ces super fonctions du noyau PHP:

similar_text - Calcule la similarité entre deux chaînes

http://www.php.net/manual/en/function.similar-text.php

levenshtein - Calcule la distance de Levenshtein entre deux chaînes

http://www.php.net/manual/en/function.levenshtein.php

soundex - Calcule la clé soundex d'une chaîne

http://www.php.net/manual/en/function.soundex.php

metaphone - Calcule la clé de métaphone d'une chaîne

http://www.php.net/manual/en/function.metaphone.php

1
Lukas

Je suis tombé sur cette classe PHP diff de Chris Boulton basée sur Python difflib qui pourrait être une bonne solution:

PHP Diff Lib

0
Shubhojoy Mitra