Click here to Skip to main content
Click here to Skip to main content

A Sortable XmlDocument Class

By , 1 Feb 2009
 

Introduction

This class makes XmlDocument class sortable. Actually, I did not search much to see if there is something like this already. I needed it and I wrote it. And, I thought that someone might find this useful.

Background

Basically, this class runs a TAXmlSorter class to sort nodes. TAXmlSorter is an abstract class that implements IComparer<XmlNode> interface. Other comparison classes are based on TAXmlSorter

TAXmlDocument.SortType Enumeration

public enum SortType
{
    Ascending,
    Descending
}

TAXmlDocument.TAXmlSorter Abstract Class

public abstract class TAXmlSorter:IComparer<XmlNode>
{
    Comparison<XmlNode> comparisonDelegate;
    protected TAXmlSorter(SortType st)
    {
        switch (st)
        {
            case SortType.Ascending:
                comparisonDelegate = CompareAsc;
                break;
            case SortType.Descending:
                comparisonDelegate = CompareDesc;
                break;
        }
    }
     protected abstract int CompareAsc(XmlNode x, XmlNode y);
    protected abstract int CompareDesc(XmlNode x, XmlNode y);
     public abstract void InitArray(XmlNode[] array);
     #region IComparer<XmlNode> Members
     int IComparer<XmlNode>.Compare(XmlNode x, XmlNode y)
    {
        return comparisonDelegate(x, y);
    }
     #endregion
}

I used a comparison delegate to better performance. In this way, the application will determine the comparison function on initialize, so it won't check every time if it is an ascending sort.

public sealed class TAXSNodeName : TAXmlSorter 

This class compares node names.

public sealed class TAXSNodeAttribute<AttributeType> : TAXmlSorter
       where AttributeType : IComparable, IConvertible

This class compares attribute of current node.

public sealed class TAXSChildCount : TAXmlSorter

This class compares child node counts.

public sealed class TAXSAttributeCount : TAXmlSorter

This class compares attribute counts.

public sealed class TAXSInnerText : TAXmlSorter

This class compares nodes' inner text values (string comparison).

public sealed class TAXSInnerValue<ValueType> : TAXmlSorter
    where ValueType : IComparable, IConvertible 

This class compares nodes' inner text values (firstly converts to given type).

public sealed class TAXSMulti : TAXmlSorter

This class is a multi comparison class.

Sorting

Actually this class does not use any special sort algorithm. It is based on Array.Sort function.

public void Sort(XmlNode parentNode, TAXmlSorter sorter)
{
    if (parentNode == null)
        throw new ArgumentNullException("parentNode");
    if (sorter == null)
        throw new ArgumentNullException("sorter");
    if (parentNode.OwnerDocument != this)
        throw new Exception(
            "parentNode value is not child of this document");
    //if childcount less than 2 then it is not necessary to sort
    if (parentNode.ChildNodes.Count < 2)
        return;
    //copy the child of parent
    XmlNode[] nodes = new XmlNode[parentNode.ChildNodes.Count];
    for (int i = 0; i < nodes.Length; i++)
        nodes[i] = parentNode.ChildNodes[i];
    
    //call abstract initarray method of sorter
    sorter.InitArray(nodes);
     //call array.sort to sort nodes
    Array.Sort(nodes, sorter);
      //remove all child nodes of parent
    parentNode.InnerXml = "";
     //add sorted nodes
    for (int i = 0; i < nodes.Length; i++)
        parentNode.AppendChild(nodes[i]);
}

Using the Code

XmlFile1.xml Content

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <q>5</q>
  <s/>
  <a/>
  <a id="1">
    <b>
      <c>123</c>
    </b>
  </a>
  <r num="0.1" id="2">false</r>
  <tolgahan id="0" />
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <zoom/>
  <zoom id="-1"/>
</root>

Sort By Node Name

Ascending
TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), new TAXmlDocument.TAXSNodeName() ); 

Output

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a id="1">
    <b>
      <c>
      </c>
    </b>
  </a>
  <a />
  <q>
  </q>
  <r num="0.1" id="2">
  </r>
  <s />
  <tolgahan id="0" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <zoom id="-1" />
  <zoom />
</root>
Descending
TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
new TAXmlDocument.TAXSNodeName( TAXmlDocument.SortType.Descending ));

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <zoom />
  <zoom id="-1" />
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <tolgahan id="0" />
  <s />
  <r num="0.1" id="2">
  </r>
  <q>
  </q>
  <a />
  <a id="1">
    <b>
      <c>
      </c>
    </b>
  </a>
