Podświetl różnicę między dwoma ciągami znaków w PHP


136

Jaki jest najłatwiejszy sposób na podkreślenie różnicy między dwoma ciągami znaków w PHP?

Myślę wzdłuż linii strony historii edycji Stack Overflow, gdzie nowy tekst jest zielony, a usunięty tekst jest na czerwono. Byłoby idealnie, gdyby były dostępne jakieś gotowe funkcje lub klasy.

Odpowiedzi:


42

Udało Ci się użyć pakietu PHP Horde_Text_Diff.

Jednak ten pakiet nie jest już dostępny.


1
link już nie działa. czy jest to jakieś inne rozwiązanie teraz w 2011 roku? ;-) to możliwe wyjście idź jak to tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavic

3
Witryna zniknęła, ale archive.org ma jej kopię: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill

15
Szkoda, że ​​wymaga PEAR. Zależność od PEAR jest do niczego.
Rudie

7
Z nowej witryny internetowej: „Aktualizacja: wbudowany moduł renderujący jest teraz natywną częścią pakietu Text_Diff PEAR. Nie musisz już używać przedstawionego tutaj hacka”. Więc po prostu użyj teraz Text_Diff.
Mat,

11
GPL jest nie tylko darmowe. Wymusza to, aby Twój moduł / projekt również podlegał licencji GPL.
Parris

76

Właśnie napisałem klasę, aby obliczyć najmniejszą (nie rozumieć dosłownie) liczbę zmian, aby przekształcić jeden ciąg w inny:

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

Posiada funkcję statyczną do renderowania wersji HTML pliku diff.

Jest to pierwsza wersja i prawdopodobnie zostanie ulepszona, ale na razie działa dobrze, więc wyrzucam ją na wypadek, gdyby ktoś musiał wydajnie wygenerować kompaktowy diff, tak jak potrzebowałem.

Edycja: jest teraz na Github: https://github.com/gorhill/PHP-FineDiff


3
Spróbuję forka na github.com/xrstf/PHP-FineDiff, aby uzyskać obsługę wielobajtową!
activout.se

1
@R. Hill - dla mnie też działa pięknie. To naprawdę lepsza odpowiedź niż obecna, która wydaje się nieaktualna.
Wonko the Sane

Jakieś aktualizacje? Mówi, że nie udało się dołączyć pliku „Texts / Diff.php” i nie ma go w archiwum.
SISYN

Niesamowity! Mam na myśli demo online z przykładowym kodem. Doskonałe różnice w poziomie znaków. Po prostu WoW! : O, dziękuję!
Filip OvertoneSinger Rydlo

2
Wygląda na to, że teraz widelec github.com/BillyNate/PHP-FineDiff jest najbardziej zaawansowany i obsługuje wiele bajtów z różnymi kodowaniami. github.com/xrstf/PHP-FineDiff to 404ing @ activout.se
Kangur.

24

Jeśli chcesz mieć solidną bibliotekę, Text_Diff (pakiet PEAR) wygląda całkiem nieźle. Ma całkiem fajne funkcje.


6
PHP Inline-Diff, wspomniany powyżej, "wykorzystuje Text_Diff z PEAR do obliczenia różnicy". :)
MN

Link jest uszkodzony. Nie mogę znaleźć paczki. Jest to ten sam pakiet Diff, który jest używany w najnowszej wersji Wordpress.
Basil Musa

24

To fajne, także http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Rozwiązanie problemu nie jest tak proste, jak się wydaje, a problem przeszkadzał mi przez około rok, zanim go rozgryzłem. Udało mi się napisać mój algorytm w PHP, w 18 linijkach kodu. Nie jest to najbardziej efektywny sposób zrobienia różnicy, ale prawdopodobnie najłatwiejszy do zrozumienia.

Działa, znajdując najdłuższą sekwencję słów wspólnych dla obu ciągów i rekurencyjnie znajdując najdłuższe sekwencje pozostałych ciągów, dopóki podciągi nie będą miały wspólnych słów. W tym momencie dodaje pozostałe nowe słowa jako wstawienie, a pozostałe stare słowa jako usunięcie.

Możesz pobrać źródło tutaj: PHP SimpleDiff ...


1
To również okazało się bardzo przydatne! Nie tak skomplikowane, jak sprawa Gruszka.
dgavey

Daje mi tutaj błąd:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dynamiczny

Ok, wysłałeś komendę, aby rozwiązać ten problem. :) dlaczego nie edytujesz go w początkowym kodzie? W każdym razie dzięki +1 ... hmm cóż, nie jesteś autorem
dynamiczny

1
oto co wygląda na najnowszą wersję z 2010 roku: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82

Właściwie +1 za prostotę
Parag Tyagi

17

Oto krótka funkcja, której możesz użyć do porównania dwóch tablic. Implementuje algorytm 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);
}

