39

How can we compare two HTML elements whether they are identical or not ?

I tried this thing but no luck

<div class="a"> Hi this is sachin tendulkar </div>
<div class="a"> Hi this is sachin tendulkar </div>

And then on button click, I call a function check()

var divs = $(".a");
alert(divs.length);    // Shows 2 here which is correct
if (divs.get(0) == divs.get(1)) alert("Same");

But this is not working. Everything is same in two divs. Apart from this How can we compare whether two HTML elements are completely idential or not. Including their innerHTML, className, Id, and their attributes.

Is this doable ?

Actually, I have two HTML documents and I want to remove the identical content from both of them So two elements can have same id.

PS: Updating after Crowder's valuable comments. If we compare two elements as strings, we would not get a match as their order of attributes may vary So the only option is to iterate through each child attribute and match. I still have to figure out completely working implementation strategy.

Sachin Jain
  • 21,353
  • 33
  • 103
  • 168
  • 3
    *"Including their...Id..."* If their `id` values match, the HTML in question is invalid. `id` values **must** be unique on the page. – T.J. Crowder May 21 '12 at 05:30
  • I'm assuming that you do want to treat two elements with the same attributes listed in a different order as equivalent...? E.g., `
    ...
    ` and `
    ...
    ` should be a match?
    – T.J. Crowder May 21 '12 at 05:35
  • But then they are not *identical*... – Derek 朕會功夫 May 21 '12 at 05:36
  • @T.J.Crowder Um... What does `hence` mean? – Derek 朕會功夫 May 21 '12 at 05:38
  • 1
    @Derek: http://www.thefreedictionary.com/hence Basically, "Hence the question" in this context means "That's why I asked the question." – T.J. Crowder May 21 '12 at 05:39
  • @blunderboy—first you need to define your criteria for "identical". In javascript, `objA == objB` only if `objA` and `objB` reference the same object. DOM elements are objects, so the same is true for them. – RobG May 21 '12 at 05:47
  • 2
    @RobG: He gives a pretty good set of criteria in the question: *"Including their innerHTML, className, Id, and their attributes."* – T.J. Crowder May 21 '12 at 05:49
  • Use regexp. Believe me, it's the best tool to parse HTML. Everyone here surely will advice it to you. Would you?... Why there's no hands up?... – Gandi May 21 '12 at 07:31
  • 1
    @T.J.Crowder and @Derek : That `hence` question should goto english.stackexchange.com :P – Rakesh Juyal May 21 '12 at 08:07
  • @T.J.Crowder—if it's just the innerHTML, that should be sufficient (though as well covered it's likely not viable or practical). Otherwise, the order of attributes and properties may not be retained if elements have been modified by script or otherwise processed, and is attribute order important anyway? That doesn't affect the *DOM element* equivalence, but might affect the *HTML markup* equivalence. – RobG May 21 '12 at 09:51
  • @RobG: I've never known whether the order of attributes was preserved. Couldn't immediately find anything about attribute order in the DOM specs, so I went empirical. Looks like it isn't: http://jsbin.com/agukag I get different results on different browsers for that. So if "equivalent" means "with the same text, with equivalent children, and with equivalent attributes", sadly the complex answer is the way to go, because apparently we can't rely on a textual comparison of `innerHTML` for equivalence (by that definition). – T.J. Crowder May 21 '12 at 10:06
  • @T.J.Crowder: would sorting the attributes to equalize the order in 2 elements help? See http://jsbin.com/ufahaj/ – KooiInc May 21 '12 at 12:39
  • @KooiInc: Doesn't seem to, for `innerHTML` purposes: http://jsbin.com/efigeg Chrome and Firefox still give mis-matching markup (IE9 seems to sort the attributes itself anyway). You could try cloning the nodes, removing the attributes, then adding them back in sorted order, but at that point I think we've reached the point of dimishing returns. The recursive-descent version in my answer seems to work and doesn't have to worry about browser quirks. – T.J. Crowder May 21 '12 at 14:10

4 Answers4

69

You can use:

element1.isEqualNode(element2);

In your specific example:

var divs = $(".a");
if ( divs.get(0).isEqualNode(divs.get(1)) ) alert("Same");

The DOM Level 3 Core Spec has all the details. Essentially this returns true of the two nodes have matching attributes, descendents, and the descendents' attributes.

There's a similar .isSameNode() that returns true only if both elements are the same node. In your example, these are not the same nodes, but they are equal nodes.

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
Keen
  • 1,327
  • 1
  • 17
  • 25
  • 4
    This is the best answer. Everything else is just doing string or attribute comparisons. – Tyson Cadenhead Dec 02 '13 at 21:52
  • This is a good answer only if you don't have to consider attributes. from the referred docs: "The following string attributes are equal: nodeName, localName, namespaceURI, prefix, nodeValue. ". Meaning- no other attributes are tested. – J. Ed Mar 04 '14 at 16:39
  • 2
    @sJhonny The item that starts with, "The attributes NamedNodeMaps are equal," means attributes are tested. – Keen Mar 04 '14 at 16:53
  • 2
    For the record, isEqualNode is [not supported in oldIE](http://www.quirksmode.org/dom/core/#t135). It works in IE9+, though. Other desktop browsers are fine, and mobile browser support [is excellent](http://www.quirksmode.org/dom/core/mobile.html#t135), too. – hashchange Apr 08 '14 at 14:20
  • Perfect answer. One line of portable code that exactly solves the problem. No need for any dirty hacks with string comparisons. Thanks. – rsp Jul 30 '14 at 12:07
  • One question: do the nodes need to be [normalized](https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize) first? In the [DOM Core there is](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isEqualNode): "Note that normalization can affect equality; to avoid this, nodes should be normalized before being compared." – rsp Jul 30 '14 at 12:25
  • This is a very simple element answer that worked perfectly. @rsp I didn't have to normalize mine. I looped through elements and everything worked perfectly for a "custom diff tool" `if(!elm1.isEqualNode(elm2)){ $(elm1).addClass('diffCurrent'); $(elm2).addClass('diffRevision'); }` great answer @Keen, +1 – David J. Davis Feb 02 '16 at 12:28
  • This doesn't work for elements that are identical except for the *sequence* of style properties (sequence, not values). If you know how to handle this situation, would you mind sharing thoughts: https://stackoverflow.com/questions/56681649/compare-two-html-elements-where-order-of-style-properties-is-unimportant – Crashalot Jun 20 '19 at 08:12
  • Note that *isEqualNode* compares ID, so if two otherwise equal nodes have different IDs, they will not be considered equal. – RobG Jun 20 '19 at 11:02
  • @RobG yes question was amended to fix this. thanks! – Crashalot Jun 21 '19 at 23:13
34

Update

See Keen's answer and also ccproj's answer to a closely-related question. There's isEqualNode for this, but it compares class and style attributes as text, so the same set of classes or the same set of style properties in different orders will make it think nodes aren't equivalent. ccprog's answer handles that.

Original Answer

(See below for a complete, largely-untested, and certainly un-refactored off-the-cuff solution. But first, the bits and pieces of it.)

Comparing their innerHTML is easy:

if (divs[0].innerHTML === divs[1].innerHTML)
// or if you prefer using jQuery
if (divs.html() === $(divs[1]).html()) // The first one will just be the HTML from div 0

...although you have to ask yourself whether these two elements are equivalent according to your criteria:

<div><span class="foo" data-x="bar">x</span></div>
<div><span data-x="bar" class="foo">x</span></div>

...because their innerHTML will be different (at least on Chrome, and I suspect on most if not all browsers). (More on that below.)

Then you need to compare all of their attributes. As far as I know, jQuery doesn't give you a means of enumerating the attributes, but the DOM does:

function getAttributeNames(node) {
  var index, rv, attrs;

  rv = [];
  attrs = node.attributes;
  for (index = 0; index < attrs.length; ++index) {
    rv.push(attrs[index].nodeName);
  }
  rv.sort();
  return rv;
}

Then

var names = [getAttributeNames(div[0]), getAttributeNames(div[1])];
if (names[0].length === names[1].length) {
    // Same number, loop through and compare names and values
    ...
}

Note that by sorting the arrays above, I'm assuming the order of their attributes is not significant in your definition of "equivalent." I hope that's the case, because it doesn't seem to be preserved, as I get different results from different browsers when running this test. That being the case, we have to come back to the innerHTML question, because if the order of attributes on the elements themselves is not significant, then presumably the order of attributes on descendant elements shouldn't be significant. If that's the case, you'll need a recursive function that checks the descendants according to your definition of equivalent, and not use innerHTML at all.

Then there's the concern raised by this subsequent question: What if the elements have different-but-equivalent style attributes? E.g.:

<div id="a" style="color: red; font-size: 28px">TEST A</div>
<div id="b" style="font-size: 28px; color: red">TEST B</div>

My answer there addresses it by looping through the contents of the elements' style objects, like this:

const astyle = div[0].style;
const bstyle = div[1].style;
const rexDigitsOnly = /^\d+$/;
for (const key of Object.keys(astyle)) {
    if (!rexDigitsOnly.test(key) && astyle[key] !== bstyle[key]) {
        // Not equivalent, stop
    }
}
// Equivalent

Sadly, as I say in that answer:

Note that the above will fail if (one of them has color: red and the other has color: #ff0000), at least on some browsers, because when a style property uses a string value, usually you get the value the way it was supplied, not normalized. You could use getComputedStyle to get the computed (ish) value instead, but then we get into issues around CSS applicability: Two elements with exactly the same markup can have different values from getComputedStyle because of where they are in the DOM and the CSS applied to them as a result. And getComputedStyle doesn't work on nodes that aren't in a document, so you can't just clone the nodes to factor out that issue.

But you should be able to put something together from the pieces above to compare two elements according to your criteria.

More to explore:


The question interested me strangely, so I kicked around at it for a while, and came up with the following. It's mostly untested, could use some refactoring, etc., but it should get you most of the way there. I do, again, assume the order of attributes is not significant. The below assumes even the slightest difference in the text is significant.

function getAttributeNames(node) {
  var index, rv, attrs;

  rv = [];
  attrs = node.attributes;
  for (index = 0; index < attrs.length; ++index) {
    rv.push(attrs[index].nodeName);
  }
  rv.sort();
  return rv;
}

function equivElms(elm1, elm2) {
  var attrs1, attrs2, name, node1, node2;

  // Compare attributes without order sensitivity
  attrs1 = getAttributeNames(elm1);
  attrs2 = getAttributeNames(elm2);
  if (attrs1.join(",") !== attrs2.join(",")) {
    display("Found nodes with different sets of attributes; not equiv");
    return false;
  }

  // ...and values
  // unless you want to compare DOM0 event handlers
  // (onclick="...")
  for (index = 0; index < attrs1.length; ++index) {
    name = attrs1[index];
    if (elm1.getAttribute(name) !== elm2.getAttribute(name)) {
      display("Found nodes with mis-matched values for attribute '" + name + "'; not equiv");
      return false;
    }
  }

  // Walk the children
  for (node1 = elm1.firstChild, node2 = elm2.firstChild;
       node1 && node2;
       node1 = node1.nextSibling, node2 = node2.nextSibling) {
     if (node1.nodeType !== node2.nodeType) {
       display("Found nodes of different types; not equiv");
       return false;
     }
     if (node1.nodeType === 1) { // Element
       if (!equivElms(node1, node2)) {
         return false;
       }
     }
     else if (node1.nodeValue !== node2.nodeValue) {
       display("Found nodes with mis-matched nodeValues; not equiv");
       return false;
     }
  }
  if (node1 || node2) {
    // One of the elements had more nodes than the other
    display("Found more children of one element than the other; not equivalent");
    return false;
  }

  // Seem the same
  return true;
}

Live examples:

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    Thanks T.J.Cowder..Gonna try out your solution and will tell how does it work. Thanks a lot btw – Sachin Jain May 21 '12 at 05:26
  • How about comparing their `outerHTML`? – Derek 朕會功夫 May 21 '12 at 05:32
  • 1
    @Derek: There is no guarantee that two elements with the same attributes in a different order will result in the same `outerHTML` string. It doesn't work if you wrap and use `innerHTML`; see my comment on other answers. – T.J. Crowder May 21 '12 at 05:34
  • 2
    @T.J.Crowder—if innerHTML doesn't work on the elements themselves, then it won't work on their content either for the same reason. – RobG May 21 '12 at 05:50
  • @RobG: LOL, I was just editing, having realized the same thing. :-) – T.J. Crowder May 21 '12 at 05:57
  • @T.J.Crowder: about using `outerHTML` didn't the OP include the order (of attributes etc.) too? In that case `outerHTML` would be usable imho. See http://jsfiddle.net/cS6eA/ – KooiInc May 21 '12 at 06:16
  • @KooiInc: I didn't see anything about order of attributes (which is why I asked the OP about it). For the purposes of comparing two documents for duplicated content, I wouldn't expect the order of attributes to be significant. – T.J. Crowder May 21 '12 at 06:32
  • Would you and @RobG still consider this the best answer today? The `isEqualNode` answer by Keen almost works for us, except we need equality checks to ignore the order of style properties (order, not values). That is, A and B should be equal if their style properties are the same but listed in different orders. Any suggestions? https://stackoverflow.com/questions/56681649/compare-two-html-elements-where-order-of-style-properties-is-unimportant – Crashalot Jun 20 '19 at 08:18
  • @Crashalot - Yeah, about the same. I've answered that question and included the same info above. :-) – T.J. Crowder Jun 20 '19 at 08:48
  • 1
    @T.J.Crowder thanks so much for the fast reply and always being such a big contributor on Stack Overflow! – Crashalot Jun 20 '19 at 08:58
  • Actually it turns out Keen's answer may be outdated since the spec @Keen quotes has been superseded by a new one where IsEqualNodes does not consider children. So more work is required if you want to account for descendants. – Crashalot Jun 21 '19 at 23:20
  • To be exact, DOM 4 no longer mentions the child list as a criterium: https://www.w3.org/TR/dom/#concept-node-equals – ccprog Jun 21 '19 at 23:33
1

Why not do it the easy way?

<div id="div1"><div class="a"> Hi this is sachin tendulkar </div></div>
<div id="div2"><div class="a"> Hi this is sachin tendulkar </div></div>

if($('#div1').html() == $('#div2').html())
    alert('div1 & div2 are the same');        
else
    alert('div1 & div2 are different');

http://jsfiddle.net/5Zwy8/1/

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
ephemeron
  • 396
  • 2
  • 12
  • 1
    I am not allowed to edit the HTML code So i can not place a wrapper div around the elements. – Sachin Jain May 21 '12 at 05:25
  • 2
    There is no guarantee that elements with equivalent attributes in a different order will be sorted by `innerHTML`. – T.J. Crowder May 21 '12 at 05:26
  • if you can't place wrapper around, maybe you can use the code i mention + http://stackoverflow.com/questions/2419749/get-selected-elements-outer-html. This way you don't need to have a wrapper. – ephemeron May 21 '12 at 05:26
  • And in fact, the attribute order is preserved, by Chrome at least: http://jsbin.com/amidip So this just does not work (unless, of course, attribute order is significant and a different order *should* result in no match). – T.J. Crowder May 21 '12 at 05:32
  • 1
    @blunderboy if you're not allowed to change the HTML code, how are you supposed to achieve the result of removing divs from it? – Mr Lister May 21 '12 at 06:09
0

How does this codes?

var d1 = document.createElement("div");
d1.appendChild(divs.get(0));
var d2 = document.createElement("div");
d2.appendChild(divs.get(1));
if (d1.innerHTML == d2.innerHTML) ?