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

136

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

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

Филип Мортон
источник

Ответы:

42

Вы смогли использовать пакет PHP Horde_Text_Diff.

Однако этот пакет больше не доступен.

Миннесота
источник
1
ссылка больше не работает. это какое-то другое решение сейчас в 2011 году? ;-) возможно ли пойти и получить вывод, подобный этому tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić
3
Сайт ушел, но archive.org имеет копию сайта: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill
15
Жаль, что для этого нужна груша. Груша-зависимость отстой.
Руди
7
С нового веб-сайта: «Обновление: встроенное средство визуализации теперь является встроенной частью пакета Text_Diff PEAR. Вам больше не нужно использовать хак, представленный здесь». Так что просто используйте Text_Diff сейчас.
Мат
11
GPL не просто бесплатна для использования. Это заставляет ваш модуль / проект быть GPL.
Паррис
76

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

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

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

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

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

Р. Хилл
источник
3
Я попробую форк на github.com/xrstf/PHP-FineDiff, чтобы получить многобайтовую поддержку!
activout.se
1
@Р. Хилл - Работает прекрасно для меня тоже. Это действительно лучший ответ, чем текущий, который кажется несуществующим.
Вонко вменяемый
Любые обновления? Он говорит, что не удалось включить файл "Texts / Diff.php", и его нет в zip.
SISYN
Удивительный! Я имею в виду онлайн демо с примером кода. Совершенные различия на уровне символов. Просто вау! : O Спасибо!
Филип ОбертонеСингер Ридло
2
Похоже, что теперь форк github.com/BillyNate/PHP-FineDiff стоит впереди и поддерживает многобайтовые кодировки с разными кодировками. github.com/xrstf/PHP-FineDiff - 404ing @ activout.se
Кангур,
24

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

Wickethewok
источник
6
PHP Inline-Diff, упомянутый выше, "..uses Text_Diff из PEAR для вычисления diff". :)
MN
Ссылка не работает. Не могу найти пакет. Это тот же пакет Diff, который используется в последней версии Wordpress.
Василий Муса
24

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

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

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

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

тряпка
источник
1
Я также нашел это очень полезным! Не так сложно, как груши.
dgavey
Это дает мне ошибку здесь:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
динамический
Хорошо, вы разместили комментарий, чтобы решить это. :) почему вы не редактируете его в исходном коде? В любом случае спасибо +1 ... хм, ну, вы не автор
динамический
1
вот что выглядит как последняя версия 2010 года: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82
На самом деле, +1 для простоты
Parag Tyagi
17

Вот короткая функция, которую вы можете использовать для сравнения двух массивов. Он реализует алгоритм 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('StackOverflow', '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) элементов. Таким образом, вы можете столкнуться с ошибками нехватки памяти, если попытаетесь различить длинные последовательности. В этом случае сначала разберитесь с большими кусками (например, строками), а затем разнесите их содержимое во втором проходе.
  • Алгоритм может быть улучшен, если вы урежете совпадающие элементы от начала и до конца, а затем запустите алгоритм только на отличающейся середине. Последняя (более раздутая) версия содержит эти изменения тоже.
Calmarius
источник
это просто, эффективно и кроссплатформенно; Я использовал эту технику с помощью explode () на разных границах (строка или слово), чтобы получить различный вывод, где это необходимо. Очень хорошее решение, спасибо!
Дядя Код Обезьяна
это говорит computeDiff is not found
ичимару
@ichimaru Вы вставили обе функции?
Кальмарий
@ Калмариус не видел другой функции ... клянусь! сейчас работает, спасибо!
ичимару
Спасибо, это очень удобно, чтобы узнать разницу, чем принятый ответ.
Каран Шарма
6

Существует также расширение 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;
}
Гордон
источник
1
Расширение xdiff pecl больше не поддерживается, по-видимому, стабильный выпуск не выпускался с 2008-07-01, в соответствии с pecl.php.net/package/xdiff , в итоге я согласился с предложением по принятому ответу, так как оно намного новее , horde.org/libraries/Horde_Text_Diff/download
Майк Перселл,
Есть простая процедура установки для PHP XDiff? (для Debian Linux)
Питер Краусс
@MikePurcell, по сути, все еще поддерживается. Последняя стабильная версия 2.0.1 с поддержкой PHP 7 была выпущена 2016-05-16.
user2513149
@PeterKrauss, да, есть. Взгляните на этот вопрос: serverfault.com/questions/362680/…
user2513149
5

У меня были ужасные проблемы с показанными на основе 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);
  }
}
xgretsch
источник
4

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

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

введите описание изображения здесь

Энди
источник
3
Не работает должным образом с UTF-8. Он использует доступ к массиву в строках, который обрабатывает каждый символ как один байт. Должно быть легко исправимо с помощью mb_split.
Геллвайлер
1
Вот быстрое решение. Просто замените $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;на$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
Этому классу не хватает памяти, используя символьный режим в функции computeTable.
Энди
1
Текущая ссылка: code.iamkate.com/php/diff-implementation . Я протестировал его, и он не поддерживает UTF-8.
Кангур
3

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

Питер Бейли
источник
Я только что протестировал этот скрипт, и он работает хорошо - операция diff завершается очень быстро (около 10 мс, чтобы обработать короткий параграф, который я тестировал), и он смог определить, когда был добавлен разрыв строки. Выполнение кода «как есть» генерирует пару уведомлений PHP, которые вы, возможно, захотите исправить, но кроме этого это очень хорошее решение, если вам нужно показать различия внутри строки, а не использовать традиционное параллельное представление diff.
Ноэль
2

Я бы порекомендовал посмотреть на эти удивительные функции из ядра 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

Лукас Лиесис
источник
0

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

PHP Diff Lib

Shubhojoy Митра
источник