Destaque a diferença entre duas strings no PHP

136

Qual é a maneira mais fácil de destacar a diferença entre duas strings no PHP?

Estou pensando nas linhas da página Histórico de edições do estouro de pilha, onde o novo texto está em verde e o texto removido está em vermelho. Se houver alguma função ou classe pré-escrita disponível, isso seria o ideal.

Philip Morton
fonte

Respostas:

42

Você conseguiu usar o pacote PHP Horde_Text_Diff.

No entanto, este pacote não está mais disponível.

MN
fonte
1
o link não funciona mais. existe outra solução agora em 2011? ;-) é possível obter uma saída como esta tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić
3
O site foi embora, mas archive.org tem uma cópia do site: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill
15
Pena que requer PERA. Dependência de pêra é uma merda.
quer
7
No novo site: "Atualização: o renderizador embutido agora é uma parte nativa do pacote Text_Diff PEAR. Você não precisa mais usar o hack apresentado aqui". Então, basta usar Text_Diff agora.
Mat
11
A GPL não é apenas de uso gratuito. Isso força o seu módulo / projeto a ser GPL também.
Par13
76

Acabei de escrever uma classe para calcular o menor número (não para ser tomado literalmente) de edições para transformar uma string em outra string:

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

Ele tem uma função estática para renderizar uma versão HTML do diff.

É uma primeira versão, e provavelmente será aprimorada, mas funciona muito bem a partir de agora, então estou lançando-a no caso de alguém precisar gerar um diff compacto com eficiência, como eu precisava.

Edit: Já está no Github: https://github.com/gorhill/PHP-FineDiff

R. Hill
fonte
3
Vou tentar o fork em github.com/xrstf/PHP-FineDiff para obter suporte a multibyte!
Activout.se
1
@R. Hill - Funciona lindamente para mim também. Esta é realmente uma resposta melhor do que a atual, que parece estar extinta.
Wonko the Sane
Alguma atualização? Ele diz que não incluiu o arquivo "Texts / Diff.php" e não está no zip.
SISYN
Surpreendente! Quero dizer a demonstração online com código de exemplo. Diferenças de nível de char perfeitas. Apenas Uau! : O Obrigado!
Filip OvertoneSinger Rydlo
2
Parece que agora o garfo do github.com/BillyNate/PHP-FineDiff é o mais avançado e suporta multibytes com codificações diferentes. github.com/xrstf/PHP-FineDiff está 404ing @ activout.se
Kangur
24

Se você deseja uma biblioteca robusta, o Text_Diff (um pacote PEAR) parece ser muito bom. Tem alguns recursos bem legais.

Wickethewok
fonte
6
O PHP Inline-Diff, mencionado acima, "..utiliza o Text_Diff do PEAR para calcular um diff". :)
MN
O link está quebrado. Não consigo encontrar o pacote. Este é o mesmo pacote Diff usado pela versão mais recente do Wordpress.
Basil Musa
24

Essa é boa, também http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Resolver o problema não é tão simples quanto parece, e o problema me incomodou por cerca de um ano antes de eu descobrir. Consegui escrever meu algoritmo em PHP, em 18 linhas de código. Não é a maneira mais eficiente de fazer uma diferença, mas é provavelmente a mais fácil de entender.

Ele funciona encontrando a sequência mais longa de palavras comum a ambas as seqüências de caracteres e recursivamente localizando as seqüências mais longas dos demais remanescentes da sequência até que as subseqüências não tenham palavras em comum. Nesse ponto, ele adiciona as novas palavras restantes como uma inserção e as palavras antigas restantes como uma exclusão.

Você pode baixar a fonte aqui: PHP SimpleDiff ...

Softy
fonte
1
Eu achei isso muito útil também! Não é tão complicado quanto o material de Pear.
dgavey
Isso me dá um erro aqui:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
dynamic
Ok, você postou um commetn para resolver isso. :) por que você não o edita no código inicial? Obrigado na mesma +1 ... hmm bem você não é o autor
dinâmica
1
aqui está o que parece ser a versão mais recente a partir de 2010: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82
Na verdade, uma para a simplicidade
Parag Tyagi
17

Aqui está uma função curta que você pode usar para diferenciar duas matrizes. Ele implementa o 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);
}

Ele gera duas matrizes:

  • matriz de valores: uma lista de elementos como eles aparecem no diff.
  • array de máscaras: contém números. 0: inalterado, -1: removido, 1: adicionado.

