3

I came across a problem similar to this:

How to prevent blank xmlns attributes in output from .NET's XmlDocument?

Except I'm creating a new node and then setting it's InnerXml property to a string of XML.

    Dim ns As String = "http://test"
    Dim doc As XmlDocument = New XmlDocument
    doc.LoadXml(String.Format("<data xmlns=""{0}""></data>", ns))

    Dim newElement As XmlElement = doc.CreateElement("new", ns)
    newElement.InnerXml = "<person><name>Joe</name></person>"

    Dim result As String = newElement.OuterXml

What I expected is:

<data xmlns="http://test">
  <new>
    <person>
      <name>Joe</name>
    </person>
  </new>
</data>

What it actually created:

<data xmlns="http://test">
  <new>
    <person xmlns="">
      <name>Joe</name>
    </person>
  </new>
</data>

According to the MSDN, the parsing is done in the current namespace context. I expected the current namespace context would have been the default namespace for not only newElement but all imported child nodes. I have experienced the same issue using CreateDocumentFragment().

Is there any way to prevent the child node immediately under newElement from showing up with an empty Namespace when importing a string of xml?

Community
  • 1
  • 1
HashTagDevDude
  • 564
  • 1
  • 4
  • 19

1 Answers1

4

The statement:

The parsing is done in the current namespace context.

means that any namespace prefixes that your XML string might contain will be interpreted in the context of the namespace prefixes that the document defines.

This causes that the following thing works:

Const ns As String = "http://test"
Const ns_foo As String = "http://www.example.com"

Dim doc As XmlDocument = New XmlDocument()
doc.LoadXml(String.Format("<data xmlns=""{0}"" xmlns:foo=""{1}""></data>", ns, ns_foo))

Dim newElement As XmlElement = doc.CreateElement("new", ns)
doc.DocumentElement.AppendChild(newElement)

newElement.InnerXml = "<foo:person><foo:name>Joe</foo:name></foo:person>"

and results in

<data xmlns:foo="http://www.example.com" xmlns="http://test">
   <new>
      <foo:person>
         <foo:name>Joe</foo:name>
      </foo:person>
   </new>
</data>

However, nodes that don't have a prefix are in no particular namespace by definition. They are in the default namespace.

There is no way you can influence the default namespace when setting InnerXml. It will always be assumed that the default namespace is the empty namespace.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 1
    This is exactly my point. Nodes that don't have a prefix are in no particular namespace, they will be in the default namespace. So why would my innerxml Joe, become Joe? Shouldn't it leave off the xmlns="" and assume the xmlns provided by the root node? – HashTagDevDude Mar 31 '16 at 16:29
  • 1
    Yes, but the default namespace is not "whatever the target document has", but "whatever you can reasonably assume when parsing a string to nodes". And you can't reasonably assume that new nodes should always get the same default namespaces that applies to their parent node - partly, because that would be wrong for "the other half" of the use cases, and partly because this would make it impossible to add "namespace-less" nodes to a document. – Tomalak Mar 31 '16 at 16:32
  • @HashTagDevDude **Solution:** Use an explicit default namespace in the XML you import. (`"Joe"`). That way there is no ambiguity about the namespace of the elements. They can stand for themselves in the string and their namespace is clear - and they can be imported into another document and their namespace is *still* clear. – Tomalak Mar 31 '16 at 16:48
  • So xmlns="" is not the same as the default namespace? I this this is what I'm incorrectly assuming. – HashTagDevDude Mar 31 '16 at 17:11
  • No, that's the *empty namespace*, that's explicitly different from the default namespace. – Tomalak Mar 31 '16 at 17:16
  • 2
    Upon parsing of a document, the default namespace initially is the empty namespace. When an element sets `xmlns="foo"` then the namespace for that element and the default for all of its descendants will be `"foo"` – until that element ends or one of its descendants overrides it with an `xmlns` of its own. `InnerXml` is a bit special, because the parsing of the main document has ended when you call it. So a new parsing session begins, and that can't make any assumptions about the default namespace, so it begins with the empty namespace. – Tomalak Mar 31 '16 at 17:19
  • 2
    Thanks for the clear explanation. I completely understand why it works the way it does now. – HashTagDevDude Mar 31 '16 at 17:22
  • The main point to understand is that namespaces are physically inseparable from their nodes. They have to be decided during node creation and they cannot ever change during that node's lifetime. There is no "default namespace" that somehow overshadows the XML document and magically applies to all elements that don't state otherwise. There is only the namespace that currently is used by the parser at the point in time when an element is created. That namespace is called the "default". – Tomalak Mar 31 '16 at 17:25
  • It seems like we need a SetInnerXml() method that takes a namespace, like CreateElement does, to solve this problem. I'm leaning toward stripping the xmlns="" text out of the xml file after I write it to disk. – Chris Arbogast May 21 '22 at 00:46
  • 1
    @ChrisArbogast Messing around XML string with search and replace is not recommended, but removing `xmlns=""` has little risk of breaking anything, unless it's actually meant to be there on some nodes. `SetInnerXml()` is a bit of a lazy approach anyway - if you're generating the XML, you could just as well use DOM methods to generate the nodes, and if you're getting the XML from some place else, you could parse it into a temporary document, and then use [`.ImportNode()`](https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.importnode) to get it into your main document. – Tomalak May 21 '22 at 10:18
  • 1
    @Tomalak I do appreciate your time. Doing this, followed by the `.ImportNode()` call on the child of ``, did solve the problem, so long as the default namespace matches in both documents. `XmlDocument doc = new XmlDocument(); doc.LoadXml(" " + snippetText + " ");` – Chris Arbogast May 23 '22 at 23:08
  • @ChrisArbogast Yep, that was the idea. :) – Tomalak May 24 '22 at 04:49