突出显示PHP中两个字符串之间的区别


136

突出显示PHP中两个字符串之间的区别的最简单方法是什么?

我正在考虑“堆栈溢出”编辑历史记录页面的内容,其中新文本为绿色,已删除文本为红色。如果有任何预写的函数或类可用,那将是理想的。

Answers:


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.山

15
太糟糕了,它需要梨。依赖梨糟透了。
Rudie 2011年

7
在新的网站上:“更新:内嵌渲染器现在是Text_Diff PEAR包的本机部分。您不再需要使用此处介绍的技巧。” 因此,现在就使用Text_Diff。

11
GPL不仅免费使用。它也迫使您的模块/项目也成为GPL。
帕里斯

76

刚刚写了一个类来计算最小的编辑量(从字面上看),以将一个字符串转换为另一个字符串:

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

它具有静态功能来呈现diff的HTML版本。

它是第一个版本,可能会进行改进,但是到目前为止它仍然可以正常工作,因此我将它扔在那里,以防有人需要像我需要的那样高效地生成紧凑的diff。

编辑:现在在Github上:https : //github.com/gorhill/PHP-FineDiff


3
我将在github.com/xrstf/PHP-FineDiff上尝试使用fork 来获得多字节支持!
activout.se 2012年

1
@R。Hill-对我来说也很漂亮。这确实是比当前似乎已失效的更好的答案。
Wonko the Sane

任何更新?它说未能包含文件“ Texts / Diff.php”,并且不在zip文件中。
SISYN 2013年

惊人!我的意思是带有示例代码的在线演示。完美的字符级差异。哇!:O谢谢!
Filip Overtone歌手Rydlo

2
现在看来github.com/BillyNate/PHP-FineDiff分支是最领先的,它支持使用不同编码的多字节。 github.com/xrstf/PHP-FineDiff正在404ing @ activout.se
Kangur

24

如果您想要一个健壮的库,Text_Diff(一个PEAR包)看起来会很好。它具有一些很酷的功能。


6
上面提到的PHP Inline-Diff,“ ..使用PEAR中的Text_Diff计算差异”。:)
MN

链接断开。找不到包。这与最新版本的Wordpress使用的Diff包相同。
罗勒·穆萨

24

这是一个不错的选择,也是 http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

解决问题并不像看起来那么简单,而且问题困扰了我大约一年,直到我弄清楚了。我设法用18行代码用PHP编写了算法。这不是进行差异的最有效方法,但它可能是最容易理解的方法。

它的工作原理是找到两个字符串共有的最长单词序列,然后递归找到字符串其余部分的最长序列,直到子字符串没有共同的单词为止。此时,它会将其余的新单词添加为插入,并将其余的旧单词添加为删除。

您可以在此处下载源代码:PHP SimpleDiff ...


1
我发现这也非常有用!不像梨那么复杂。
dgavey 2011年

这给了我一个错误:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
动态

好的,您发布了一个解决方案。:)为什么不在初始代码中对其进行编辑?无论如何,还是谢谢+1 ...嗯,您不是作者
动态的

1
以下是2010年以来
rsk82 2011年

实际上,为简单起见+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);
}

它生成两个数组:

  • values数组:在差异中显示的元素列表。
  • 掩码数组:包含数字。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> 

小号粘性erverF福尔owŤ

补充笔记:

  • diff矩阵需要(m + 1)*(n + 1)个元素。因此,如果尝试对长序列进行比较,则可能会遇到内存不足错误。在这种情况下,首先比较较大的块(例如行),然后在第二遍比较它们的内容。
  • 如果从头到尾修剪匹配的元素,然后仅在不同的中间位置运行算法,则可以改进算法。后一个版本(更膨胀)也包含这些修改。

这是简单,有效且跨平台的;我在各种边界(线或字)上将这种技术与explode()结合使用,以在适当的地方获得不同的输出。非常好的解决方案,谢谢!
叔叔代码猴子

它说computeDiff is not found
ichimaru

@ichimaru您粘贴了两个功能吗?
Calmarius

@ Calmarius没有看到其他功能...我发誓!它的工作现在谢谢!

谢谢,这是一个很容易找到差异而不是公认的答案。
卡兰·沙尔玛

6

xdiff还有一个PECL扩展:

特别是:

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
x差值PECL扩展不再维持,显然是一个稳定的版本还没有被做过2008-07-01据pecl.php.net/package/xdiff,我结束了与接受的答案建议去,因为它是非常新,horde.org/libraries/Horde_Text_Diff/download
Mike Purcell

PHP的XDiff有简单的安装过程吗?(对于Debian Linux)
Peter Krauss 2014年

@MikePurcell,事实上,它仍然保持着。2016年7月16日发布了支持PHP 7的最新稳定版本2.0.1。
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);
  }
}

4

3
无法与UTF-8一起正常使用。它对字符串使用数组访问,将每个字符视为一个字节宽。应该可以通过mb_split轻松修复。
Gellweiler

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。
Kangur

3

您正在寻找的是“差异算法”。快速的Google搜索使我找到了这个解决方案。我没有测试它,但是也许它可以满足您的需求。


我刚刚测试了该脚本,它运行良好-diff操作非常快速地完成(处理我测试的短段大约需要10毫秒),并且它能够检测到何时添加了换行符。按原样运行代码会生成几个PHP通知,您可能需要修复它们,但是除此之外,如果您需要内联显示差异而不是使用传统的并排diff视图,那么这是一个很好的解决方案。
Noel Whitemore


2

我建议您从PHP内核中查看这些很棒的功能:

similar_text —计算两个字符串之间的相似度

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

levenshtein —计算两个字符串之间的Levenshtein距离

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

soundex —计算字符串的soundex键

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

metaphone —计算字符串的metaphone键

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



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.