Testing XML.Nodes for equality

I’m having trouble making the test for equality between XML.nodes work. XML.jl defines a nodes_equal function to test for equality, but I can’t make it work reliably.

For example, I have two XML.nodes called node and new_node. If I compare all the attributes tested in the function for equality like this:

        println(XML.write(node))
        println(XML.write(new_font))
        println(XML.nodes_equal(node, new_font))
        println("tags       : ", XML.tag(node) == XML.tag(new_font))
        println("types      : ", XML.nodetype(node) == XML.nodetype(new_font))
        println("attributes : ", XML.attributes(node) == XML.attributes(new_font))
        println(XML.attributes(node))
        println(XML.attributes(new_font))
        println("values     : ", XML.value(node) == XML.value(new_font))
        println("length     : ", length(XML.children(node)) == length(XML.children(new_font)))
        println("children   : ", all(XML.nodes_equal(ai, bi) for (ai,bi) in zip(XML.children(node), XML.children(new_font))))

I get the following results:

<font>                                      # This is `node`
  <b/>
  <sz val="18"/>
  <color theme="1"/>
  <name val="Calibri"/>
  <family val="2"/>
  <scheme val="minor"/>
</font>
<font>                                      # This is `new_font`
  <b/>
  <sz val="18"/>
  <color theme="1"/>
  <name val="Calibri"/>
  <family val="2"/>
  <scheme val="minor"/>
</font>
false                                       # They look the same, but they are not!
tags       : true
types      : true
attributes : false
nothing                                     # This is the source of the difference!
OrderedCollections.OrderedDict{String, String}()
values     : true
length     : true
children   : false

The two parent <font.../> nodes have no attributes, but in one this is shown as XML.attributes(node) == nothing while in the other, it results in XML.attributes(new_font) == OrderedCollections.OrderedDict{String, String}(). This difference isn’t a result of anything I have done intentionally. How can I avoid it?

I suspect that the source of the differences in the children is the same but haven’t investigated.

The first node is read from an XML file. The second node is constructed this way:

const font_tags = ["b", "i", "u", "strike", "outline", "shadow", "condense", "extend", "sz", "color", "name", "family", "scheme"]

function buildNode(tag::String, attributes::Dict{String, Union{Nothing, Dict{String, String}}}) :: XML.Node
    new_node = XML.Element(tag)
    for a in font_tags # Use this as a device to keep ordering constant
        if haskey(attributes, a)
            n=XML.Element(a)
            if !isnothing(attributes[a])
                for (k, v) in attributes[a]
                    n[k] = v
                end
            end
            push!(new_node, n)
        end
    end
    return new_node
end

and perhaps this is giving rise to the difference somehow.

How can I avoid this issue?

Well, one way to avoid the issue is like this:

XML.parse(XML.Node, XML.write(node)) == XML.parse(XML.Node, XML.write(new_font))

but this isn’t exactly elegant. Additionally, it forces the XML.nodetype to be Document, thus removing one element of the comparison.

Does anyone have a better solution? If not, I think the problem/fix lies with XML.jl.

I have created an XML.jl issue here and a (somewhat speculative) PR here