Destaque la diferencia entre dos cadenas en PHP

136

¿Cuál es la forma más fácil de resaltar la diferencia entre dos cadenas en PHP?

Estoy pensando en la línea de la página del historial de edición de Desbordamiento de pila, donde el nuevo texto está en verde y el texto eliminado está en rojo. Si hay algunas funciones o clases previamente escritas disponibles, sería ideal.

Philip Morton
fuente

Respuestas:

42

Pudiste usar el paquete PHP Horde_Text_Diff.

Sin embargo, este paquete ya no está disponible.

Minnesota
fuente
1
El enlace ya no funciona. ¿Hay alguna otra solución ahora en 2011? ;-) ¿es posible obtener resultados como este tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić
3
El sitio se ha ido, pero archive.org tiene una copia del sitio: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill
15
Lástima que requiere PERA. La dependencia de la PERA apesta.
Rudie
77
Desde el nuevo sitio web: "Actualización: el renderizador en línea ahora es una parte nativa del paquete Text_Diff PEAR. Ya no necesita usar el hack presentado aquí". Así que solo usa Text_Diff ahora.
Mat
11
GPL no solo es de uso gratuito. Obliga a su módulo / proyecto a ser GPL también.
Parris
76

Acabo de escribir una clase para calcular el número más pequeño (no para ser tomado literalmente) de ediciones para transformar una cadena en otra cadena:

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

Tiene una función estática para representar una versión HTML de la diferencia.

Es una primera versión, y es probable que se mejore, pero funciona bien ahora, por lo que la estoy lanzando por si alguien necesita generar un diferencial compacto de manera eficiente, como yo necesitaba.

Editar: ahora está en Github: https://github.com/gorhill/PHP-FineDiff

R. Hill
fuente
3
¡ Probaré el fork en github.com/xrstf/PHP-FineDiff para obtener soporte multibyte!
activout.se
1
@R. Hill - Funciona muy bien para mí también. Esta es realmente una mejor respuesta que la actual que parece estar extinta.
Wonko the Sane
¿Alguna actualización? Dice que no se pudo incluir el archivo "Texts / Diff.php" y no está en el archivo zip.
SISYN
¡Asombroso! Me refiero a la demostración en línea con código de ejemplo. Diferencias perfectas de nivel de carbón. ¡Simplemente guau! : ¡Oh, gracias!
Filip OvertoneSinger Rydlo
2
Parece que ahora la bifurcación github.com/BillyNate/PHP-FineDiff es la más avanzada y admite multibytes con diferentes codificaciones. github.com/xrstf/PHP-FineDiff está 404ing @ activout.se
Kangur
24

Si desea una biblioteca robusta, Text_Diff (un paquete PEAR) parece ser bastante bueno. Tiene algunas características geniales.

Wickethewok
fuente
66
PHP Inline-Diff, mencionado anteriormente, ".. utiliza Text_Diff de PEAR para calcular una diferencia". :)
MN
El enlace está roto. No puedo encontrar el paquete. Este es el mismo paquete Diff utilizado por la última versión de Wordpress.
Basil Musa
24

Este es bueno, también http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Resolver el problema no es tan simple como parece, y el problema me molestó durante aproximadamente un año antes de resolverlo. Logré escribir mi algoritmo en PHP, en 18 líneas de código. No es la forma más eficiente de hacer una diferencia, pero es probablemente la más fácil de entender.

Funciona encontrando la secuencia más larga de palabras comunes a ambas cadenas y encontrando recursivamente las secuencias más largas de los restos de la cadena hasta que las subcadenas no tengan palabras en común. En este punto, agrega las palabras nuevas restantes como inserción y las palabras antiguas restantes como eliminación.

Puede descargar la fuente aquí: PHP SimpleDiff ...

Memo
fuente
1
¡Esto me pareció muy útil también! No es tan complicado como las cosas de Pear.
dgavey
Aquí me da un error:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dinámico
Ok, publicaste un comentario para resolver eso. :) ¿por qué no lo editas en el código inicial? Gracias de todos modos +1 ... hmm, bueno, tú no eres el autor
dinámico
1
Esto
rsk82
En realidad, +1 por simplicidad
Parag Tyagi
17

Aquí hay una función corta que puede usar para diferenciar dos matrices. Implementa el 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 dos matrices:

  • matriz de valores: una lista de elementos tal como aparecen en la diferencia.
  • matriz de máscara: contiene números. 0: sin cambios, -1: eliminado, 1: agregado.

