Search code examples
c#xmlsortinglinq-to-xmlxelement

Sorting an XDocument based on name of XElement


I have an XML file like this:

 <file name="picture1.jpg"/>
 <file name="file1.txt"/>
 <folder name="subfolder">
      <file name="picture2.jpg"/>
      <file name="file2.txt"/>
      <folder name="anotherfolder">
           <file name="file3.txt"/>
      </folder>
 </folder>
 <folder name="anotherfolder">
 </folder>

It needs to be sorted like this:

<folder name="anotherfolder">
 </folder>
 <file name="file1.txt"/>
 <file name="picture1.jpg"/>
 <folder name="subfolder">
      <folder name="anotherfolder">
           <file name="file3.txt"/>
      </folder>
      <file name="file2.txt"/>
      <file name="picture2.jpg"/>
 </folder>

The code I have to sort is:

public static XDocument Sort(XDocument file)
    {
        return new XDocument(Sort(file.Root));
    }
    private static XElement Sort(XElement element)
    {
        XElement newElement = new XElement(element.Name,
            from child in element.Elements()
            orderby child.Name.ToString()
            select Sort(child));
        if (element.HasAttributes)
        {
            foreach (XAttribute attrib in element.Attributes())
            {
                newElement.SetAttributeValue(attrib.Name, attrib.Value);
            }
        }
        return newElement;
    }

It does the job...but only partly. It sorts FILE based on name attribute separately from FOLDER. I would like them to be considered together for the sorted xml. What should be done?


Solution

  • Actually this code does not order child elements by name attribute; it sorts by element name:

    orderby child.Name.ToString()
    

    What you need to do is get the value of the "name" attribute (case sensitive!) instead. This is possible with child.Attribute("name"), but you need to get the .Value of this while watching out for null attributes; the code is going to be slightly awkward to write inline.

    Personally I find this version with child.Attributes (that returns a collection) more convenient:

    orderby child.Attributes("name").Select(a => a.Value).FirstOrDefault()