Mettez en évidence la différence entre deux chaînes en PHP

136

Quelle est la manière la plus simple de mettre en évidence la différence entre deux chaînes en PHP?

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

Philip Morton
la source

Réponses:

42

Vous avez pu utiliser le package PHP Horde_Text_Diff.

Cependant, ce package n'est plus disponible.

MN
la source
1
le lien ne fonctionne plus. est-ce une autre solution maintenant en 2011? ;-) est-il possible d'aller obtenir une sortie comme celle-ci tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić
3
Le site a disparu, mais archive.org a une copie du site: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill
15
Dommage qu'il nécessite PEAR. La dépendance à la poire est nul.
Rudie
7
Depuis le nouveau site Web: "Mise à jour: le moteur de rendu en ligne fait désormais partie du package Text_Diff PEAR. Vous n'avez plus besoin d'utiliser le hack présenté ici." Alors utilisez simplement Text_Diff maintenant.
Mat
11
La GPL n'est pas seulement gratuite. Cela oblige également votre module / projet à être GPL.
Parris
76

Je viens 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 chaîne:

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

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

C'est une première version, et susceptible d'être améliorée, mais cela fonctionne très bien pour le moment, donc je la jette là-bas au cas où quelqu'un aurait besoin de générer un diff compact efficacement, comme j'en avais besoin.

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

R. Hill
la source
3
Je vais essayer le fork sur github.com/xrstf/PHP-FineDiff pour obtenir un support multi-octets!
activout.se
1
@R. Hill - Fonctionne à merveille pour moi aussi. C'est vraiment une meilleure réponse que la réponse actuelle qui semble être caduque.
Wonko the Sane le
Les mises à jour? Il dit n'a pas réussi à inclure le fichier "Textes / Diff.php" et il n'est pas dans le zip.
SISYN
Incroyable! Je veux dire la démo en ligne avec un exemple de code. Différences parfaites au niveau des caractères. Juste wow! : O merci!
Filip OvertoneSinger Rydlo
2
Il semble que maintenant la fourche github.com/BillyNate/PHP-FineDiff soit la plus avancée et qu'elle supporte les multi-octets avec différents encodages. github.com/xrstf/PHP-FineDiff est 404ing @ activout.se
Kangur
24

Si vous voulez une bibliothèque robuste, Text_Diff (un package PEAR) semble plutôt bon. Il a des fonctionnalités assez intéressantes.

Wickethewok
la source
6
PHP Inline-Diff, mentionné ci-dessus, ".. utilise Text_Diff de PEAR pour calculer un diff". :)
MN
Le lien est rompu. Je ne trouve pas le paquet. Il s'agit du même package Diff utilisé par la dernière version de Wordpress.
Basil Musa
24

C'est une belle, également http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Résoudre le problème n'est pas aussi simple qu'il y paraît, et le problème m'a dérangé pendant environ un an avant de le découvrir. J'ai réussi à écrire mon algorithme en PHP, en 18 lignes de code. Ce n'est pas la manière la plus efficace de faire une différence, mais c'est probablement la plus simple à comprendre.

Il fonctionne en trouvant la plus longue séquence de mots commune aux deux chaînes et en recherchant de manière récursive les séquences les plus longues des restes de la chaîne jusqu'à ce que les sous-chaînes n'aient pas de mots en commun. À ce stade, il ajoute les nouveaux mots restants en tant qu'insertion et les anciens mots restants en tant que suppression.

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

Doux
la source
1
J'ai trouvé cela très utile aussi! Pas aussi compliqué que les trucs de poire.
dgavey
Cela me donne une erreur ici:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dynamique
Ok, vous avez publié un message pour résoudre ce problème. :) pourquoi vous ne le modifiez pas dans le code initial? Merci quand même +1 ... hmm eh bien vous n'êtes pas l'auteur
dynamique
1
voici ce qui semble être la dernière version de 2010: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82
En fait, +1 pour plus de simplicité
Parag Tyagi
17

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:

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

Si vous remplissez un tableau avec des caractères, il peut être utilisé pour calculer la différence en ligne. Maintenant, une seule étape pour mettre en évidence 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')

Sortira:

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

STackOserveurFFaulowt

