Search code examples
vb.netlinq-to-xmlxml-namespacesxelement

Adding namespace attribute to XElement - how to prevent blank/empty namespace on child elements?


I need to read an xml document from a database record into an XDocument object in order for it to be deserialized. So that the deserialization will work, I need to apply a specific namespace to each of the level 1 elements. So XML looks a bit like this:

<Itinerary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Segments>
        <SegmentFlight>
        </SegmentFlight>
        <!-- more child elements -->
    </Segments>
    <References>
        <!-- child elements -->
    </References>
    <Fares>
        <!-- child elements -->
    </Fares>
</Itinerary>

And I need it to look like this:

<Itinerary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Segments xmlns="http://myurl">
        <SegmentFlight>
        </SegmentFlight>
        <!-- more child elements -->
    </Segments>
    <References xmlns="http://myurl">
        <!-- child elements -->
    </References>
    <Fares xmlns="http://myurl">
        <!-- child elements -->
    </Fares>
</Itinerary>

But when I run the following code to apply the namespace to each of the top-level elements within the Itinerary node:

Dim xmlDoc As XDocument = XDocument.Load(New System.IO.StringReader(xmlStringFromDB))
Dim ns As XNamespace = "http://myurl"

For Each elem In xmlDoc.Descendants("Itinerary").Elements
    elem.Name = ns + elem.Name.LocalName
Next    

I get a blank xmln="" namespace attribute on each child element within that element, which causes the deserialization to fail:

<Itinerary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Segments xmlns="http://myurl">
        <SegmentFlight xmlns="">
            <!-- etc ... -->
        </Segments>

How do I prevent the blank/empty namespace being added to each child element of the element to which the required namespace has been applied?


Solution

  • Remove the Elements from your For loop, it was causing all child elements to be processed, too.

        For Each elem In xmlDoc.Descendants("Itinerary") ''//.Elements
            elem.Name = ns + elem.Name.LocalName
        Next
    

    EDIT

    Sorry, that didn't work as you noticed, I hadn't had my coffee yet.

    The reason that .Net is doing that is because you are resetting the default namespace in the middle of a document. If it didn't append the empty namespace to the child elements then all child elements of <Segments> would be automatically part of the http://myurl namespace. Maybe this is the result that you want but since you didn't tell .Net that its assuming you don't.

    To say that in a different way, the output that you are getting says that <Itinerary> is in the empty namespace, <Segments> is in the http://myurl namespace and <SegmentFlight> is in the same empty namespace as <Itinerary>. If you want <SegmentFlight> to be part of the same namespace as <Segments> then you need to recursively apply the namespace. When you call ToString() .Net will output what you are expecting. Here's a recursive version:

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim xmlDoc As XDocument = XDocument.Load(New System.IO.StringReader(xmlStringFromDB))
    
        Dim ns As XNamespace = "http://myurl"
        ApplyNameSpaceToAllChildren(xmlDoc.Descendants("Itinerary").Elements(), ns)
    
        Trace.WriteLine(xmlDoc.ToString())
    End Sub
    Private Sub ApplyNameSpaceToAllChildren(ByVal elements As IEnumerable(Of XElement), ByVal ns As XNamespace)
        For Each elem In elements
            elem.Name = ns + elem.Name.LocalName
            If elem.HasElements Then
                ApplyNameSpaceToAllChildren(elem.Elements, ns)
            End If
        Next
    End Sub
    

    This outputs:

    <Itinerary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Segments xmlns="http://myurl">
        <SegmentFlight></SegmentFlight>
        <!-- more child elements -->
      </Segments>
      <References xmlns="http://myurl">
        <!-- child elements -->
      </References>
      <Fares xmlns="http://myurl">
        <!-- child elements -->
      </Fares>
    </Itinerary>