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?
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);
}
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.