</root>

All comparison classes have SortType (ascending or descending) parameter on constructor.

Sort by Attribute Value (If it is boolean)

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
         new TAXmlDocument.TAXSNodeAttribute<Boolean>( "bool" ));

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahan id="0" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <zoom id="-1" />
  <zoom />
  <a />
  <s />
  <q>
  </q>
  <r num="0.1" id="2">
  </r>
  <a id="1">
    <b>
      <c>
      </c>
    </b>
  </a>
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
</root>

Sort by Attribute Value (If it is date)

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
         new TAXmlDocument.TAXSNodeAttribute<DateTime>( "date", 
                           TAXmlDocument.SortType.Descending ));

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <zoom id="-1" />
  <zoom />
  <tolgahan id="0" />
  <s />
  <q>
  </q>
  <a />
  <r num="0.1" id="2">
  </r>
  <a id="1">
    <b>
      <c>
      </c>
    </b>
  </a>
</root>

Sort by ChildCount

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
    new TAXmlDocument.TAXSChildCount());

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <zoom id="-1" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <zoom />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <tolgahan id="0" />
  <s />
  <a />
  <q>5</q>
  <a id="1">
    <b>
      <c>123</c>
    </b>
  </a>
  <r num="0.1" id="2">false</r>
</root>

Sort by Attribute Count

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
    new TAXmlDocument.TAXSAttributeCount());

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <a />
  <zoom />
  <q>5</q>
  <s />
  <zoom id="-1" />
  <tolgahan id="0" />
  <a id="1">
    <b>
      <c>123</c>
    </b>
  </a>
  <r num="0.1" id="2">false</r>
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
</root>

Sort by Inner Value (Try to Sort Integer Value)

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"), 
    new TAXmlDocument.TAXSInnerValue<Int32>());

Output

<?xml version="1.0" encoding="utf-8"?>
<root>
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <zoom id="-1" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
  <zoom />
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <s />
  <tolgahan id="0" />
  <a />
  <r num="0.1" id="2">false</r>
  <q>5</q>
  <a id="1">
    <b>
      <c>123</c>
    </b>
  </a>
</root>

Multi Sorting 

(Try to sort firstly boolean attribute "bool" desc, then date attribute "date" asc, then inner value):

TAXmlDocument doc = new TAXmlDocument();
doc.Load("c:\\xmlfile1.xml");
doc.Sort(doc.SelectSingleNode("root"),
    new TAXmlDocument.TAXSMulti(
        new TAXmlDocument.TAXSNodeAttribute<Boolean>("bool",
        TAXmlDocument.SortType.Descending),
        new TAXmlDocument.TAXSNodeAttribute<DateTime>("date"),
        new TAXmlDocument.TAXSInnerValue<Int32>()
    )
);

Output

?<?xml version="1.0" encoding="utf-8"?>
<root>
  <tolgahanAlbayrak bool="true" date="01.01.2008 15:13:10" />
  <r num="0.1" id="2">false</r>
  <zoom id="-1" />
  <zoom />
  <s />
  <tolgahan id="0" />
  <a />
  <q>5</q>
  <a id="1">
    <b>
      <c>123</c>
    </b>
  </a>
  <tolgahanAlbayrak bool="false" date="01.01.2008 11:13:10" />
  <tolgahanAlbayrak bool="false" date="01.02.2008 15:13:10" />
</root> 

History

  • 02 Feb 2009: Added sorting operation for child element's one of child value by Andrew Chudley
  • 13 Jan 2009: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Tolgahan ALBAYRAK
Software Developer (Senior)
Turkey Turkey
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSort By XPathmemberJames Hutchison28 Nov '12 - 7:30 
I know this is an old project, but it's slick so I thought I may add the following sorter to allow sorting by an XPath statement.
 
This allows you to add a sorter which is basically limitless, particularly useful when the node you're dealing with a deeply nested Xml document.
 
