views:

148

answers:

2

I need to move sibling nodes before and after certain nodes. Here is the code im working with

<tabs>
     <tab>
          <name>Overview</name>
     </tab>
     <tab>
          <name>Testing</name>
     </tab>
     <tab>
          <name>Performance</name>
     </tab>
     <tab>
          <name>Braking</name>
     </tab>
</tabs>

I would like to move the tab with testing in it above Overview. How would I go about this using linq to XML?

+2  A: 

You can move the elements by removing them and then reinserting them at the desired position:

var doc = XDocument.Parse(@"<tabs>...</tabs>");

var tab = doc.Root.Elements().ElementAt(1);
tab.Remove();
doc.Root.AddFirst(tab);

Alternatively, you can create a new document from the existing elements in the desired order:

var doc = XDocument.Parse(@"<tabs>...</tabs>");

var tabs = doc.Root.Elements();

var result = new XDocument(
                 new XElement("tabs", 
                     tabs.ElementAt(1),
                     tabs.ElementAt(0),
                     tabs.ElementAt(2)));

I haven't tested it, but this might work:

void Swap(XElement a, XElement b)
{
    var c = new XElement("dummy");
    a.ReplaceWith(c);
    b.ReplaceWith(a);
    c.ReplaceWith(b);
}
dtb
I am really not interested in moving nodes to the first or last position I simply want to swap nodes. How would I swap Testing and Performance if the current tab is Testing?
Luke101
Also, your example assumes I know how many siblings there are? If I don't know how many siblings there are but i know the Testing element exists somewhere in the siblings how would I swap these elements?
Luke101
@Luke101: I've added a solution to swap to arbitrary XElements in an XDocument. I haven't tested it though.
dtb
wow..this is exactly what I needed.
Luke101
+2  A: 

Sorry, this is VB.NET and XML Literals, but it can be done old school in C#. The idea here is to use the Reverse extention method:

Sub Main()
        Dim tab = <tabs>
                      <tab>
                          <name>Overview</name>
                      </tab>
                      <tab>
                          <name>Testing</name>
                      </tab>
                      <tab>
                          <name>Performance</name>
                      </tab>
                      <tab>
                          <name>Braking</name>
                      </tab>
                  </tabs>
        Console.WriteLine(SwapElements("Testing", "Performance", tab).ToString)
        Console.ReadLine()
    End Sub
    Function SwapElements(ByVal firstElement As String, ByVal secondElement As String, ByVal tab As XElement) As XElement
        Dim swapped = tab.Elements.Where(Function(e) e.Value = firstElement Or e.Value = secondElement).Reverse
        Dim middle = tab.Elements.SelectMany(Function(e) e.ElementsAfterSelf.Where(Function(f) e.Value = firstElement).TakeWhile(Function(g) g.Value <> secondElement))
        swapped.ElementAt(0).AddAfterSelf(middle)
        Return <<%= tab.Name %>>
                   <%= tab.Elements.Select(Function(e) e.ElementsBeforeSelf.Where(Function(f) e.Value = firstElement)) %>
                   <%= swapped %>
                   <%= tab.Elements.Select(Function(e) e.ElementsAfterSelf.Where(Function(f) e.Value = secondElement)) %>
               </>
    End Function
Otaku
wow..after reading this several times I see that this is the most elegant way of solving this problem. Sorry I have already choosen the answer. I have implemented your solution instead. Thank you
Luke101
Happy to help Luke101
Otaku
What if the two elements are not adjacent to each other?
dtb
@dtb: Then you'd need more code. Query for all after `firstElement` and all before `secondElement` and do a `.Add` to `swapped`. I've updated the code above with 2 lines of code to reflect that.
Otaku