Si llena una matriz con caracteres, se puede usar para calcular la diferencia en línea. Ahora solo un paso para resaltar las diferencias:

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

P.ej.:

echo diffline('StackOverflow', 'ServerFault')

Saldrá:

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

StackOerverFFaulAyt

Notas adicionales:

  • La matriz de diferencias requiere elementos (m + 1) * (n + 1). Por lo tanto, puede encontrarse con errores de falta de memoria si intenta diferir secuencias largas. En este caso, difunda los trozos más grandes (por ejemplo, líneas) primero, luego diferencie su contenido en una segunda pasada.
  • El algoritmo se puede mejorar si recorta los elementos coincidentes desde el principio y el final, luego ejecuta el algoritmo solo en el medio diferente. Una última versión (más hinchada) también contiene estas modificaciones.
Calmarius
fuente
esto es simple, efectivo y multiplataforma; Utilicé esta técnica con explotar () en varios límites (línea o palabra) para obtener una salida diferente cuando corresponda. Muy buena solución, gracias!
Tío Code Monkey
dicecomputeDiff is not found
ichimaru
@ichimaru ¿Has pegado ambas funciones?
Calmarius
@Calmarius no vio la otra función ... ¡lo juro! está funcionando ahora gracias!
ichimaru
Gracias, esta es bastante útil para encontrar diferencias que la respuesta aceptada.
Karan Sharma
6

También hay una extensión PECL para xdiff:

En particular:

Ejemplo del manual de 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
fuente
1
La extensión xdiff pecl ya no se mantiene, aparentemente no se ha hecho una versión estable desde 2008-07-01, de acuerdo con pecl.php.net/package/xdiff , terminé yendo con la sugerencia por respuesta aceptada, ya que es mucho más nuevo , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell
¿Hay un procedimiento de instalación simple para XDiff de PHP? (para Debian Linux)
Peter Krauss
@MikePurcell, de hecho, todavía se mantiene. La última versión estable 2.0.1 que admite PHP 7 se lanzó el 2016-05-16.
user2513149
@ PeterKrauss, sí, la hay. Eche un vistazo a esta pregunta: serverfault.com/questions/362680/…
user2513149
5

Tuve problemas terribles con las alternativas basadas en PEAR y las más simples que se muestran. Entonces, aquí hay una solución que aprovecha el comando Unix diff (obviamente, debe estar en un sistema Unix o tener un comando diff de Windows que funcione para que funcione). Elija su directorio temporal favorito y cambie las excepciones a códigos de retorno si lo prefiere.

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

Este es el mejor que he encontrado.

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

ingrese la descripción de la imagen aquí

Andy
fuente
3
No funciona correctamente con UTF-8. Utiliza el acceso a la matriz en cadenas, que trata a cada carácter como un byte de ancho. Debería ser fácilmente reparable con mb_split.
Gellweiler
1
Aquí hay una solución rápida. Simplemente reemplace $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
Esta clase se queda sin memoria usando el modo de caracteres en la función computeTable.
Andy
1
El enlace actual es code.iamkate.com/php/diff-implementation . Lo he probado y no es compatible con UTF-8.
Kangur
3

Lo que está buscando es un "algoritmo diff". Una búsqueda rápida en Google me llevó a esta solución . No lo probé, pero tal vez haga lo que necesita.

Peter Bailey
fuente
Acabo de probar ese script y funciona bien: la operación diff se completa muy rápidamente (tarda unos 10 ms en procesar el párrafo corto que probé) y fue capaz de detectar cuándo se agregó un salto de línea. Ejecutar el código tal como está genera un par de avisos PHP que tal vez desee solucionar, pero aparte de eso, es una muy buena solución si necesita mostrar las diferencias en línea en lugar de usar la vista de diferencia tradicional de lado a lado.
Noel Whitemore
2

Recomendaría mirar estas increíbles funciones desde el núcleo de PHP:

similar_text - Calcula la similitud entre dos cadenas

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

levenshtein - Calcular la distancia de Levenshtein entre dos cuerdas

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

soundex - Calcula la clave soundex de una cadena

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

metaphone - Calcule la tecla metaphone de una cadena

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

Lukas Liesis
fuente
0

Encontré esta clase PHP diff de Chris Boulton basada en Python difflib, que podría ser una buena solución:

PHP Diff Lib

Shubhojoy Mitra
fuente