public sealed class TAXSXPathValue<ValueType> : TAXmlSorter
            where ValueType : IComparable, IConvertible
        {
            Dictionary<XmlNode, ValueType> values;
            string _xPath;
 
            public TAXSXPathValue(string xPath)
                : this(SortType.Ascending, xPath)
            {
            }
 
            public TAXSXPathValue(SortType st, string xPath)
                : base(st)
            {
                _xPath = xPath;
            }
 
            public override void InitArray(XmlNode[] array)
            {
                values = new Dictionary<XmlNode, ValueType>();
                TypeConverter tc = TypeDescriptor.GetConverter(typeof(ValueType));
                foreach (XmlNode n in array)
                {
                    try
                    {
                        string val = n.SelectSingleNode(_xPath).InnerText;
                        values.Add(n, (ValueType)tc.ConvertFrom(val));
                    }
                    catch
                    {
                        values.Add(n, default(ValueType));
                    }
                }
            }
 
            protected override int CompareAsc(XmlNode x, XmlNode y)
            {
                return values[x].CompareTo(values[y]);
            }
 
            protected override int CompareDesc(XmlNode x, XmlNode y)
            {
                return values[y].CompareTo(values[x]);
            }
        }

Generalfor sorting elements by their child elements [modified]memberandrew.chudley29 Jan '09 - 5:46 
Hi Tolgahan,
 
Thumbs Up | :thumbsup:
 
I get alot of XML which is all elements so I added this method to sort child elements by one of their children's nodevalue. If your code already does this please ignore it.
 
Its for situations like
 
sales > salesArea > sort by sale value
 
This is how I call it
 
doc.Sort(doc.SelectNodes("//price"),
                        new TAXmlDocument.TAXSInnerValue<double>());
                 
public void Sort(XmlNodeList nodelist, TAXmlSorter sorter)
            {
  
                  try
                  {
                        if (nodelist == null)
                              throw new ArgumentNullException("nodelist");
                        if (nodelist.Count == 0)
                              throw new ArgumentNullException("nodelist empty");
                        if (sorter == null)
                              throw new ArgumentNullException("sorter");
                        if (nodelist[0].OwnerDocument != this)
                              throw new Exception("nodelist is not child of this document");
                        //if childcount less than 2 then it is not necessary to sort
                        if (nodelist.Count < 2)
                              return;
                        //copy the children
                        XmlNode[] nodes = new XmlNode[nodelist.Count];
                        for (int i = 0; i < nodes.Length; i++)
                              nodes[i] = nodelist[i];
 
                        //call abstract initarray method of sorter
                        sorter.InitArray(nodes);
 
                        //call array.sort to sort nodes
                        Array.Sort(nodes, sorter);
 
                        XmlNode grandparentNode = nodelist[0].ParentNode.ParentNode;
 
                        //remove all child nodes of grandparent
                        grandparentNode.InnerXml = "";
 
                        //add parents of the sorted nodes
                        for (int i = 0; i < nodes.Length; i++)
                              grandparentNode.AppendChild(nodes[i].ParentNode);
 
                  }
                  catch (Exception ex)
                  {
                        System.Diagnostics.Debug.Write(ex.Message);
 
                  }
 
            }
 
<div class="ForumMod">modified on Thursday, January 29, 2009 4:32 PM</div>
GeneralRe: for sorting elements by their child elementsmemberTolgahan ALBAYRAK1 Feb '09 - 14:11 
Thank you Andrew,
That is cool!
I modified the source, and added your code. And i gave a credit to you.
 
BTW, if you want to use XmlDocument class more simple and more interactive please notice that : http://www.codeproject.com/KB/cs/TAXI.aspx[^]
 
Thank you again..
 
I havent been found the mean of life yet.

GeneralVery nicememberandrew.chudley29 Jan '09 - 3:51 
Thank you for a great utility. Big Grin | :-D
GeneralCool waymemberDr.Luiji14 Jan '09 - 12:21 
First of all good article Tolgahan, and I give you a 5 becouse the way You choose is really simple.
 
Another way to do that can be to use the XslCompiledTransform[^] take a look on the link.
Kind regards.
 
Dr.Luiji
 
Trust and you'll be trusted.
 
Try iPhone UI [^] a new fresh face for your Windows Mobile, here on Code Project.

GeneralRe: Cool waymemberTolgahan ALBAYRAK19 Jan '09 - 3:53 
Thank you, Sir.. I really like your iPhone UI staff, that is one of my fav.
I have another Object Oriented XML article, TAXI[^]. I will be pleasure, if you leave a comment for it too..
 
I havent been found the mean of life yet.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 1 Feb 2009
Article Copyright 2009 by Tolgahan ALBAYRAK
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid