Выделите разницу между двумя строками в PHP

Какой самый простой способ выделить разницу между двумя строками в PHP?

Я думаю о том, что происходит на странице истории редактирования переполнения стека, где новый текст выделен зеленым, а удаленный - красным. Если есть какие-либо заранее написанные функции или классы, это было бы идеально.

17 ответов

Решение

Вы можете использовать пакет PHP Horde_Text_Diff. Он соответствует вашим потребностям, а также вполне настраиваемый.

Он также лицензирован по лицензии GPL, так что наслаждайтесь!

Просто написал класс для вычисления наименьшего (не считаться буквально) количества правок для преобразования одной строки в другую:

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

Он имеет статическую функцию для отображения HTML-версии diff.

Это первая версия, которая, вероятно, будет улучшена, но на данный момент она работает просто отлично, поэтому я добавлю ее на тот случай, если кому-то понадобится создать компактный файл эффективно, как мне и нужно.

Изменить: сейчас на Github: https://github.com/gorhill/PHP-FineDiff

Это хороший, также http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Решение проблемы не так просто, как кажется, и проблема беспокоила меня около года, прежде чем я понял это. Мне удалось написать свой алгоритм на PHP, в 18 строк кода. Это не самый эффективный способ проведения различий, но, вероятно, его проще всего понять.

Он работает, находя самую длинную последовательность слов, общих для обеих строк, и рекурсивно находит самые длинные последовательности остатков строки, пока подстроки не имеют общих слов. На этом этапе он добавляет оставшиеся новые слова в качестве вставки и оставшиеся старые слова в качестве удаления.

Вы можете скачать исходный код здесь: PHP SimpleDiff...

Вот короткая функция, которую вы можете использовать для сравнения двух массивов. Он реализует алгоритм 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);
}

Генерирует два массива:

  • массив значений: список элементов в том виде, в котором они отображаются в diff.
  • Маска-массив: содержит цифры. 0: без изменений, -1: удалено, 1: добавлено.

Если вы заполняете массив символами, его можно использовать для вычисления встроенной разницы. Теперь просто один шаг, чтобы выделить различия:

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

Например.:

echo diffline('Stackru', 'ServerFault')

Будет выводить:

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

STacko ерверее Фолвл T

Дополнительные примечания:

  • Для матрицы diff требуется (m+1)*(n+1) элементов. Таким образом, вы можете столкнуться с ошибками нехватки памяти, если попытаетесь различить длинные последовательности. В этом случае сначала разберитесь с большими кусками (например, строками), а затем разнесите их содержимое во втором проходе.
  • Алгоритм может быть улучшен, если вы урежете совпадающие элементы от начала и до конца, а затем запустите алгоритм только на отличающейся середине. Последняя (более раздутая) версия также содержит эти модификации.

Если вам нужна надежная библиотека, Text_Diff (пакет PEAR) выглядит неплохо. У него есть довольно интересные функции.

Существует также расширение PECL для xdiff:

Особенно:

Пример из руководства по 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;
}

Это лучший, который я нашел.

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

У меня были ужасные проблемы с показанными на основе PEAR и более простыми альтернативами. Итак, вот решение, которое использует команду Unix diff (очевидно, вы должны быть в системе Unix или иметь работающую команду Windows diff для ее работы). Выберите ваш любимый временный каталог и измените исключения на коды возврата, если хотите.

