How to set the default XML namespace for an XDocument
It seems that Linq to XML does not provide an API for this use case (disclaimer: I didn't investigate very deep). If change the namespace of the root element, like this:
XNamespace xmlns = "http://schemas.datacontract.org/2004/07/Widgets";
doc.Root.Name = xmlns + doc.Root.Name.LocalName;
Only the root element will have its namespace changed. All children will have an explicit empty xmlns tag.
A solution could be something like this:
public static void SetDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
if(xelem.Name.NamespaceName == string.Empty)
xelem.Name = xmlns + xelem.Name.LocalName;
foreach(var e in xelem.Elements())
e.SetDefaultXmlNamespace(xmlns);
}
// ...
doc.Root.SetDefaultXmlNamespace("http://schemas.datacontract.org/2004/07/Widgets");
Or, if you prefer a version that does not mutate the existing document:
public XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
XName name;
if(xelem.Name.NamespaceName == string.Empty)
name = xmlns + xelem.Name.LocalName;
else
name = xelem.Name;
return new XElement(name,
from e in xelem.Elements()
select e.WithDefaultXmlNamespace(xmlns));
}
Specifiying a default namespace for XDocument gives empty value
I think I understand why I don't get the default namespace. According to MSDN:
When creating XML trees using C#, even if an XML tree would be
serialized with a default namespace, if the namespace is not persisted
in the XML tree as an attribute, this method will not report the
namespace as the default namespace.
So I probably need to set the xmlns attribute explicitly:
class Program
{
static void Main(string[] args)
{
var xmlRoot = new XElement(XName.Get("root", "http://somenamespace"));
xmlRoot.SetAttributeValue("xmlns", "http://somenamespace");
var xmlDocument = new XDocument(xmlRoot);
var xmlNamespace = xmlDocument.Root.GetDefaultNamespace().NamespaceName;
}
}
Now I'm getting the correct namespace name.
How to specify an xmlns for XDocument?
The way the XDocument
API works with namespace-scoped names is as XName
instances. These are fairly easy to work with, as long as you accept that an XML name isn't just a string, but a scoped identifier. Here's how I do it:
var ns = XNamespace.Get("http://example.com");
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null));
var root = new XElement(ns + "root1", new XElement(ns + "a", "b"));
doc.Add(root);
Result:
<root1 xmlns="http://example.com">
<a>b</a>
</root1>
Note the +
operator is overloaded to accept an XNamespace
and a String
to result in and XName
instance.
Adding a namespace to existing XDocument
First of all, while XML markup allows you to use
<root xmlns="http://example.com/ns">
<foo>
<bar>baz</bar>
</foo>
</root>
to use a single namespace declaration attribute to put the root element as well as those descendant elements into the declared namespace, when you manipulate the tree model you need to change the Name
of all elements so you need e.g.
XNamespace myNs = "http://example.com/ns";
foreach (XElement el in xdoc.Descendants())
{
el.Name = myNs + el.Name.LocalName;
}
If you also want to set a certain prefix pf
then addionally set
xdoc.Root.Add(new XAttribute(XNamespace.Xmlns + "pf", myNs));
Is there a way to set the default namespace to query from an XDocument?
Unfortunately this is what you must do when working with LINQ to XML. You must provide the namespace each time you query the document for a particular element.
How do I compose XDocument and keep the same namespace prefix throughout?
I think this behavior is intended. Attributes without a namespace prefix isn't part of any namespace, not even the default namespace. It needed to put the attribute in that namespace but since it didn't have a prefix to use, it had to create one. I think it'll be easier to just create the document but use explicit prefixes for the namespaces, it'll come out a lot cleaner.
var e_graphml = new XElement(ns_graphML + "graphml",
new XAttribute(XNamespace.Xmlns + "g", ns_graphML),
new XAttribute(XNamespace.Xmlns + "y", ns_yGraphML)
);
This will yield xml like so:
<g:graphml xmlns:g="http://graphml.graphdrawing.org/xmlns" xmlns:y="http://www.yworks.com/xml/graphml">
<g:graph g:edgedefault="directed" g:id="fileReferences" />
</g:graphml>
If you specifically want to have it render the attributes without prefixes, remove the namespace when you generate them. Attributes typically don't need to be namespaced unless explicitly required.
var e_graph = new XElement(ns_graphML + "graph",
new XAttribute("edgedefault", "directed"),
new XAttribute("id", Name)
);
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:y="http://www.yworks.com/xml/graphml">
<graph edgedefault="directed" id="fileReferences" />
</graphml>
LINQ to XML - How to fix default namespace in the root element
Quoting from here:
An attribute is not considered a child of its parent element. An attribute never inherits the namespace of its parent element. For that reason an attribute is only in a namespace if it has a proper namespace prefix. An attribute can never be in a default namespace.
and here:
A default namespace declaration applies to all unprefixed element names within its scope. Default namespace declarations do not apply directly to attribute names; the interpretation of unprefixed attributes is determined by the element on which they appear.
It seems that this odd behavior of LINQ-to-XML is rooted in standards. Therefore whenever adding a new attribute its namespace must be compared against the parents' default namespace which is active in its scope. I use this extension method for adding attributes:
public static XAttribute AddAttributeNamespaceSafe(this XElement parent,
XName attrName, string attrValue, XNamespace documentDefaultNamespace)
{
if (newAttrName.Namespace == documentDefaultNamespace)
attrName = attrName.LocalName;
var newAttr = new XAttribute(attrName, attrValue);
parent.Add(newAttr);
return newAttr;
}
And use this extension method for retrieving attributes:
public static XAttribute GetAttributeNamespaceSafe(this XElement parent,
XName attrName, XNamespace documentDefaultNamespace)
{
if (attrName.Namespace == documentDefaultNamespace)
attrName = attrName.LocalName;
return parent.Attribute(attrName);
}
Alternatively, if you have the XML structure at hand and want to fix the namespaces already added to attributes, use the following extension method to fix this (which is slightly different from that outlined in the question):
public static void FixDefaultXmlNamespace(this XElement xelem,
XNamespace documentDefaultNamespace)
{
if (xelem.Attributes().Any(x => x.Name.Namespace == documentDefaultNamespace))
{
var attrs = xelem.Attributes().ToArray();
for (int i = 0; i < attrs.Length; i++)
{
var attr = attrs[i];
if (attr.Name.Namespace == documentDefaultNamespace)
{
attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
}
}
xelem.ReplaceAttributes(attrs);
}
foreach (var elem in xelem.Elements())
elem.FixDefaultXmlNamespace(documentDefaultNamespace);
}
Note that you won't need to apply the above method, if you have used the first two methods upon adding and retrieving attributes.
Related Topics
On Postback, How to Check Which Control Cause Postback in Page_Init Event
Resizing an Image in ASP.NET Without Losing the Image Quality
Determine What Control the Contextmenustrip Was Used On
How to Ensure a Form Displays on the "Additional" Monitor in a Dual Monitor Scenario
Why Is Ushort + Ushort Equal to Int
Using the "Params" Keyword for Generic Parameters in C#
Using Process.Start() to Start a Process as a Different User from Within a Windows Service
How to Read Value of a Registry Key C#
Default Value of a Type at Runtime
How to Add an Extra Button to the Window's Title Bar
"Could Not Load Type [Namespace].Global" Causing Me Grief
Select Parsed Int, If String Was Parseable to Int
How to Use Use Late Binding to Get Excel Instance
What Is the Impact of Thread.Sleep(1) in C#
Why Can't I Use System.Io.File Methods in an MVC Controller
How to Compare Only Date Without Time in Datetime Types in Linq to SQL with Entity Framework
Why Does (Does It Really) List<T> Implement All These Interfaces, Not Just Ilist<T>