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

在 PHP 中,突出显示两个字符串之间差异的最简单方法是什么?

我想沿着 Stack Overflow 编辑历史页面的思路,其中新文本是绿色的,删除的文本是红色的。如果有任何预先编写的函数或类可用,那将是理想的。

122756 次浏览

你要寻找的是一个“差异算法”。一个快速的谷歌搜索引导我到 这个解决方案。我没有测试它,但也许它会做你需要的。

如果您想要一个健壮的库,那么 文本 _ 差异(一个 PEAR 包)看起来相当不错。它有一些很酷的功能。

您可以使用 PHP Horde _ Text _ Diff 包。

但是这个软件包已经不可用了。

这个也不错 Http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

解决这个问题并不像看起来那么简单,在我弄明白之前,这个问题困扰了我大约一年。我设法用18行代码用 PHP 编写了我的算法。这不是做差异的最有效的方法,但它可能是最容易理解的。

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

你可以在这里下载源代码: PHP SimpleDiff..。

刚刚编写了一个类来计算将一个字符串转换为另一个字符串的最小(不是字面意思)编辑次数:

Http://www.raymondhill.net/finediff/

它有一个静态函数来呈现差异的 HTML 版本。

这是第一个版本,可能会得到改进,但目前为止它工作得很好,所以我把它扔在那里,以防有人需要有效地生成一个小型差异,就像我需要的那样。

编辑: 它现在在 Github 上: Https://github.com/gorhill/php-finediff

我在使用基于 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);
}
}

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

下面是一个可以用来区分两个数组的简短函数,它实现了 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);
}

它生成两个数组:

  • Value array: 出现在 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>

故障

附加说明:

  • Diff 矩阵需要(m + 1) * (n + 1)元素。因此,如果试图区分长序列,就可能会遇到内存不足的错误。在这种情况下,首先区分较大的块(例如行) ,然后在第二次传递中区分它们的内容。
  • 如果从头到尾对匹配元素进行修剪,然后只在不同的中间部分运行算法,则可以改进算法。后者(更臃肿的)版本也包含这些修改。

我建议从 PHP 核心查看这些非常棒的函数:

Type _ text ー计算两个字符串之间的相似度

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

Levenshtein ー计算两个字符串之间的莱文斯坦距离

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

Soundex ー计算字符串的 soundex 键

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

计算字符串的变音位键

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

我偶然发现了 Chris Boulton 的这个 PHP diff 类,它基于 Pythondeflib,可能是一个很好的解决方案:

PHP Diff Lib

另一种解决方案(用于并行比较而非统一视图) : https://github.com/danmysak/side-by-side

我已经尝试了一个简单的方法与两个文本框和一些颜色样式。 注意: 我的 diff 检查器只会突出显示单词的差异,而不会显示字符的差异。

    <?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>

对于那些只是寻找一个非常简单的函数来查找字符串 A 中的字符而不是字符串 B 的人,我写了这个快速而又非常简单的函数。

function strdiff($a,$b){


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


return array_diff($a,$b);


}

嗨,这个会对你很有帮助:

$old_data = "We'll of today's hunt we will find inner zen. You are awesome [TEAM_NAME]! Cleveland has a lot more to offer though, so keep on roaming and find some happiness with Let's Roam!;";
$new_data = "We'll of today's hunt we will find inner zen. Great job today, you are freaking super awesome [TEAM_NAME]! though, so keep roaming Cleveland has a lot more to offer and find happiness on www.letsroam.com!;";


if($old_data) {
$old_words = explode(" " , $old_data);
$new_words = explode(" ", $new_data);


$added_words = array();
$deleted_words = array();
$unchanged_words = array();
foreach($new_words as $new_word) {
$new_word_index = array_search($new_word, $old_words);
// if($new_word == "you"){
//   die_r(array());
// }
if( $new_word_index > -1) {
// word already exists
array_push($unchanged_words, $new_word);
unset($old_words[$new_word_index]);
} else {
// word does not already exists
array_push($added_words, $new_word);
}
      

}
$deleted_words = $old_words;
$added_word_count = count($added_words);
$added_word_characters = strlen(implode(" ", $added_words));
}
die_r(array(
"old_data"=> $old_data,
"new_data"=> $new_data,
"unchanged_words"=> $unchanged_words,
"added_words"=> $added_words,
"deleted_words"=> $deleted_words,
"added_word_count"=>$added_word_count,
"added_word_characters"=>$added_word_characters
));