4

Can you write a function that sees if two pieces of DOM are equivalent? It should ignore irrelevant whitespace. Ideally, it should also ignore empty attributes.

So the following should be equal:

<div style="">
<div>a</div>
</div>

and

<div><div>a</div></div>
Joe
  • 7,922
  • 18
  • 54
  • 83
  • 2
    possible duplicate: http://stackoverflow.com/questions/3176962/jquery-object-equality – Steven V Jul 29 '13 at 20:27
  • 1
    What have your tried? – Naftali Jul 29 '13 at 20:27
  • @defaultNINJA No, you can't. `$('#selector') == $(this)` isn't correct at all. That just checks whether the objects are literally the same object, not whether two (possible different) pieces of DOM are the same. – user229044 Jul 29 '13 at 20:33
  • @Neal. I tried the equivalence function inside Jasmine-jquery. I tried just wrapping things in jQuery. Nothing seems to handle the difference between tabs and spaces and other problems. – Joe Jul 29 '13 at 21:23
  • I don't think you should use jQuery to fix webpages. After all you'd fix it only for the current call of the webpage. –  Jul 30 '13 at 14:11
  • @Joe Made a little function that I believe may work for you. Let me know and I can tweak or modify if needed. – Iron3eagle Jul 30 '13 at 15:33
  • Step 1: define clearly the rules for "equivalent". Why is `style=""` the same as not including that attribute? (Clearly omitting, say, `checked` or `readonly` isn't equivalent to setting them to no value.) What about whitespace? What about significant and insignificant whitespace? (Doing this correctly would involve reimplementing the algorithm for collapsing whitespace that the browser uses - unfortunately `innerText` is IE-only.) In your case the two samples will actually render differently if the `div`s are `display: inline-block` - the first will be `" a "` and the second one just `"a"` – millimoose Jul 30 '13 at 16:15
  • Basically I think this question is too vaguely defined to be answerable in the scope of a SO question - without you hammering out the requirements it'll likely just lead to an endless back and forth as you discover any given answer is missing an edge case. – millimoose Jul 30 '13 at 16:16
  • Equivalent means they html of them will be interpreted the same by the browser. What the browser will interpret the same is a long, messy, poorly defined question but that's not really my fault. The end result is a well defined problem that I have good reasons for wanting to solve. – Joe Jul 31 '13 at 15:07
  • Whitespace is all equivalent in html from what I understand. checked and readonly with no value means something, but style with no value means nothing, so that's the difference. – Joe Jul 31 '13 at 15:08
  • @Joe The fact it's not your fault doesn't mean you're exonerated from having unambiguous requirements. You're just punting discovering the edge cases to anyone answering, when it's clearly research you could do. And I *know* why `style=""` is equivalent to omitting it, my point is it's up to you to clearly define these rules up front. – millimoose Jul 31 '13 at 17:30
  • @Joe And whitespace is clearly not equivalent in your two samples, because in the first one there is whitespace (a newline) between the starting tag of the outer `
    ` and that of the inner `
    ` - but not in the second example where they follow immediately. In the first case that newline would collapse to a single space, and then *probably* wouldn't be rendered because whitespace between *block* elements is insignificant. However, it's not between *inline* elements, and whether an element is block or inline is controlled by CSS.
    – millimoose Jul 31 '13 at 17:32
  • @Joe And that's just scratching the surface of the edge cases that such a semantic comparison of arbitrary chunks of HTML would have to consider. Hence my position that this question has an unreasonable scope. It would be daunting even if you had listed an exhaustive list of requirements - and even then it'd likely get nuked because once you have a list of requirements why not start implementing them? As it stands all you're going to get are woefully incomplete answers that will be useless to you. – millimoose Jul 31 '13 at 17:33
  • @millimoose My unambiguous requirement is that it test for equality is true when the html would be rendered the same by the browser. Do I need to know what the exact rules are to ask the question? No, not really. Someone either has a function for me or they don't. It is unlikely that someone would write non-trivial new code for me because I asked. Therefore, there really isn't a good reason for me to figure out everything about the browser rendering engines. – Joe Jul 31 '13 at 20:31
  • @Joe I never said "ambiguous" as in it's unclear what you want. I said "vague" in that it's impossible to say whether any solution anyone has satisfies your requirement, short of a builtin browser function that exists and is specified to do this. Since you haven't found this on Google, the smart money is on "it doesn't", meaning you haven't posed an answerable question - and if you're just as fine with this function not existing, then you're not really facing a problem you need to resolve. – millimoose Aug 01 '13 at 08:02
  • It's not unclear what I want. I want a function that tests equality on the grounds that the html is rendered the same way. It might be difficult to know that such a function is perfectly bug free, but you can write robust testing. And notice I said I want something not fooled by whitespace differences and IDEALLY not by blank attributes. Granted you pointed out some blank attributes are not irrelevant. That's a valid point, but doesn't really change the issue. – Joe Aug 01 '13 at 20:06
  • 1
    It's pretty ridiculous to say I shouldn't ask on Stackoverflow because I haven't found it on Google. You can say that about any almost any question asked here. A lot of times these Google searches give Stackoverflow answers so how can you say don't ask on Stackoverflow if you don't find it? – Joe Aug 01 '13 at 20:10

1 Answers1

2

I've created a function that will compare the lengths of the jQuery objects and compare the tag names of each child element. So far it seems to work fairly well in the basic fiddle I've created for it. Here is the code, let me know how it works for you and if there are any bugs/improvements needed to be made. This does not compare the inner text though, however if you need that I think it should be easy enough to tweak.

UPDATE 1:

Changed to use recursion instead of a for loop, and it now compares the inner text of each element as well. If you don't need the text checked just delete that comparison.

UPDATE 2:

Per the OP comment, I've found and edited a few extra functions that can be utilized to compare CSS of the elements and check that they are the same. If not then the element are not the same and will return false.

jQuery:

This first function is a jQuery function that will return an object of the CSS properties.

(function ($) {
    $.fn.getStyleObject = function () {
        var dom = this.get(0);
        var style;
        var returns = {};
        if (window.getComputedStyle) {
            var camelize = function (a, b) {
                return b.toUpperCase();
            }
            style = window.getComputedStyle(dom, null);
            for (var i = 0; i < style.length; i++) {
                var prop = style[i];
                var camel = prop.replace(/\-([a-z])/g, camelize);
                var val = style.getPropertyValue(prop);
                returns[camel] = val;
            }
            return returns;
        }
        if (dom.currentStyle) {
            style = dom.currentStyle;
            for (var prop in style) {
                returns[prop] = style[prop];
            }
            return returns;
        }
        return this.css();
    }
})(jQuery);

Next function defined is used to compare the objects returned by the previous function.

function arrays_equal(a, b) {
    var result = true;
    $.each(a, function (key, value) {
        if (!b.hasOwnProperty(key) || b[key] !== a[key]) {
            result = false;
        }
    });

    return result;
}

This is the primary function that checks for equality between the two elements. It will make a call to the added jQuery function to retrieve an object made of the CSS properties and then compares the two objects the second function that was added.

 function equalElements(a, b) {
    if (a.length != b.length) {
        return false;
    }

    if (a.children().length != b.children().length) {
        return false;
    } else {
        if (a.children().length > 0) {
            var x = a.children().first();
            var y = b.children().first();
            equalElements(x, y);
        }

        if (a.get(0).tagName != b.get(0).tagName) {
            return false;
        }

        if (a.text() != b.text()) {
            return false;
        }

        var c = a.getStyleObject();
        var d = b.getStyleObject();

        if (!arrays_equal(c, d)) {
            return false;
        }
    }

    return true
}

HTML: (Used to compare and test function)

<div id="div1">
    <div>a</div>
</div>
<div id="div2" style="">
    <div>a</div>
</div>
<div id="div3" style=""></div>
<div id="div4" style="">
    <div>a</div>
    <div>a</div>
</div>
<div id="div5" style="">
    <div>b</div>
</div>
<div id="div6" style="width: 50px;">
    <div>a</div>
</div>
<div id="div7" style="width: 50px;">
    <div>a</div>
</div>
<div id="div8" style="width: 25px;">
    <div>a</div>
</div>
<div id="div9" style="width: 50px; height: 10px;">
    <div>a</div>
</div>
<ul id="list1">
    <li>a</li>
</ul>

Test Code: (Sample see fiddle for full test)

var a = $("#div1");
var b = $("#div2");
var same = equalElements(a, b);

if (same) {
    alert("div1 is equal to div2");
}
Iron3eagle
  • 1,077
  • 7
  • 23
  • Updated for better children looping. Decided recursion would better handle this. – Iron3eagle Jul 30 '13 at 16:04
  • It's a nice function, but I'm sure you realize that it is a little limited. It doesn't look at some relevant things like what styles the elements have on them. For example, if an element has style="height:5px;" that should not be equivalent. – Joe Jul 31 '13 at 15:15
  • @Joe Do you want that to be part of the check? In your example you said you wanted it to be the same, one having style the other having none, so I figured that didn't matter. I can try and tweak it and should be able to for you. I just need more specs on what it is you need. – Iron3eagle Jul 31 '13 at 15:52
  • @Joe Updated the function with some extra code to test the CSS for equality. See updated code and fiddle for testing against edge cases. – Iron3eagle Jul 31 '13 at 18:59