Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Query XDocument with xmlns attribute (namespace)

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?

like image 745
Joel Avatar asked Apr 15 '13 14:04

Joel


2 Answers

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);
}
like image 181
Jon Skeet Avatar answered Sep 29 '22 02:09

Jon Skeet


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.

like image 42
Felix Keil Avatar answered Sep 29 '22 02:09

Felix Keil