Generuje dwie tablice:

  • tablica wartości: lista elementów, które pojawiają się w pliku różnicowym.
  • tablica masek: zawiera liczby. 0: bez zmian, -1: usunięte, 1: dodane.

Jeśli wypełnisz tablicę znakami, można jej użyć do obliczenia różnicy między wierszami. Teraz wystarczy jeden krok, aby podkreślić różnice:

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

Na przykład.:

echo diffline('StackOverflow', 'ServerFault')

Wyświetli:

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

StackOkiedykolwiekfaFaulowt

Dodatkowe uwagi:

  • Macierz różnic wymaga (m + 1) * (n + 1) elementów. Więc możesz napotkać błędy braku pamięci, jeśli spróbujesz porównać długie sekwencje. W tym przypadku porównuj najpierw większe porcje (np. Linie), a następnie porównuj ich zawartość w drugim przebiegu.
  • Algorytm można ulepszyć, jeśli przyciąć pasujące elementy od początku i końca, a następnie uruchomić algorytm tylko na innym środku. Drugi (więcej wleczenia) wersja zawiera takich modyfikacji też.

to jest proste, efektywne i wieloplatformowe; Użyłem tej techniki z eksplodowaniem () na różnych granicach (wiersz lub słowo), aby uzyskać inne wyniki tam, gdzie było to konieczne. Bardzo fajne rozwiązanie, dzięki!
Uncle Code Monkey

mówicomputeDiff is not found
ichimaru

@ichimaru Czy wkleiłeś obie funkcje?
Calmarius

@Calmarius nie widział drugiej funkcji ... przysięgam! teraz działa, dzięki!
ichimaru

Dzięki, ten jest bardzo przydatny do znalezienia różnicy niż zaakceptowana odpowiedź.
Karan Sharma

6

Istnieje również rozszerzenie PECL dla xdiff:

W szczególności:

Przykład z podręcznika 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;
}

1
Rozszerzenie xdiff pecl nie jest już obsługiwane, najwyraźniej stabilna wersja nie została wydana od 2008-07-01, zgodnie z pecl.php.net/package/xdiff , w końcu poszedłem z sugestią przez zaakceptowaną odpowiedź, ponieważ jest znacznie nowsza , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell,

Czy istnieje prosta procedura instalacji XDiff PHP? (dla Debian Linux)
Peter Krauss

@MikePurcell, w rzeczywistości, nadal jest utrzymywany. Najnowsza stabilna wersja 2.0.1 obsługująca PHP 7 została wydana 16.05.2016.
user2513149

@PeterKrauss, tak, jest. Spójrz na to pytanie: serverfault.com/questions/362680/ ...
user2513149

5

Miałem straszne kłopoty z przedstawionymi alternatywami opartymi na PEAR i prostszymi. Oto rozwiązanie, które wykorzystuje polecenie Unix diff (oczywiście musisz być w systemie Unix lub mieć działające polecenie różnicowania systemu Windows, aby działało). Wybierz swój ulubiony katalog tymczasowy i jeśli wolisz, zmień wyjątki na kody zwrotne.

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

3
Nie działa poprawnie z UTF-8. Używa dostępu do tablicy w łańcuchach, które traktują każdy znak jako szerokość jednego bajtu. Powinien być łatwy do naprawienia za pomocą mb_split.
Gellweiler

1
Oto szybkie rozwiązanie. Wystarczy wymienić $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;z$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

W tej klasie zabraknie pamięci przy użyciu trybu znakowego w funkcji computeTable.
Andy

1
Obecny link to code.iamkate.com/php/diff-implementation . Przetestowałem to i nie obsługuje UTF-8.
Kangur

3

To, czego szukasz, to „algorytm różnicowania”. Szybkie wyszukiwanie w Google doprowadziło mnie do tego rozwiązania . Nie testowałem tego, ale może zrobi to, czego potrzebujesz.


Właśnie przetestowałem ten skrypt i działa dobrze - operacja porównywania kończy się bardzo szybko (przetwarzanie krótkiego akapitu, który testowałem, zajmuje około 10 ms) i był w stanie wykryć, kiedy dodano podział wiersza. Uruchomienie kodu w stanie takim, w jakim jest, generuje kilka powiadomień PHP, które możesz chcieć naprawić, ale poza tym jest to bardzo dobre rozwiązanie, jeśli chcesz pokazać różnice w tekście zamiast używać tradycyjnego widoku różnic obok siebie.
Noel Whitemore


2

Poleciłbym przyjrzeć się tym niesamowitym funkcjom z rdzenia PHP:

similar_text - Oblicz podobieństwo między dwoma ciągami

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

levenshtein - Oblicz odległość Levenshteina między dwoma strunami

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

soundex - Oblicz klucz soundex łańcucha

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

metaphone - Oblicz klucz metafonu dla ciągu

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



Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.