For the past eight(8) years Schalk Neethling has been working as a freelance developer under the pseudo of Volume4 and is now the president of Overt Strategy Consulting. During this period he has completed over 300 projects ranging from full web application development to complete branding. As president and lead developer of Overt Strategy Consulting, Schalk Neethling and his team has released a 100% Java standards based content management system called AlliedBridge and business document exchange and review system, called Doc-Central. Schalk Neethling is also actively involved on a daily basis in the open source, web standards and accessibility areas and is a current active member of the Web Standards Group. Schalk is also the co-founder and president of the non-profit The South Web Standards and Accessibility Group, which aims to actively educate and raise awareness of web standards and accessibility to both the developer society as well as business large and small. Schalk also has a long relationship with DZone and is currently zone leader for both the web builder, css.dzone.com, as well as the .NET zone, dotnet.dzone.com, and you can find a lot of his writing there as well as on his blog located at schalkneethling.alliedbridge.com. Schalk is constantly expanding on his knowledge of various aspects of technology and loves to stay in touch with the latest happenings. For Schalk web development and the internet is not just a job, it is a love, a passion and a life style. Schalk has posted 173 posts at DZone. View Full User Profile

Traversing XML in .NET

06.09.2008
| 5213 views |
  • submit to reddit

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) {

}

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

References
Published at DZone with permission of its author, Schalk Neethling. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: