views:

213

answers:

1

I'm not quite sure how to ask this, or if this even exists, but I have a need to merge two xelements with one taking precendence over the other, to become just one element. This is may be thought of as similar to the way Outlook 2007 merges two contacts that seem to be the same based of the contact name.

The preference here is VB.NET and Linq, but any language would be helpful if it demonstrates how to do this without me coding to manually pick apart and and resolve every single element and attribute.

For example, let's say I have two elements. Humor me on them being as different as they are.

1.

<HockeyPlayer height="6.0" hand="left">
<Position>Center</Position>
<Idol>Gordie Howe</Idol>
</HockeyPlayer>

2.

<HockeyPlayer height="5.9" startinglineup="yes">
<Idol confirmed="yes">Wayne Gretzky</Idol>
</HockeyPlayer>

The result of a merge would be

<HockeyPlayer height="6.0" hand="left" startinglineup="yes">
<Position>Center</Position>
<Idol confirmed="yes">Gordie Howe</Idol>
</HockeyPlayer>

Notice a few things: the height attribute value of #1 overrode #2. The hand attribute and value was simply copied over from #1 (it doesn't exist in #2). The startinglineup attribute and value from #2 was copied over (it doesn't exist in #1). The Position element in #1 was copied over (it doesn't exist in #2). The Idol element value in #1 overrides #2, but #2's attribute of confirmed (it doesn't exist in #1) is copied over.

Net net, #1 takes precendence over #2 where there is a conflict (meaning both have the same elements and/or attributes) and where there is no conflict, they both copy to the final result.

I've tried searching on this, but just can't seem to find anything, possibly because the words I'm using to search are too generic. Any thoughts or solutions (esp. for Linq)?

+3  A: 

Here's a console app that produces the result listed in your question. It uses recursion to process each sub element. The one thing it doesn't check for is child elements that appear in Elem2 that aren't in Elem1, but hopefully this will get you started towards a solution.

I'm not sure if I would say this is the best possible solution, but it does work.

Module Module1

Function MergeElements(ByVal Elem1 As XElement, ByVal Elem2 As XElement) As XElement

    If Elem2 Is Nothing Then
        Return Elem1
    End If

    Dim result = New XElement(Elem1.Name)

    For Each attr In Elem1.Attributes
        result.Add(attr)
    Next

    Dim Elem1AttributeNames = From attr In Elem1.Attributes _
                              Select attr.Name

    For Each attr In Elem2.Attributes
        If Not Elem1AttributeNames.Contains(attr.Name) Then
            result.Add(attr)
        End If
    Next

    If Elem1.Elements().Count > 0 Then
        For Each elem In Elem1.Elements
            result.Add(MergeElements(elem, Elem2.Element(elem.Name)))
        Next
    Else
        result.Value = Elem1.Value
    End If

    Return result
End Function

Sub Main()
    Dim Elem1 = <HockeyPlayer height="6.0" hand="left">
                    <Position>Center</Position>
                    <Idol>Gordie Howe</Idol>
                </HockeyPlayer>

    Dim Elem2 = <HockeyPlayer height="5.9" startinglineup="yes">
                    <Idol confirmed="yes">Wayne Gretzky</Idol>
                </HockeyPlayer>

    Console.WriteLine(MergeElements(Elem1, Elem2))
    Console.ReadLine()
End Sub

End Module

Edit: I just noticed that the function was missing As XElement. I'm actually surprised that it worked without that! I work with VB.NET every day, but it has some quirks that I still don't totally understand.

Dennis Palmer
This is brilliant, thank you. I really appreciate the work and insight you've put into this!
Otaku