Evidenzia la differenza tra due stringhe in PHP


136

Qual è il modo più semplice per evidenziare la differenza tra due stringhe in PHP?

Sto pensando sulla falsariga della pagina della cronologia delle modifiche di Stack Overflow, in cui il nuovo testo è in verde e il testo rimosso è in rosso. Se ci sono funzioni o classi pre-scritte disponibili, sarebbe l'ideale.

Risposte:


42

Hai potuto usare il pacchetto PHP Horde_Text_Diff.

Tuttavia questo pacchetto non è più disponibile.


1
il collegamento non funziona più. c'è qualche altra soluzione ora nel 2011? ;-) è possibile ottenere output come questo tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić,

3
Il sito non c'è più, ma archive.org ha una copia del sito: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill

15
Peccato che richiede PERA. La dipendenza dalla PERA fa schifo.
Rudie,

7
Dal nuovo sito Web: "Aggiornamento: il renderer inline è ora una parte nativa del pacchetto PEAR Text_Diff. Non è più necessario utilizzare l'hack presentato qui." Quindi usa subito Text_Diff.
Mat

11
GPL non è solo gratuito da usare. Costringe anche il tuo modulo / progetto ad essere GPL.
Parris,

76

Ho appena scritto una classe per calcolare il numero più piccolo (da non prendere alla lettera) di modifiche per trasformare una stringa in un'altra stringa:

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

Ha una funzione statica per il rendering di una versione HTML del diff.

È una prima versione, e probabilmente migliorerà, ma funziona benissimo da ora, quindi la sto lanciando là fuori nel caso in cui qualcuno debba generare un differenziale compatto in modo efficiente, come avevo bisogno.

Modifica: è su Github ora: https://github.com/gorhill/PHP-FineDiff


3
Proverò il fork su github.com/xrstf/PHP-FineDiff per ottenere il supporto multibyte!
activout.se

1
@R. Hill - Funziona magnificamente anche per me. Questa è davvero una risposta migliore di quella attuale che sembra essere defunta.
Wonko the Sane,

Nessun aggiornamento? Dice che non è riuscito a includere il file "Texts / Diff.php" e non è nella zip.
SISYN,

Sorprendente! Intendo la demo online con un codice di esempio. Perfette differenze a livello di carattere. Solo WoW! : O grazie!
Filip OvertoneSinger Rydlo,

2
Sembra che ora il fork github.com/BillyNate/PHP-FineDiff sia il più avanti e supporti multibyte con codifiche diverse. github.com/xrstf/PHP-FineDiff è 404ing @ activout.se
Kangur

24

Se vuoi una libreria robusta, Text_Diff (un pacchetto PEAR) sembra essere abbastanza buono. Ha alcune caratteristiche piuttosto interessanti.


6
PHP Inline-Diff, menzionato sopra, "..usava Text_Diff da PEAR per calcolare un diff". :)
MN,

Il collegamento è interrotto. Non riesco a trovare il pacchetto. Questo è lo stesso pacchetto Diff utilizzato dall'ultima versione di Wordpress.
Basil Musa,

24

Questo è bello, anche http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Risolvere il problema non è così semplice come sembra, e il problema mi ha infastidito per circa un anno prima che lo capissi. Sono riuscito a scrivere il mio algoritmo in PHP, in 18 righe di codice. Non è il modo più efficiente per fare un diff, ma è probabilmente il più facile da capire.

Funziona trovando la sequenza di parole più lunga comune ad entrambe le stringhe e trovando ricorsivamente le sequenze più lunghe dei resti della stringa fino a quando le sottostringhe non hanno parole in comune. A questo punto aggiunge le nuove parole rimanenti come inserimento e le vecchie parole rimanenti come una cancellazione.

Puoi scaricare la fonte qui: PHP SimpleDiff ...


1
Ho trovato anche questo molto utile! Non così complicato come la roba di Pera.
dgavey,

Mi dà un errore qui:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dinamico

Ok hai pubblicato un commento per risolverlo. :) perché non lo modifichi nel codice iniziale? Grazie comunque +1 ... hmm beh non sei l'autore
dinamico

1
ecco quella che sembra essere l'ultima versione del 2010: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82

In realtà, +1 per semplicità
Parag Tyagi,

17

Ecco una breve funzione che puoi usare per diff due array. Implementa l' algoritmo 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);
}

Genera due array:

  • array di valori: un elenco di elementi come appaiono nel diff.
  • array di maschere: contiene numeri. 0: invariato, -1: rimosso, 1: aggiunto.