Se você preencher uma matriz com caracteres, ela poderá ser usada para calcular a diferença embutida. Agora, apenas um passo para destacar as diferenças:

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

Por exemplo.:

echo diffline('StackOverflow', 'ServerFault')

Saída:

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

StackOerverfFaulowt

Notas Adicionais:

  • A matriz diff requer (m + 1) * (n + 1) elementos. Assim, você poderá encontrar erros de falta de memória se tentar diferenciar longas seqüências. Nesse caso, diferencie primeiro os pedaços maiores (por exemplo, linhas) e depois difira o conteúdo em uma segunda passagem.
  • O algoritmo pode ser aprimorado se você aparar os elementos correspondentes do começo e do fim e executar o algoritmo apenas no meio diferente. Uma última versão (mais inchada) também contém essas modificações.
Calmarius
fonte
isso é simples, eficaz e multiplataforma; Eu usei essa técnica com explodir () em vários limites (linha ou palavra) para obter resultados diferentes, quando apropriado. Solução muito boa, obrigado!
Uncle Code Monkey
dizcomputeDiff is not found
ichimaru
@ichimaru Você colou as duas funções?
Calmarius
@Calmarius não viu a outra função ... eu juro! está funcionando agora obrigado!
Ichimaru
Obrigado, Este é bastante útil para descobrir diferenças do que a resposta aceita.
precisa saber é o seguinte
6

Há também uma extensão PECL para xdiff:

Em particular:

Exemplo do manual do 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
fonte
1
A extensão xdiff pecl não é mais mantida, aparentemente uma versão estável não foi feita desde 01/07/2008 - de acordo com pecl.php.net/package/xdiff , acabei seguindo a sugestão de resposta aceita, pois é muito mais recente , horde.org/libraries/Horde_Text_Diff/download
Mike Purcell
Existe um procedimento de instalação simples para o XDiff do PHP? (para Debian Linux)
Peter Krauss
@MikePurcell, na verdade, ele ainda é mantido. A versão estável mais recente 2.0.1 do PHP 7 foi lançada em 16/05/2016.
user2513149
@ PeterKrauss, sim, existe. Dê uma olhada nesta pergunta: serverfault.com/questions/362680/…
user2513149
5

Eu tive um problema terrível com as alternativas baseadas em PEAR e as mais simples mostradas. Então, aqui está uma solução que aproveita o comando diff do Unix (obviamente, você precisa estar em um sistema Unix ou ter um comando diff do Windows em funcionamento para que ele funcione). Escolha seu diretório temporário favorito e altere as exceções para retornar códigos, se você preferir.

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

Este é o melhor que eu encontrei.

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

insira a descrição da imagem aqui

Andy
fonte
3
Não funciona corretamente com UTF-8. Ele usa acesso à matriz em cadeias, que trata cada caractere como um byte de largura. Deve ser facilmente corrigível com o mb_split.
Gellweiler # 25/15
1
Aqui está uma solução rápida. Basta substituir $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;por$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
Essa classe fica sem memória usando o modo de caractere na função computeTable.
217 Andy
1
O link atual é code.iamkate.com/php/diff-implementation . Eu testei e ele não suporta UTF-8.
Kangur
3

O que você está procurando é um "algoritmo diff". Uma rápida pesquisa no Google me levou a esta solução . Não testei, mas talvez faça o que você precisa.

Peter Bailey
fonte
Acabei de testar esse script e ele funciona bem - a operação diff é concluída muito rapidamente (demorando cerca de 10 ms para processar o breve parágrafo que testei) e foi capaz de detectar quando uma quebra de linha foi adicionada. A execução do código como está gera alguns avisos do PHP que você pode querer corrigir, mas, além disso, é uma solução muito boa se você precisar mostrar as diferenças em linha, em vez de usar a exibição tradicional de comparação lado a lado.
precisa saber é o seguinte
2

Eu recomendaria olhar para essas funções impressionantes do núcleo do PHP:

similar_text - Calcula a similaridade entre duas strings

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

levenshtein - Calcule a distância de Levenshtein entre duas cordas

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

soundex - Calcula a tecla soundex de uma string

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

metafone - Calcule a chave de metafone de uma string

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

Lukas Liesis
fonte
0

Me deparei com esta classe diff de PHP de Chris Boulton, baseada no Python difflib, que poderia ser uma boa solução:

PHP Diff Lib

Shubhojoy Mitra
fonte