Platinum Partner
dotnet,.net,how-to,xml

Traversing XML in .NET

If you ever need to traverse each xml element in an xml document , you may want to implement your own XmlEnumerable. I've had some issues with the .NET XML API recently. The built in .NET XmlElement implements the non generic IEnumerable which means you've got to foreach through a bunch of objects.

foreach (object o in rootElement) {

}

[img_assist|nid=3400|title=|desc=|link=none|align=left|width=148|height=244]This kind of scares me a bit because of the Xml object hierarchy. The reason being, there are several sub classes of XmlNode, and trying to understand this object hierarchy is not interesting to me.

Rather than having to check if each item is an Xml element, we just created our own abstraction that we prefer to work with, and map from the framework XmlElement to our own IXmlElement.

public interface IXmlElement : IEquatable<IXmlElement>, IEnumerable<IXmlElement> {
string Name();
string ToXml();
}

 

 

Let's say you need to traverse and Xml that looks like this:

@"<root>
<GrandParent>
<Parent>
<Child>
<GrandChild>
</GrandChild>
</Child>
</Parent>
</GrandParent>
<GrandParent>
<Parent>
<Child>
<GrandChild>
</GrandChild>
</Child>
</Parent>
</GrandParent>
<Cousin>
</Cousin>
</root>");

If we were to traverse this document we would expect to find 10 elements

[Test]
public void should_traverse_through_each_element() {
CreateSUT().Count().ShouldBeEqualTo(10);
}

[Test]
public void should_contain_one_root_element() {
CreateSUT()
.Where(x => x.Name().Equals("root"))
.Count()
.ShouldBeEqualTo(1);
}

[Test]
public void should_contain_two_grand_parents() {
CreateSUT()
.Where(x => x.Name().Equals("GrandParent"))
.Count()
.ShouldBeEqualTo(2);
}

We could walk this xml structure and query it, using an API that we prefer by building our own IEnumerable<IXmlElement> and extension methods for querying.

public class XmlElementEnumerable : IEnumerable<IXmlElement> {
private XmlElement rootElement;
private IMapper<XmlElement, IXmlElement> mapper;

public XmlElementEnumerable(XmlElement rootElement) {
this.rootElement = rootElement;
mapper = new XmlElementMapper();
}

public IEnumerator<IXmlElement> GetEnumerator() {
yield return mapper.MapFrom(rootElement);
foreach (var element in RecursivelyWalkThrough(rootElement)) {
yield return mapper.MapFrom(element);
}
}

IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}

private IEnumerable<XmlElement> RecursivelyWalkThrough(XmlNode element) {
if (element.HasChildNodes) {
foreach (var childNode in element.ChildNodes) {
if (childNode is XmlElement) {
yield return childNode.DownCastTo<XmlElement>();
foreach (var xmlElement in RecursivelyWalkThrough(childNode.DownCastTo<XmlElement>())) {
yield return xmlElement;
}
}
}
}
}
}

Now you can traverse your own xml data structures using a more strongly typed API that suits your needs. For example:

public class RawXmlElement : IXmlElement {
public RawXmlElement(string rawXml) {
_rawXml = rawXml;
}

public string ToXml() {
return _rawXml;
}

public string Name() {
return Parse.Xml(this).ForItsName();
}

public bool Equals(IXmlElement other) {
return other != null && other.ToXml().Equals(_rawXml);
}

public override bool Equals(object obj) {
return ReferenceEquals(this, obj) || Equals(obj as IXmlElement);
}

public override int GetHashCode() {
return _rawXml != null ? _rawXml.GetHashCode() : 0;
}

IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}

public IEnumerator<IXmlElement> GetEnumerator() {
return new XmlEnumerable(this).GetEnumerator();
}

public override string ToString() {
return _rawXml;
}

private readonly string _rawXml;
}

Or...

public class SingleXmlElement<T> : IXmlElement {
public SingleXmlElement(string elementName, T elementValue) {
this.elementName = elementName;
this.elementValue = elementValue;
}

public string ToXml() {
return ToString();
}

public string Name() {
return Parse.Xml(this).ForItsName();
}

public IEnumerator<IXmlElement> GetEnumerator() {
return new XmlEnumerable(this).GetEnumerator();
}

public override string ToString() {
return string.Format("<{0}>{1}</{0}>", elementName, elementValue);
}

public bool Equals(IXmlElement other) {
return other != null && ToString().Equals(other.ToXml());
}

public override bool Equals(object obj) {
return ReferenceEquals(this, obj) || Equals(obj as IXmlElement);
}

public override int GetHashCode() {
return
(elementName != null ? elementName.GetHashCode() : 0) +
29*(elementValue != null ? elementValue.GetHashCode() : 0);
}

IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}

private readonly string elementName;
private readonly T elementValue;
}

Hopefully, this helps someone else who's drowning in xml!

Original Author

Original Article written by Mo Khan

Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}