Recursively processing XML elements in C#

Recursion can be tricky. But when you’ve nailed it, it’s damn sexy. Below is a basic outline for recursively processing an XML document for all its child elements, and keeping track of what depth the element is at (how many ancestors, or parents, are above it):

void Process(XElement element, int depth)
{
	// For simplicity, argument validation not performed
	
	if (!element.HasElements)
	{
		// element is child with no descendants
	}
	else
	{
		// element is parent with children
		
		depth++;

		foreach (var child in element.Elements())
		{
			Process(child, depth);
		}

		depth--;
	}
}

To begin processing a document, pass in the root element and a depth of 0:

Process(XDocument.Load(@"C:\test.xml").Root, 0);

Here’s an example of recursively displaying an XML document:


void Process(XElement element, int depth)
{
	// For simplicity, argument validation not performed

	if (!element.HasElements)
	{	
		Console.WriteLine
		(
			string.Format
			(
				"{0}<{1}>{2}</{1}>", 
				"".PadLeft(depth, '\t'), // {0}
				element.Name.LocalName,  // {1}
				element.Value			 // {2}
			)
		);
	}
	else
	{
		Console.WriteLine
		(
			"".PadLeft(depth, '\t') + // Indent to show depth
			"<" + element.Name.LocalName + ">"
		);

		depth++;

		foreach (XElement child in element.Elements())
		{
			Process(child, depth);
		}

		depth--;
		
		Console.WriteLine
		(
			"".PadLeft(depth, '\t') + // Indent to show depth
			"</" + element.Name.LocalName + ">"
		);
	}
}

Processing an XML file recursively in using extension methods and lambdas:

using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        XDocument.Load(@"C:\test.xml").Root.RecursivelyProcess
        (
            // Element with no children reach

            new Action<XElement, int>((child, depth) =>
            {
                // Example of something to do with the child

                Console.WriteLine
                (
                    string.Format
                    (
                        "{0}<{1}>{2}</{1}>",
                        "".PadLeft(depth, '\t'), // {0}
                        child.Name.LocalName,    // {1}
                        child.Value			     // {2}
                    )
                );
            }),

            // Element with children reached

            new Action<XElement, int>((parent, depth) =>
            {
                // Example of something to do with the parent open

                Console.WriteLine
                (
                    "".PadLeft(depth, '\t') // Indent to show depth
                    + "<" + parent.Name.LocalName + ">"
                );
            }),

            // Finished processing element with children

            new Action<XElement, int>((parent, depth) =>
            {
                // Example of something to do with the parent close

                Console.WriteLine
                (
                    "".PadLeft(depth, '\t') // Indent to show depth
                    + "</" + parent.Name.LocalName + ">"
                );
            })
        );

        Console.Read();
    }
}

/* Alternatively, for clarity, see this implimentation:
 *
class Program
{
    static void Main(string[] args)
    {
        XDocument.Load(@"C:\test.xml").Root.RecursivelyProcess
		(
			Program.ProcessChild,
			Program.ProcessParentOpen,
			Program.ProcessParentClose
		);

        Console.Read();
    }

    static void ProcessChild(XElement child, int depth)
    {
        Console.WriteLine
        (
            string.Format
            (
                "{0}<{1}>{2}</{1}>",
                "".PadLeft(depth, '\t'), // {0}
                child.Name.LocalName,    // {1}
                child.Value			     // {2}
            )
        );
    }

    static void ProcessParentOpen(XElement parent, int depth)
    {
        Console.WriteLine
        (
            "".PadLeft(depth, '\t') // Indent to show depth
            + "<" + parent.Name.LocalName + ">"
        );
    }

    static void ProcessParentClose(XElement parent, int depth)
    {
        Console.WriteLine
        (
            "".PadLeft(depth, '\t') // Indent to show depth
            + "</" + parent.Name.LocalName + ">"
        );
    }
}
*/

