using System.Collections.Generic;
using System.Xml.Serialization;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.ObjectModel;
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Steps
readonly ChildCollection<Step> steps;
this.steps = new ChildCollection<Step>();
this.steps.ChildAdded += (s, e) =>
[System.Xml.Serialization.XmlElementAttribute("Step")]
public Collection<Step> StepList { get { return steps; } }
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Step
readonly ChildCollection<Step> steps;
this.steps = new ChildCollection<Step>();
this.steps.ChildAdded += (s, e) =>
e.Item.ParentId = this.Id;
[System.Xml.Serialization.XmlElementAttribute("Step")]
public Collection<Step> StepList { get { return steps; } }
[System.Xml.Serialization.XmlAttributeAttribute("Name")]
public string Name { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute("id")]
public string Id { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute("ParentID")]
public string ParentId { get; set; }
public class ChildCollectionEventArgs<TChild> : EventArgs
public readonly TChild Item;
public ChildCollectionEventArgs(TChild item)
public class ChildCollection<TChild> : Collection<TChild>
public event EventHandler<ChildCollectionEventArgs<TChild>> ChildAdded;
public event EventHandler<ChildCollectionEventArgs<TChild>> ChildRemoved;
void OnRemoved(TChild item)
var removed = ChildRemoved;
removed(this, new ChildCollectionEventArgs<TChild>(item));
void OnAdded(TChild item)
added(this, new ChildCollectionEventArgs<TChild>(item));
public ChildCollection() : base() { }
protected override void ClearItems()
foreach (var item in this)
protected override void InsertItem(int index, TChild item)
base.InsertItem(index, item);
protected override void RemoveItem(int index)
if (index >= 0 && index < Count)
protected override void SetItem(int index, TChild item)
base.SetItem(index, item);
public static class StepExtensions
public static IEnumerable<Step> TraverseSteps(this Steps root)
throw new ArgumentNullException();
return RecursiveEnumerableExtensions.Traverse(root.StepList, s => s.StepList);
public static IEnumerable<Step> TraverseSteps(this Step root)
throw new ArgumentNullException();
return RecursiveEnumerableExtensions.Traverse(root, s => s.StepList);
public static bool TryAdd(this Steps root, Step step, string parentId)
foreach (var item in root.TraverseSteps())
if (item != null && item.Id == parentId)
public static void Add(this Steps root, Step step, string parentId)
if (!root.TryAdd(step, parentId))
throw new InvalidOperationException(string.Format("Parent {0} not found", parentId));
public static class RecursiveEnumerableExtensions
public static IEnumerable<T> Traverse<T>(
Func<T, IEnumerable<T>> children)
var stack = new Stack<IEnumerator<T>>();
stack.Push(children(root).GetEnumerator());
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
yield return enumerator.Current;
stack.Push(children(enumerator.Current).GetEnumerator());
foreach (var enumerator in stack)
public static IEnumerable<T> Traverse<T>(
Func<T, IEnumerable<T>> children)
return from root in roots
from item in Traverse(root, children)
public static class TestClass
<Step id =""1"" Name=""S1"">
<Step id =""2"" Name=""S11"">
<Step id =""3"" Name=""S111"" />
<Step id =""4"" Name=""S112"" />
<Step id =""5"" Name=""S1121"" />
<Step id =""6"" Name=""S12"" />
static void ValidateParentIds(Steps steps)
foreach (var item in steps.TraverseSteps())
foreach (var subItem in item.StepList)
if (subItem != null && subItem.ParentId != item.Id)
throw new InvalidOperationException(string.Format("Child {0} of parent {1} has wrong parent id {2}", subItem.Id, item.Id, subItem.ParentId));
public static void Test()
var steps = GetXml().LoadFromXML<Steps>();
var xml2 = steps.GetXml();
Console.WriteLine("Re-serialized XML with ParentId values set: ");
Console.WriteLine("List of all step IDs in pre-order traversal: ");
foreach (var item in steps.TraverseSteps())
Console.WriteLine(item.Id);
ValidateParentIds(steps);
steps.Add(new Step { Id = "4C", Name = "S112C" }, "4");
var xml3 = steps.GetXml();
Console.WriteLine("Re-serialized XML with step '4C' added to step '4': ");
ValidateParentIds(steps);
public static class XmlSerializationHelper
public static T LoadFromXML<T>(this string xmlString)
using (StringReader reader = new StringReader(xmlString))
return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
public static string GetXml<T>(this T obj, bool omitStandardNamespaces = false)
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
ns = new XmlSerializerNamespaces();
using (var textWriter = new StringWriter())
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
public static void Main()