Se si popola un array con caratteri, può essere utilizzato per calcolare la differenza in linea. Ora basta un solo passaggio per evidenziare le differenze:

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

Per esempio.:

echo diffline('StackOverflow', 'ServerFault')

Uscita:

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

STackoerverfFaulOWt

Note aggiuntive:

  • La matrice diff richiede (m + 1) * (n + 1) elementi. In questo modo è possibile che si verifichino errori di memoria insufficiente se si tenta di differenziare sequenze lunghe. In questo caso prima diffondi blocchi più grandi (ad es. Linee), quindi diffondi il loro contenuto in un secondo passaggio.
  • L'algoritmo può essere migliorato se si tagliano gli elementi corrispondenti dall'inizio e dalla fine, quindi si esegue l'algoritmo solo sul mezzo diverso. Un'ultima versione (più gonfia) contiene anche queste modifiche.

questo è semplice, efficace e multipiattaforma; Ho usato questa tecnica con explode () su vari confini (linea o parola) per ottenere output diversi dove appropriato. Ottima soluzione, grazie!
Zio Code Monkey

dicecomputeDiff is not found
ichimaru

@ichimaru Hai incollato entrambe le funzioni?
Calmarius

@Calmarius non ha visto l'altra funzione ... lo giuro! ora funziona grazie!
ichimaru

Grazie, questo è abbastanza utile per scoprire diff rispetto alla risposta accettata.
Karan Sharma,

6

Esiste anche un'estensione PECL per xdiff:

In particolare:

Esempio dal manuale di 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
L'estensione pecd di xdiff non è più mantenuta, a quanto pare non è stata fatta una versione stabile dal 01/07/2008, secondo pecl.php.net/package/xdiff , ho finito per seguire il suggerimento della risposta accettata poiché è molto più recente , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell l'

Esiste una semplice procedura di installazione per XDiff di PHP? (per Debian Linux)
Peter Krauss,

@MikePurcell, infatti, è ancora mantenuto. L'ultima versione stabile 2.0.1 che supporta PHP 7 è stata rilasciata il 16/05/2016.
user2513149

@PeterKrauss, sì, c'è. Dai un'occhiata a questa domanda: serverfault.com/questions/362680/…
user2513149

5

Ho avuto terribili problemi con entrambe le alternative basate su PEAR e le più semplici mostrate. Quindi ecco una soluzione che sfrutta il comando diff Unix (ovviamente, devi essere su un sistema Unix o avere un comando diff Windows funzionante per farlo funzionare). Scegli la tua directory temporanea preferita e, se preferisci, modifica le eccezioni per restituire i codici.

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

Questo è il migliore che ho trovato.

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

inserisci qui la descrizione dell'immagine


3
Non funziona correttamente con UTF-8. Utilizza l'accesso alla matrice su stringhe, che considera ogni carattere largo un byte. Dovrebbe essere facilmente risolvibile difficile con mb_split.
Gellweiler,

1
Ecco una soluzione rapida. Basta sostituire $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;con$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

Questa classe esaurisce la memoria utilizzando la modalità carattere nella tabella di calcolo delle funzioni.
Andy,

1
Il link corrente è code.iamkate.com/php/diff-implementation . L'ho provato e non supporta UTF-8.
Kangur,

3

Quello che stai cercando è un "algoritmo diff". Una rapida ricerca su Google mi ha portato a questa soluzione . Non l'ho provato, ma forse farà ciò di cui hai bisogno.


Ho appena testato quello script e funziona bene - l'operazione diff si completa molto rapidamente (impiegando circa 10ms per elaborare il breve paragrafo che ho testato) ed è stata in grado di rilevare quando è stata aggiunta un'interruzione di riga. L'esecuzione del codice così com'è genera un paio di avvisi PHP che potresti voler correggere, ma a parte questo è un'ottima soluzione se devi mostrare le differenze in linea piuttosto che usare la tradizionale vista diff side-by-side.
Noel Whitemore,


2

Consiglierei di guardare queste fantastiche funzioni dal core di PHP:

similar_text - Calcola la somiglianza tra due stringhe

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

levenshtein - Calcola la distanza di Levenshtein tra due stringhe

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

soundex - Calcola la chiave soundex di una stringa

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

metafono: calcola la chiave del metafono di una stringa

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


0

Mi sono imbattuto in questa classe diff di PHP di Chris Boulton basata sul difflib di Python che potrebbe essere una buona soluzione:

PHP Diff Lib


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.