Markieren Sie den Unterschied zwischen zwei Zeichenfolgen in PHP

136

Was ist der einfachste Weg, um den Unterschied zwischen zwei Zeichenfolgen in PHP hervorzuheben?

Ich denke in Anlehnung an die Seite "Stapelüberlauf-Bearbeitungsverlauf", auf der neuer Text grün und entfernter Text rot angezeigt wird. Wenn vorab geschriebene Funktionen oder Klassen verfügbar sind, wäre dies ideal.

Philip Morton
quelle

Antworten:

42

Sie konnten das PHP Horde_Text_Diff-Paket verwenden.

Dieses Paket ist jedoch nicht mehr verfügbar.

MN
quelle
1
Der Link funktioniert nicht mehr. Ist es jetzt eine andere Lösung im Jahr 2011? ;-) ist es möglich, Ausgabe wie diese zu erhalten tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić
3
Die Site ist weg, aber archive.org hat eine Kopie der Site: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill
15
Schade, dass es PEAR erfordert. PEAR-Abhängigkeit ist scheiße.
Rudie
7
Von der neuen Website: "Update: Der Inline-Renderer ist jetzt ein nativer Bestandteil des Text_Diff PEAR-Pakets. Sie müssen den hier vorgestellten Hack nicht mehr verwenden." Verwenden Sie jetzt einfach Text_Diff.
Mat
11
GPL ist nicht nur kostenlos zu benutzen. Es erzwingt, dass Ihr Modul / Projekt auch GPL ist.
Parris
76

Ich habe gerade eine Klasse geschrieben, um die kleinste (nicht wörtlich zu nehmen) Anzahl von Änderungen zu berechnen, um eine Zeichenfolge in eine andere Zeichenfolge umzuwandeln:

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

Es hat eine statische Funktion zum Rendern einer HTML-Version des Diff.

Es ist eine erste Version, die wahrscheinlich verbessert wird, aber sie funktioniert ab sofort einwandfrei. Ich werfe sie also raus, falls jemand ein kompaktes Diff effizient generieren muss, wie ich es brauchte.

Bearbeiten: Es ist jetzt auf Github: https://github.com/gorhill/PHP-FineDiff

R. Hill
quelle
3
Ich werde die Gabel unter github.com/xrstf/PHP-FineDiff ausprobieren , um Multibyte-Unterstützung zu erhalten!
activout.se
1
@R. Hill - Funktioniert auch wunderbar für mich. Dies ist wirklich eine bessere Antwort als die aktuelle, die nicht mehr zu funktionieren scheint.
Wonko der Gesunde
Irgendwelche Updates? Es heißt, dass die Datei "Texts / Diff.php" nicht enthalten war und nicht in der Zip-Datei enthalten ist.
SISYN
Tolle! Ich meine die Online-Demo mit Beispielcode. Perfekte Unterschiede im Char-Level. Einfach wow! : O danke!
Filip OvertoneSinger Rydlo
2
Es scheint, dass jetzt die Gabel github.com/BillyNate/PHP-FineDiff am weitesten vorne liegt und Multibyte mit unterschiedlichen Codierungen unterstützt. github.com/xrstf/PHP-FineDiff ist 404ing @ activout.se
Kangur
24

Wenn Sie eine robuste Bibliothek wünschen, sieht Text_Diff (ein PEAR-Paket) ziemlich gut aus. Es hat einige ziemlich coole Funktionen.

Wickethewok
quelle
6
PHP Inline-Diff, oben erwähnt, "..verwendet Text_Diff von PEAR, um ein Diff zu berechnen". :)
MN
Die Verbindung ist unterbrochen. Kann das Paket nicht finden. Dies ist das gleiche Diff-Paket, das von der neuesten Version von Wordpress verwendet wird.
Basil Musa
24

Dies ist eine schöne, auch http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Das Problem zu lösen ist nicht so einfach, wie es scheint, und das Problem hat mich ungefähr ein Jahr lang gestört, bevor ich es herausgefunden habe. Ich habe es geschafft, meinen Algorithmus in PHP in 18 Codezeilen zu schreiben. Es ist nicht der effizienteste Weg, einen Diff zu machen, aber wahrscheinlich der am einfachsten zu verstehende.

Es funktioniert, indem die längste Folge von Wörtern gefunden wird, die beiden Zeichenfolgen gemeinsam sind, und rekursiv die längsten Folgen der verbleibenden Zeichenfolgen gefunden werden, bis die Teilzeichenfolgen keine gemeinsamen Wörter mehr haben. Zu diesem Zeitpunkt werden die verbleibenden neuen Wörter als Einfügung und die verbleibenden alten Wörter als Löschung hinzugefügt.

Sie können die Quelle hier herunterladen: PHP SimpleDiff ...

Softy
quelle
1
Ich fand das auch sehr nützlich! Nicht so kompliziert wie das Birnenmaterial.
Dgavey
Es gibt mir einen Fehler hier:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dynamische
Ok, Sie haben ein Commetn gepostet, um das zu lösen. :) warum bearbeitest du es nicht im ursprünglichen Code? Trotzdem danke +1 ... hmm gut, du bist nicht der Autor
dynamisch
1
Hier ist die neueste Version aus dem Jahr 2010: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82
Eigentlich +1 der Einfachheit halber
Parag Tyagi
17

Hier ist eine kurze Funktion, mit der Sie zwei Arrays unterscheiden können. Es implementiert den LCS- Algorithmus:

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);
}

Es werden zwei Arrays generiert:

  • Werte-Array: Eine Liste von Elementen, wie sie im Diff erscheinen.
  • Maskenarray: enthält Zahlen. 0: unverändert, -1: entfernt, 1: hinzugefügt.

