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