/// <summary>
/// Extension methods for the .NET 3.5 System.Xml.Linq namespace
/// </summary>
public static class XExtensions
{
    /// <summary>
    /// Recursively perform operations on a XML element.
    /// </summary>
    /// <param name="element"></param>
    /// <param name="childAction">
    /// What to do when an element with no children is reached.
    /// The XElement is the child element, the int is the depth.
    /// To do nothing, pass null.
    /// </param>
    /// <param name="parentOpenAction">
    /// What to do when an element with children is reached.
    /// The XElement is the parent element, the int is the depth.
    /// To do nothing, pass null.
    /// </param>
    /// <param name="parentCloseAction">
    /// What to do when finished processing element with children.
    /// The XElement is the parent element, the int is the depth.
    /// To do nothing, pass null.
    /// </param>
    public static void RecursivelyProcess
    (
        this XElement element,
        Action<XElement, int> childAction,
        Action<XElement, int> parentOpenAction,
        Action<XElement, int> parentCloseAction
    )
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.RecursivelyProcess
        (
            0,
            childAction,
            parentOpenAction,
            parentCloseAction
        );
    }

    private static void RecursivelyProcess
    (
        this XElement element,
        int depth,
        Action<XElement, int> childAction,
        Action<XElement, int> parentOpenAction,
        Action<XElement, int> parentCloseAction
    )
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (!element.HasElements)
        {
            // Reached the deepest child

            if (childAction != null)
            {
                childAction(element, depth);
            }
        }
        else
        {
            // element has children

            if (parentOpenAction != null)
            {
                parentOpenAction(element, depth);
            }

            depth++;

            foreach (XElement child in element.Elements())
            {
                child.RecursivelyProcess
                (
                    depth,
                    childAction,
                    parentOpenAction,
                    parentCloseAction
                );
            }

            depth--;

            if (parentCloseAction != null)
            {
                parentCloseAction(element, depth);
            }
        }
    }
}
Advertisements

11 Comments »

  1. Excellent work. I have searched it lot. Bundle of thanks.

  2. Stephen Korow said

    Great. Thanks. It worked like a charm.

  3. Why not just use XElement.VisitElements?

    XElement root = BuildXmlTree(); // code omitted

    root.VisitElements((XElement e, int depth)
    => Console.WriteLine(“{0}{1}”,””.PadLeft(depth,’\t’),e.Name.LocalName);

  4. Thank you very much – that was exactly what I was looking for! This is also a very nice explanation of lambda-functions.

  5. Greg said

    Instead of incrementing then decrementing you can just do:

    Process(child, depth + 1);

    Simplifies the code a little bit.

  6. q said

    wa

  7. Todd said

    Modified to include XAttribute…

    ///
    /// Recursively perform operations on a XML element.
    ///
    ///
    ///
    /// What to do when an element with no children is reached.
    /// The XElement is the child element, the int is the depth.
    /// To do nothing, pass null.
    ///
    ///
    /// What to do when an element with children is reached.
    /// The XElement is the parent element, the int is the depth.
    /// To do nothing, pass null.
    ///
    ///
    /// What to do when finished processing element with children.
    /// The XElement is the parent element, the int is the depth.
    /// To do nothing, pass null.
    ///
    public static void Recurse(this XElement element
    , Action elementAction
    , Action attributeAction
    )
    {
    if (element == null)
    {
    throw new ArgumentNullException(“element”);
    }

    element.Recurse
    (
    0
    , elementAction
    , attributeAction
    );
    }

    private static void Recurse(this XElement element
    , int depth
    , Action elementAction
    , Action attributeAction
    )
    {
    if (element == null)
    {
    throw new ArgumentNullException(“element”);
    }

    if (!element.HasElements)
    {
    // Reached the deepest child
    if (elementAction != null)
    {
    elementAction(element, depth);
    }

    foreach (XAttribute attribute in element.Attributes())
    {
    if (attributeAction != null)
    {
    attributeAction(attribute, depth);
    }
    }
    }
    else
    {
    foreach (XAttribute attribute in element.Attributes())
    {
    if (attributeAction != null)
    {
    attributeAction(attribute, depth);
    }
    }

    // element has children
    if (elementAction != null)
    {
    elementAction(element, depth);
    }

    depth++;

    foreach (XElement child in element.Elements())
    {
    child.Recurse
    (
    depth
    , elementAction
    , attributeAction
    );
    }

    depth–;
    }
    }

    • Todd said

      Sorry, forgot to update the comments :)

  8. sdfdasf said

    Pasting this code into VS deposits it on one line!!! Better formatting needed.

  9. Brad said

    Very sexy. You just saved me a bucket load of frustration with this little gem. For the benefit of any VB.Net people, I have transposed an example of this to a Sub that outputs key/value pairs to a Dictionary.

    Dim items As New Dictionary(Of String, String)
    Dim loXElement As XElement

    loXElement = XElement.Load(“C:\test.xml”)
    XMLRecurse(loXElement, 0, items)

    Sub XMLRecurse(ByVal element As XElement, ByVal depth As Integer, ByRef items As Dictionary(Of String, String))

    Dim Item As String = String.Empty
    Dim Value As String = String.Empty

    If Not element.HasElements Then
    ‘element is child with no descendants
    Item = element.Name.ToString
    Value = element.Value.ToString
    If Not items.ContainsKey(Item) Then
    items.Add(Item, Value)
    End If
    Else
    ‘element is parent with children
    depth += 1
    For Each child As XElement In element.Elements
    XMLRecurse(child, depth, items)
    Next
    depth -= 1
    End If
    End Sub

  10. You are a God send. More power to you🗽

RSS feed for comments on this post · TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: