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);
            }
        }
    }
}

1 Comment »

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

RSS feed for comments on this post · TrackBack URI

Leave a Comment