Wenn Sie ein Array mit Zeichen füllen, können Sie damit die Inline-Differenz berechnen. Jetzt nur ein einziger Schritt, um die Unterschiede hervorzuheben:

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;
}

Z.B.:

echo diffline('StackOverflow', 'ServerFault')

Wird ausgegeben:

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

S.TackOerverfFaulowt

Zusätzliche Bemerkungen:

  • Die Diff-Matrix erfordert (m + 1) * (n + 1) Elemente. Wenn Sie versuchen, lange Sequenzen zu unterscheiden, können daher Speicherfehler auftreten. In diesem Fall werden zuerst größere Teile (z. B. Linien) und dann in einem zweiten Durchgang deren Inhalt unterschieden.
  • Der Algorithmus kann verbessert werden, wenn Sie die übereinstimmenden Elemente von Anfang bis Ende kürzen und den Algorithmus dann nur in der unterschiedlichen Mitte ausführen. Eine letztere (aufgeblähte) Version enthält auch diese Modifikationen.
Calmarius
quelle
Dies ist einfach, effektiv und plattformübergreifend. Ich habe diese Technik mit explode () an verschiedenen Grenzen (Zeile oder Wort) verwendet, um gegebenenfalls unterschiedliche Ausgaben zu erhalten. Sehr schöne Lösung, danke!
Onkel Code Monkey
es heißtcomputeDiff is not found
ichimaru
@ichimaru Hast du beide Funktionen eingefügt?
Calmarius
@ Calmarius hat die andere Funktion nicht gesehen ... ich schwöre! es funktioniert jetzt danke!
Ichimaru
Danke, dies ist sehr praktisch, um herauszufinden, was anders ist als die akzeptierte Antwort.
Karan Sharma
6

Es gibt auch eine PECL-Erweiterung für xdiff:

Bestimmtes:

Beispiel aus dem PHP-Handbuch:

<?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
quelle
1
Die xdiff pecl-Erweiterung wird nicht mehr beibehalten. Anscheinend wurde seit dem 01.07.2008 keine stabile Version mehr veröffentlicht. Laut pecl.php.net/package/xdiff habe ich den Vorschlag durch akzeptierte Antwort angenommen, da er viel neuer ist , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell
Gibt es eine einfache Installationsprozedur für XDiff von PHP? (für Debian Linux)
Peter Krauss
@ MikePurcell, in der Tat wird es immer noch gepflegt. Die neueste stabile Version 2.0.1, die PHP 7 unterstützt, wurde am 16.05.2016 veröffentlicht.
user2513149
@ PeterKrauss, ja, das gibt es. Schauen Sie sich diese Frage an: serverfault.com/questions/362680/…
user2513149
5

Ich hatte schreckliche Probleme mit den PEAR-basierten und den einfacheren Alternativen. Hier ist also eine Lösung, die den Unix-Diff-Befehl nutzt (offensichtlich müssen Sie sich auf einem Unix-System befinden oder über einen funktionierenden Windows-Diff-Befehl verfügen, damit er funktioniert). Wählen Sie Ihr temporäres Lieblingsverzeichnis und ändern Sie die Ausnahmen in Rückkehrcodes, wenn Sie dies bevorzugen.

/**
 * @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
quelle
4

Dies ist das beste, das ich gefunden habe.

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

Geben Sie hier die Bildbeschreibung ein

Andy
quelle
3
Funktioniert nicht richtig mit UTF-8. Es verwendet den Array-Zugriff auf Zeichenfolgen, bei dem jedes Zeichen als ein Byte breit behandelt wird. Sollte mit mb_split leicht zu reparieren sein.
Gellweiler
1
Hier ist eine schnelle Lösung. Ersetzen Sie einfach $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;mit$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
Diese Klasse hat im Zeichenmodus in der Funktion computeTable nicht genügend Speicher.
Andy
1
Der aktuelle Link lautet code.iamkate.com/php/diff-implementation . Ich habe es getestet und es unterstützt UTF-8 nicht.
Kangur
3

Was Sie suchen, ist ein "Diff-Algorithmus". Eine schnelle Google-Suche führte mich zu dieser Lösung . Ich habe es nicht getestet, aber vielleicht macht es das, was Sie brauchen.

Peter Bailey
quelle
Ich habe dieses Skript gerade getestet und es funktioniert gut - der Diff-Vorgang wird sehr schnell abgeschlossen (es dauert ungefähr 10 ms, um den kurzen Absatz zu verarbeiten, den ich getestet habe) und er konnte erkennen, wann ein Zeilenumbruch hinzugefügt wurde. Wenn Sie den Code so wie er ist ausführen, werden einige PHP-Hinweise generiert, die Sie möglicherweise beheben möchten. Ansonsten ist dies eine sehr gute Lösung, wenn Sie die Unterschiede inline anzeigen müssen, anstatt die herkömmliche Diff-Ansicht nebeneinander zu verwenden.
Noel Whitemore
2

Ich würde empfehlen, sich diese fantastischen Funktionen aus dem PHP-Kern anzusehen:

like_text - Berechnet die Ähnlichkeit zwischen zwei Zeichenfolgen

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

levenshtein - Berechnet den Levenshtein-Abstand zwischen zwei Saiten

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

Soundex - Berechnet den Soundex-Schlüssel einer Zeichenfolge

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

Metaphon - Berechnet den Metaphonschlüssel einer Zeichenfolge

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

Lukas Liesis
quelle
0

Ich bin auf diese PHP-Diff-Klasse von Chris Boulton gestoßen, die auf Python-Difflib basiert und eine gute Lösung sein könnte:

PHP Diff Lib

Shubhojoy Mitra
quelle