12

I know how to highlight part of a text with CSS using this script:

span.highlight {
    background-color: #B4D5FF;
}

Now I want to highlight the difference of two strings. For example given the following two strings:

this is number 123

and

that is number 124

I need to have a text highlighted like what I've shown here. Could somebody help me achieve this?

span.highlight {background-color: #B4D5FF}
<p>this is number 123</p>
<p>
th<span class="highlight">at</span> is number 12<span class="highlight">4</span>
</p>
Mohammad
  • 21,175
  • 15
  • 55
  • 84
B Faley
  • 17,120
  • 43
  • 133
  • 223

4 Answers4

35

You need to get all character of new string using split() method and iterate every character and compare it with character of old string.

highlight($("#new"), $("#old"));

function highlight(newElem, oldElem){ 
  var oldText = oldElem.text(),     
      text = '';
  newElem.text().split('').forEach(function(val, i){
    if (val != oldText.charAt(i))
      text += "<span class='highlight'>"+val+"</span>";  
    else
      text += val;            
  });
  newElem.html(text); 
}
.highlight {background-color: #B4D5FF}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="old">this is number 123</p>
<p id="new">that is number 124</p>

Also you can use simpler code as shown in bottom

highlight($("#new"), $("#old"));

function highlight(newElem, oldElem){ 
  newElem.html(newElem.text().split('').map(function(val, i){
    return val != oldElem.text().charAt(i) ?
      "<span class='highlight'>"+val+"</span>" : 
      val;            
  }).join('')); 
}
.highlight {background-color: #B4D5FF}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="old">this is number 123</p>
<p id="new">that is number 124</p>

And if you want to use less span check bottom code

highlight($("#new"), $("#old"));

function highlight(newElem, oldElem){ 
  var oldText = oldElem.text(),     
      text = '',
      spanOpen = false;  
  newElem.text().split('').forEach(function(val, i){  
    if (val != oldText.charAt(i)){   
      text += !spanOpen ? "<span class='highlight'>" : "";
      spanOpen = true;
    } else {       
      text += spanOpen ? "</span>" : "";
      spanOpen = false;  
    }  
    text += val;
  });
  newElem.html(text); 
}
.highlight {background-color: #B4D5FF}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="old">this is number 123</p>
<p id="new">that is number 124</p>
Mohammad
  • 21,175
  • 15
  • 55
  • 84
  • I like how concise your answer is! I guess with my solution you'd have less tags since mine looks for ranges instead of adding a tag for each character, but I like how elegant it is! – Omar Shehata Jun 26 '16 at 10:34
  • @OmarShehata See new example with less span. – Mohammad Jun 26 '16 at 10:51
  • Thank you for the code snippet. Like I commented on Omar's answer, if I changed "123" to "12", nothing would get highlighted, while I think something should be highlighted here to reflect the change. It seems that I have to use a diff library, as everyone has recommended :) – B Faley Jun 26 '16 at 18:41
  • @Meysam Best way to do this is using libray, but i answerd to your question and your example. – Mohammad Jun 26 '16 at 18:44
  • 1
    Alright, I marked you answer as accepted then :) – B Faley Jun 26 '16 at 18:54
  • Hey, you might be interested in https://stackoverflow.com/q/45344295/562769 :-) – Martin Thoma Jul 27 '17 at 07:49
  • I liked this solution, easy to implement, but it breaks the HTML code of anything I run it through sadly – Dennis May 17 '18 at 10:21
5

So you'll have to use Javascript to accomplish this, since it involves a fair bit of logic. As mentioned in the other answer(s), detecting difference in text is a little bit more complicated than just checking character by character.

However, for curiosity's sake, I put together this naive implementation:

CodePen

Here's what the function that gets the differences looks like:

function getDiff(text1,text2){
  //Takes in two strings 
  //Returns an array of the span of the differences 
  //So if given:
  // text1: "that is number 124"
  // text2: "this is number 123"
  //It will return:
  // [[2,4],[17,18]]
  //If the strings are of different lengths, it will check up to the end of text1 
  //If you want it to do case-insensitive difference, just convert the texts to lowercase before passing them in 
  var diffRange = []
  var currentRange = undefined
  for(var i=0;i<text1.length;i++){
    if(text1[i] != text2[i]){
      //Found a diff! 
      if(currentRange == undefined){
        //Start a new range 
        currentRange = [i]
      }
    }
    if(currentRange != undefined && text1[i] == text2[i]){
      //End of range! 
      currentRange.push(i)
      diffRange.push(currentRange)
      currentRange = undefined
    }
  }
  //Push any last range if there's still one at the end 
  if(currentRange != undefined){
    currentRange.push(i)
    diffRange.push(currentRange)
  }
  return diffRange;
}

The function getDiff takes in two strings, and returns the ranges at which they differ. This works great as long as the two strings are of the same length.

So to use this, all I do is:

var text1 = "that is number 124"
var text2 = "this is number 123"
var diffRange = getDiff(text1,text2)

Try changing the texts there in the CodePen and watch it update!

Once it gets these ranges, it then generates the html and inserts the <span> tags, and adds the element to the page. If you know your strings will always be the same, this could be enough. Otherwise, it would be best to look at a Javascript diff libary! (Jsdiff seems like a good library)

Omar Shehata
  • 1,054
  • 14
  • 18
  • Thank you for the code example. One thing with your sample is that if I changed "123" to "12", nothing gets highlighted, while I think something should be highlighted here to reflect the change :) – B Faley Jun 26 '16 at 10:49
  • Like I said, this works only if the strings will be of the same length. That's why everyone else recommended you use a diff library, to handle deletions like that :) – Omar Shehata Jun 26 '16 at 11:07
  • You are right, thanks a lot – B Faley Jun 26 '16 at 11:13
  • This is a great solution, thank you for contributing it @OmarShehata. The only thing I had to check to get it to work fully for my purposes was that I had to run it twice, with the arguments into the function swapped out. I'm sure there's a cleaner way to accomplish this but it worked for me. – James Stewart Jul 07 '22 at 08:10
2

'Diffing' text is more complicated than it might seem at first glance. It depends on exactly how you want to detect each difference, but in general, you want to detect:

  • changes at the character level (123 vs. 124)
  • Additions of text ('I have been to Sydney' vs. 'I have never been to Sydney).
  • Removal of text (reverse of previous example).

The tricky bit is detecting the differences and commonalities between the two strings. A naive implementation might just compare the strings character by character. The problem with that is, as soon as one character is added or removed, the rest of the string will appear as a difference, even though it's actually the same as the original text.

Your best bet is to use an existing diffing library. There are a lot out there for Javascript. This: https://github.com/kpdecker/jsdiff looks good, though it might be overkill for what you need. Basically though - don't try to invent this yourself unless you want to learn a lot about text parsing!

Ben Hull
  • 7,524
  • 3
  • 36
  • 56
0

Of course the previous answer is correct, but if you have a very specific format to compare against and don't want to use an external framework a javascript like this could be used to group your characters and style them with css:

<html>

<style type="text/css">
.highlighted{
    background-color:blue;
}
</style>

<script type="text/javascript">

function init(){
    var output = document.getElementById("output");
    output.appendChild(highlightChanges("12777345aaaabbbbb","1277845abaababababababadsrfgadsg"));
}

var highlightChanges = function(str, compareStr){
    var strlength = str.length > compareStr.length ? compareStr.length : str.length;
    var allStr = document.createElement("span");
    var hl = null;
    var nohl = null;
    for(i = 0; i < strlength ; i++){
        if(str.charAt(i) != compareStr.charAt(i)){
            if(nohl != null){ 
                allStr.appendChild(nohl);
                nohl = null;
            }
            if(hl != null) hl.innerHTML += str.charAt(i);
            else{
                hl = document.createElement("span");
                hl.classList.add("highlighted");
                hl.innerHTML = str.charAt(i);
            } 
        }
        else{
            if(hl != null){
                allStr.appendChild(hl);
                hl = null;
            }
            if(nohl != null) nohl.innerHTML += str.charAt(i);
            else{
                nohl = document.createElement("span");
                nohl.innerHTML = str.charAt(i);
            } 
        }

        <!-- Fix by James Moberg to prevent premature end of comparison-->
        if(hl != null){
            allStr.appendChild(hl);
        } else if (nohl != null){
            allStr.appendChild(nohl);
        }
    }
    return allStr;
}
</script>

<body onload="init()">
    <div id="output"></div>
</body>

</html>
elfwyn
  • 568
  • 2
  • 11
  • 33
  • FYI: [4 yrs later] You'll need to add an extra check at the end of the "for" loop to determine if h1!==null and then appendChild or the result may be truncated too early and the full result won't be returned. – James Moberg Jun 18 '20 at 23:24
  • Correction: if h1 or nohl is not null, then appendChild the variable or the full result won't be returned. Here's the changes I made https://gist.github.com/JamoCA/94d97a3e4250e0810b8b75423a1dbbc3 – James Moberg Jun 18 '20 at 23:36