Notes complémentaires:

  • La matrice diff nécessite (m + 1) * (n + 1) éléments. Ainsi, vous pouvez rencontrer des erreurs de mémoire insuffisante si vous essayez de différencier de longues séquences. Dans ce cas, différez d'abord les gros morceaux (par exemple les lignes), puis différez leur contenu dans une seconde passe.
  • L'algorithme peut être amélioré si vous coupez les éléments correspondants du début et de la fin, puis exécutez l'algorithme sur le milieu différent uniquement. Une dernière version (plus gonflée) contient également ces modifications.
Calmaire
la source
c'est simple, efficace et multiplateforme; J'ai utilisé cette technique avec explode () sur différentes limites (ligne ou mot) pour obtenir une sortie différente le cas échéant. Très belle solution, merci!
Uncle Code Monkey
il ditcomputeDiff is not found
ichimaru
@ichimaru Avez-vous collé les deux fonctions?
Calmarius
@Calmarius n'a pas vu l'autre fonction ... je le jure! son travail maintenant merci!
ichimaru
Merci, celui-ci est assez pratique pour découvrir la différence que la réponse acceptée.
Karan Sharma
6

Il existe également une extension PECL pour xdiff:

En particulier:

Exemple 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;
}
Gordon
la source
1
xdiff pecl extension n'est plus maintenue, apparemment une version stable n'a pas été faite depuis le 01/07/2008, selon pecl.php.net/package/xdiff , j'ai fini par accepter la suggestion par réponse acceptée car elle est beaucoup plus récente , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell
Il existe une procédure d'installation simple pour XDiff de PHP? (pour Debian Linux)
Peter Krauss
@MikePurcell, en fait, il est toujours maintenu. La dernière version stable 2.0.1 prenant en charge PHP 7 a été publiée le 16/05/2016.
user2513149
@PeterKrauss, oui, il y en a. Regardez cette question: serverfault.com/questions/362680/…
user2513149
5

J'ai eu de terribles problèmes avec les alternatives basées sur PEAR et les alternatives plus simples présentées. Voici donc une solution qui exploite la commande Unix diff (évidemment, vous devez être sur un système Unix ou avoir une commande Windows diff fonctionnelle pour que cela fonctionne). Choisissez votre répertoire temporaire préféré et modifiez les exceptions en codes de retour 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);
  }
}
xgretsch
la source
4

C'est le meilleur que j'ai trouvé.

http://code.stephenmorley.org/php/diff-implementation/

entrez la description de l'image ici

Andy
la source
3
Ne fonctionne pas correctement avec UTF-8. Il utilise l'accès au tableau sur les chaînes, qui traite chaque caractère comme un octet de large. Devrait être facilement réparable avec mb_split.
Gellweiler
1
Voici une solution rapide. Juste remplacer $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;par$sequence1 = preg_split('//u', $string1, -1, PREG_SPLIT_NO_EMPTY); $sequence2 = preg_split('//u', $string2, -1, PREG_SPLIT_NO_EMPTY); $end1 = count($sequence1) - 1; $end2 = count($sequence2) - 1;
Gellweiler
Cette classe manque de mémoire en utilisant le mode caractère dans la fonction computeTable.
Andy
1
Le lien actuel est code.iamkate.com/php/diff-implementation . Je l'ai testé et il ne prend pas en charge UTF-8.
Kangur
3

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.

Peter Bailey
la source
Je viens de tester ce script et cela fonctionne bien - l'opération de diff se termine très rapidement (prenant environ 10 ms pour traiter le court paragraphe que j'ai testé) et il a pu détecter lorsqu'un saut de ligne a été ajouté. L'exécution du code tel quel génère quelques avis PHP que vous voudrez peut-être corriger, mais à part cela, c'est une très bonne solution si vous avez besoin d'afficher les différences en ligne plutôt que d'utiliser la vue de différence traditionnelle côte à côte.
Noel Whitemore
2

Je recommanderais de regarder ces fonctions impressionnantes du noyau PHP:

similar_text - Calcule la similitude entre deux chaînes

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

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

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

soundex - Calcule la clé soundex d'une corde

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

Lukas Liesis
la source
0

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

PHP Diff Lib

Shubhojoy Mitra
la source