[Coding] Displaying String diffs
Posted by Khatharsis on April 4, 2014
I hacked together this bit of trickery from various articles and coffee-fueled ingenuity. Or maybe just illusions of grandeur. After all, I happen to like this cobbled-together feature, but my clients may not. Nonetheless, this was an interesting puzzle to figure out and when it finally all came together, I got that nice rush of satisfaction for solving it. And what’s more, I’ll be sharing it in the hopes someone else will find it useful.
The basic premise is I am comparing two files in a similar fashion to Beyond Compare and other similar products. The actual comparison is much simpler and you can think of it as an array of strings of a set length (e.g., 10). These strings are extracted from the larger file. If there are any differences between the characters, I want to display it as red. To further up the aesthetic value, I wanted to add line numbers to make it easier for someone to go to the file and figure out which line is different (remember, I’m extracting the strings to compare so the line number displayed on the webpage will be different from the line number in the file).
The backend code generates the actual HTML output for displaying a character as red. Let’s look at a simple example.
String A: 1234567890
String B: 1234567891
My output from .NET would look something like:
String A: 123456789<span class='red'>0</span>
String B: 123456789<span class='red'>1</span>
Of course, this will generate a lot of extra span tags if each character happens to not match, but since this is a small comparison and I am not expecting major differences to be present, it will do (until such a time comes that optimization is absolutely necessary).
My HTML code for containing these side-by-side strings uses a table (gasp, the horror–this is why I’m not a web designer), but you can/should use whatever you see fit:
<table id="string-compare"> <tr> <td id="tdStringA" runat="server" clientidmode="Static"></td> <td id="tdStringB" runat="server" clientidmode="Static"></td> </tr> </table>
I have string arrays that I’m actually populating tdStringA and tdStringB with. I generate the full HTML string for set A a little differently than set B. Take a look:
stringA[];
stringB[];
// Some code to populate stringA and stringB goes here.
tdStringA.InnerHtml = "<code>";
foreach (string line in stringA)
{
tdStringA.InnerHtml += "<span>" + line + "</span><br/>";
}
tdStringA.InnerHtml += "</code>";
tdStringB.InnerHtml = "<code>";
tdStringB.InnerHtml += String.Join("<br/>", stringB);
tdStringB.InnerHtml += "</code>";
For both stringA and string B, I wrap whatever I end up generating in a code tag (you may not need to, but I did for aesthetic reasons). For stringA, I iterate through each item in the array and wrap it in a span tag with a linebreak appended at the end. For stringB, I just Join() the items together with a linebreak as the glue.
Why did I add a span tag to stringA? Well, there’s a neat CSS feature :before (or ::before). Basically what this does is add text before the specified tag. Couple this feature with the CSS counter and you can easily number things without hardcoding it. I only want to add line numbers once, hence the slightly different HTML generation for stringA.
Below is sample CSS code combining these two features:
#string-compare td
{
line-height: 1;
counter-reset: br;
}
#string-compare td:first-child code>span:before
{
counter-increment: br;
content: counter(br);
display: inline-block;
width: 1.5em;
border-right: 1px solid;
text-align: right;
margin: 0 1em 0 -1em;
padding:0 .5em 0 1em;
}
The problem is, it was difficult to see the differences when there are 30 lines of 10-char strings and only one letter is red. So, I added a background <span class='gray-bg'> wrapper to the line that has a discrepancy. The gray background added a little bit, but it’s quite light so it didn’t add a lot. I could have darkened it or done any other variety of visual changes, but I settled on making the line number stand out. Because that’s what the user is ultimately going to want to know–which line number is different.
The next problem is CSS is limited in its logic capabilities and since it is indirectly adding additional text, the line numbers are not actually part of the DOM tree. They’re not even in the source code. They just magically appear. That means jQuery cannot locate them.
So, I took a different approach. I removed the counter elements. I took out the CSS display properties and stuck them in a class:
.line-numbers
{
display: inline-block;
width: 1.5em;
border-right: 1px solid #000;
text-align: right;
margin: 0 1em 0 -1em;
padding:0 .5em 0 1em;
}
Then, I wrote a JavaScript function that gets called when the page finishes loading:
this.addLineNumbers = function() {
var spans = $('#tdStringA code > span'),
item;
for (var i = 0; i < spans.length; i++) {
item = $(spans[i]);
if ($(item).children().has("span").hasClass("gray-bg")) {
$(item).prepend('<span class="line-numbers red">' + (i + 1) + "</span>");
}
else {
$(item).prepend('<span class="line-numbers">' + (i + 1) + "</span>");
}
}
};
What I’m doing here is grabbing the set/array of spans I generated from stringA – one per item in stringA. Then, I loop through each of the spans checking if the span contains an inner span with the CSS class “gray-bg”. This is the main indicator this particular line has a difference and therefore, the line number needs to be colored red. I prepend the span with a line number span and the appropriate class set (red or not).
And you can see the results and play around with the code here. Enjoy!