How to Compare Two Rich Text Box Contents and Highlight the Characters That Are Changed

How to compare two rich text box contents and highlight the characters that are changed?

First kudos to ArtyomZzz for pointing to the great source of DiffMatchPatch!

Here is a piece of code the will paint the changed characters in two RichTextboxes upon a button click.

First download the diff-match-patchsource. (!See update below!) From the zip file copy 'DiffMatchPatch.cs' and also 'COPY' to your project and inlude the cs file in you project. Also add the namespace to your using clauses.

using DiffMatchPatch;

namespace RTF_diff
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

// this is the diff object;
diff_match_patch DIFF = new diff_match_patch();

// these are the diffs
List<Diff> diffs;

// chunks for formatting the two RTBs:
List<Chunk> chunklist1;
List<Chunk> chunklist2;

// two color lists:
Color[] colors1 = new Color[3] { Color.LightGreen, Color.LightSalmon, Color.White };
Color[] colors2 = new Color[3] { Color.LightSalmon, Color.LightGreen, Color.White };

public struct Chunk
{
public int startpos;
public int length;
public Color BackColor;
}

private void button1_Click(object sender, EventArgs e)
{
diffs = DIFF.diff_main(RTB1.Text, RTB2.Text);
DIFF.diff_cleanupSemanticLossless(diffs); // <--- see note !

chunklist1 = collectChunks(RTB1);
chunklist2 = collectChunks(RTB2);

paintChunks(RTB1, chunklist1);
paintChunks(RTB2, chunklist2);

RTB1.SelectionLength = 0;
RTB2.SelectionLength = 0;
}

List<Chunk> collectChunks(RichTextBox RTB)
{
RTB.Text = "";
List<Chunk> chunkList = new List<Chunk>();
foreach (Diff d in diffs)
{
if (RTB == RTB2 && d.operation == Operation.DELETE) continue; // **
if (RTB == RTB1 && d.operation == Operation.INSERT) continue; // **

Chunk ch = new Chunk();
int length = RTB.TextLength;
RTB.AppendText(d.text);
ch.startpos = length;
ch.length = d.text.Length;
ch.BackColor = RTB == RTB1 ? colors1[(int)d.operation]
: colors2[(int)d.operation];
chunkList.Add(ch);
}
return chunkList;

}

void paintChunks(RichTextBox RTB, List<Chunk> theChunks)
{
foreach (Chunk ch in theChunks)
{
RTB.Select(ch.startpos, ch.length);
RTB.SelectionBackColor = ch.BackColor;
}

}

}
}

At first I was trying to also color the changed lines as a whole in a lighter color; however that takes considerably more work, can't be done (coloring the whole line as opposed to just the part with content), and was not part of your question in the first place..

Note: There are four different post-diff cleanup methods. Which is best suited will depend on the input and purpose. I suggest trying the cleanupSemanticLossless. I have added a 3rd screenshot showing how this cleanup works

Here is a screenshot of the output:
original output

And one of the new version:
new screenshot

Screenshot after cleanupSemanticLossless:
3rd screenshot

Update: The sources seem to have moved. This should help..

How to compare two textfield contents and highlight the characters that are changed in ReactJS?

Much thanks to the people who replied. I amusingly came to a solution. Hopefully this helps other people with this problem.

The concept is that I have two editable textboxes with content, and I highlight the differences between the two. Firstly, let's address the highlighting. This is straightforward. I used Squiggs's method of comparing two strings by using the following package:

https://github.com/kpdecker/jsdiff

Utilizing the Diff package, I created a function that compares the two strings and returns a result:

function compareStrings(string1, string2) {
let results = Diff.diffChars(string1, string2);

let output = "";
results.forEach((item) => {

if (item.removed) {
output += `<span style="background-color:yellow">${item.value}</span>`;
} else if (!item.added) {
output += `${item.value}`;
}
});

return output;
}

When using the Diff package, it separates the identified differences and similarities into different items. It makes working with the findings relatively easy. In the code above, the results in results are iterated over. If an item has removed set to true, it's an item in string1 that's not in string2. If added is set to true, its an item in string2 that's not in string1. If neither are set to true, it's an item in both strings. The output of my function will contain all the characters of string1. If a item has added set to true, the item is ignored. For example, if string1 is cat and string2 is bat, output will be cat but with c highlighted in yellow.

So how do I insert this highlighted text into a Material-Ui textbox? Well, I chose to go down the route of utilizing a contentEditable control. You can transform a p tag (and other tags) to an editable textbox that renders HTML. This isn't a solution that I love since it opens up the hassle of dealing with XSS, but it seems unavoidable. If anyone follows this solution, I implore you to implement client-side and server-side input sanitation to prevent XSS. The latter is especially important because client-side validation can be bypassed rather easily through various methods, such as modifying the page with inspect element or by utilizing packet crafting tools.

Here's how a contentEditable textbox looks like in HTML:

<p
id="textbox1"
className="textbox"
variant="outlined"
contentEditable={true}
/>

From here, I can assign the results of compareStrings to it:

document.getElementById("textbox1").innerHTML = htmlData

So the end result would have two contentEditable textboxes. I would have to invoke compareStrings twice and apply the results to both.

Of course, I've only detailed just the essentials to solve this problem. There's other quality of life features such as copy and paste as detailed below:

Highlight value of @material-ui/core TextField in React at specific indices

Rich Text Box how to highlight text block

I think you are looking for ScintillaNET.

On the other hand if you want to do this by yourself in RTB then you can do it by first finding the lineNumber using TextBoxBase.Lines property. Then ...

//Select the line from it's number
startIndex = richTextBox.GetFirstCharIndexFromLine(lineNumber);
richTextBox.Select(startIndex, length);

//Set the selected text fore and background color
richTextBox.SelectionColor = System.Drawing.Color.White;
richTextBox.SelectionBackColor= System.Drawing.Color.Blue;

Append rich formatted text from 2 RichTextboxes into another RichTextBox in C#

You will want to do this:

richTextBox3.Rtf = richTextBox1.Rtf
richTextBox3.Select(richTextBox3.TextLength, 0);
richTextBox3.SelectedRtf = richTextBox2.Rtf;

That should do the trick.

Don't highlight newline characters when selecting with TextPointers

I have figured out that the problem is not specifically related to the \n character.

I'm still not entirely sure what caused it, but I fixed it like this:

After playing around and noticing that changing the width of the column had an effect on the width of the bad highlighted area, I tried decreasing it away. Problem was, this caused text to wrap.

So I figured, okay, I'll just disable WordWrap for the RichTextBox, right? Wrong. WPF does not allow us to disable this.

I needed a way to achieve the effect of disabling WordWrap, so after doing some reading, I saw that I could set the FlowDocument's Width property to a very large number so that the text would never wrap. Even to do this, I then had to go and disable the scroll bar.

So then, I had the effect of wordwrap disabled and I was able to continue decreasing the width of the RichTextBox until the bad space was invisible, if not gone.

So, problem solved, but I'm still left with one question: Why does Microsoft hate me?



Related Topics



Leave a reply



Submit