/**
 * @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);
  }
}

Порт php Neil Frasers diff_match_patch (лицензированный Apache 2.0)

То, что вы ищете, это "алгоритм сравнения". Быстрый поиск в Google привел меня к этому решению. Я не проверял, но, возможно, он будет делать то, что вам нужно.

Я бы порекомендовал посмотреть на эти удивительные функции из ядра PHP:

Similar_text - вычислить сходство между двумя строками

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

Левенштейн - вычислить расстояние Левенштейна между двумя строками

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

soundex - вычисляет ключ soundex строки

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

metaphone - вычисляет метафоновый ключ строки

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

Для тех, кто просто ищет очень простую функцию для поиска символов в строке A, но не в строке B, я написал эту быструю и очень простую функцию.

      function strdiff($a,$b){

    $a = str_split($a);
    $b = str_split($b);

    return array_diff($a,$b);

}

Я попробовал простой подход с двумя текстовыми полями и некоторыми цветовыми стилями. Примечание: моя программа проверки различий будет выделять разницу только в словах, а не в символах.

          <?php
    $valueOne = $_POST['value'] ?? "";
    $valueTwo = $_POST['valueb'] ?? "" ;
    
    $trimValueOne = trim($valueOne);
    $trimValueTwo = trim($valueTwo);

    $arrayValueOne = explode(" ",$trimValueOne);
    $arrayValueTwo = explode(" ",$trimValueTwo);

    $allDiff = array_merge(array_diff($arrayValueOne, $arrayValueTwo), array_diff($arrayValueTwo, $arrayValueOne));
    if(array_intersect($arrayValueOne,$allDiff) && array_intersect($arrayValueTwo,$allDiff)){

        if(array_intersect($arrayValueOne,$allDiff)){
            $highlightArr = array_intersect($arrayValueOne,$allDiff);
            $highlightArrValue = array_values($highlightArr);
            for ($i=0; $i <count($arrayValueOne) ;$i++) { 
                for ($j=0; $j <count($highlightArrValue) ; $j++) { 
                    if($arrayValueOne[$i] == $highlightArrValue[$j]){
                        $arrayValueOne[$i] = "<span>".$arrayValueOne[$i]."</span>";
                    }
                }
            }
            $strOne = implode(" ",$arrayValueOne);
            echo "<p class = \"one\">{$strOne}</p>";
        }if(array_intersect($arrayValueTwo,$allDiff)){
        $highlightArr = array_intersect($arrayValueTwo,$allDiff);
        $highlightArrValue = array_values($highlightArr);
        for ($i=0; $i <count($arrayValueTwo) ;$i++) { 
            for ($j=0; $j <count($highlightArrValue) ; $j++) { 
                    if($arrayValueTwo[$i] == $highlightArrValue[$j]){
                        $arrayValueTwo[$i] = "<span>".$arrayValueTwo[$i]."</span>";
                    }
                }
        }
        $strTwo = implode(" ",$arrayValueTwo);
        echo "<p class = \"two\">{$strTwo}</p>";
        }
    }elseif(!(array_intersect($arrayValueOne,$allDiff) && array_intersect($arrayValueTwo,$allDiff))){
        if($trimValueOne == $trimValueTwo){
            echo"<p class = \"one green\">$trimValueOne</p></p>";
            echo"<p class = \"two green\">$trimValueTwo</p></p>";
        }
        else{
            echo"<p class = \"one \">$trimValueOne</p></p>";
            echo"<p class = \"two \">$trimValueTwo</p></p>";
        }

    }
?>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <form method="post" action="">
    <textarea type="text" name="value" placeholder="enter first text"></textarea>
    <textarea type="text" name="valueb" placeholder="enter second text"></textarea>
    <input type="submit">
    </form>
</body>
</html>

Другое решение (для параллельного сравнения, а не для единого представления): https://github.com/danmysak/side-by-side.

Привет, это вам очень поможет:

Я наткнулся на этот класс PHP diff от Chris Boulton, основанный на Python difflib, который может быть хорошим решением:

PHP Diff Lib

Попробуйте https://methodfish.com/Projects/MFStrDiff Скачатьclass.MFStrDiff.phpв свой путь, а затем используйте следующий код для создания сравнения на уровне слов:

      include_once("class.MFStrDiff.php");
$diff=new MFStrDiff();
$diffHtml = $diff->getDiff($tx1, $tx2); // HTML output by default

См. демо на https://methodfish.com/Projects/MFStrDiff/demo .

Другие вопросы по тегам