views:

71

answers:

2

I have an XML file like this:

<SiteConfig>
  <Sites>
    <Site Identifier="a" />
    <Site Identifier="b" />
    <Site Identifier="c" />
  </Sites>
</SiteConfig>

The file is user-editable, so I want to provide reasonable error message in case I can't properly parse it. I could probably write a .xsd for it, but that seems kind of overkill for a simple file.

So anyway, when querying for the list of <Site> nodes, there's a couple of ways I could do it:

var doc = XDocument.Load(...);

var siteNodes = from siteNode in 
                  doc.Element("SiteConfig").Element("Sites").Elements("Site")
                select siteNode;

But the problem with this is that if the user has not included the <SiteUrls> node (say) it'll just throw a NullReferenceException which doesn't really say much to the user about what actually went wrong.

Another possibility is just to use Elements() everywhere instead of Element(), but that doesn't always work out when coupled with calls to Attribute(), for example, in the following situation:

var siteNodes = from siteNode in 
                  doc.Elements("SiteConfig")
                     .Elements("Sites")
                     .Elements("Site")
                where siteNode.Attribute("Identifier").Value == "a"
                select siteNode;

(That is, there's no equivalent to Attributes("xxx").Value)

Is there something built-in to the framework to handle this situation a little better? What I would prefer is a version of Element() (and of Attribute() while we're at it) that throws a descriptive exception (e.g. "Looking for element <xyz> under <abc> but no such element was found") instead of returning null.

I could write my own version of Element() and Attribute() but it just seems to me like this is such a common scenario that I must be missing something...

A: 

You could use XPathSelectElements

using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

class Program
{
    static void Main()
    {
        var ids = from site in XDocument.Load("test.xml")
                  .XPathSelectElements("//SiteConfig/Sites/Site")
                  let id = site.Attribute("Identifier")
                  where id != null
                  select id;
        foreach (var item in ids)
        {
            Console.WriteLine(item.Value);
        }
    }
}

Another thing that comes to mind is to define an XSD schema and validate your XML file against this schema. This will generate meaningful error messages and if the file is valid you can parse it without problems.

Darin Dimitrov
+2  A: 

You could implement your desired functionality as an extension method:

public static class XElementExtension
{
    public static XElement ElementOrThrow(this XElement container, XName name)
    {
        XElement result = container.Element(name);
        if (result == null)
        {
            throw new InvalidDataException(string.Format(
                "{0} does not contain an element {1}",
                container.Name,
                name));
        }
        return result;
    }
}

You would need something similar for XDocument. Then use it like this:

var siteNodes = from siteNode in 
    doc.ElementOrThrow("SiteConfig")
       .ElementOrThrow("SiteUrls")
       .Elements("Sites")
    select siteNode;

Then you will get an exception like this:

SiteConfig does not contain an element SiteUrls
Mark Byers
Yeah, I think this is what I'll go with. I might create an XSD file at the same time, since that also gives the added benefit of letting Visual Studio do intellisense :)
Dean Harding