18

I try to query elements from an visual studio *.csproj file. I created a short example to illustrate the problem:

    // Working
    string xml1 = @"<Project ToolsVersion='4.0'>
                      <ItemGroup Label='Usings'>
                        <Reference Include='System' />
                        <Reference Include='System.Xml' />
                      </ItemGroup>
                    </Project>"; 
    // Not working
    string xml2 = @"<Project ToolsVersion='4.0' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
                      <ItemGroup Label='Usings'>
                        <Reference Include='System' />
                        <Reference Include='System.Xml' />
                      </ItemGroup>
                    </Project>";

    XDocument doc = XDocument.Parse(xml2);

    foreach (XElement element in doc.Descendants("ItemGroup"))
    {
        Console.WriteLine(element);
    }

The string xml1 works fine, xml2 doesn't return anything. The only difference between those strings is xmlns attribute in the document root.

How do i query documents containing xmlns attributes? Why is it a problem when a xml document contains an xmlns attribute?

Joel
  • 4,862
  • 7
  • 46
  • 71

2 Answers2

24

Why is it a problem when a xml document contains an xmlns attribute?

It's not, if you understand what it means :) Basically you've applied a default namespace URI of "http://schemas.microsoft.com/developer/msbuild/2003" to all elements. So when querying, you need to specify that namespace too. Fortunately, LINQ to XML makes that really simple:

XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
XDocument doc = XDocument.Parse(xml2);
foreach (XElement element in doc.Descendants(ns + "ItemGroup"))
{
    Console.WriteLine(element);
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Another syntax: `doc.Descendants("{http://schemas.microsoft.com/developer/msbuild/2003}ItemGroup")` – mtmk Sep 10 '15 at 19:40
  • 1
    Sorry I don't understand why we have to explicitly write the namespace if it is the DEFAULT for this document? What is the reason that this is not resolved by XElement/XDocument itself implicit? – Felix Keil Sep 16 '15 at 12:55
  • @FelixKeil: Because when you call `Element` or `Descendants` you're specifying an `XName`, which is fully-qualified... and the fully-qualified name includes the namespace. I sort of see your point, but I think it makes sense the way it's done, and the namespace support in LINQ to XML is the nicest of any XML API I've seen. – Jon Skeet Sep 16 '15 at 13:12
7

It is not necessary to know the namespace beforehand. You can write code, that works with both Xmls because you can get the default namespace from XElement.

XDocument doc = XDocument.Parse(xml2);
XNamespace ns = doc.Root.GetDefaultNamespace();
foreach (XElement element in doc.Descendants(ns + "ItemGroup"))
{
    Console.WriteLine(element);
}

I also wrote an extension method to resolve the XName from any XObject (XElement, XDocument, etc.).

The benefit in using the extension method instead of GetDefaultNamespace is that you don't have to check if there is already another Namespace provided.

public static XName ResolveName(this XObject xObj, XName name)
{
    //If no namespace has been added, use default namespace anyway
    if (string.IsNullOrEmpty(name.NamespaceName))
    {
        name = xObj.Document.Root.GetDefaultNamespace() + name.LocalName;
    }
    return name;
}

You can use it like this

XDocument doc = XDocument.Parse(xml2);
foreach (XElement element in doc.Descendants(doc.ResolveName("ItemGroup")))
{
    Console.WriteLine(element);
}

I think LINQ to XML is a wonderful API. But I think it is clear, that if I don't provide a namespace, that I always mean the default namespace. I don't see any reason why LINQ to XML does not behave this way. This is a little drawback that really annoyed me. And the first time as a beginner with LINQ to XML I didn't know what I did wrong for hours when I forgot to provide the default namespace.

Felix Keil
  • 2,344
  • 1
  • 25
  • 27