More on XML Namespaces in VB....
Posted
Sunday, December 09, 2007 11:20 PM
by
bill
A couple of weeks ago I wrote about XML Namespace issues in VB: one in particular was to do with namespace declarations being repeated in the output XML. In those cases we only looked at common namespaces for the entire document.
However the example of creating a word document I posted earlier today raises a similar issue, but this time it's with namespaces that are used in child elements not in the root element. Previously, one suggested workaround was to Import namespaces, and use expression placeholders, but as the word document shows, this does not produce what is often the desirable outcome.
The following simplified example demonstrates this:
- the goal of this exercise is to output the following XML:
<?xml version="1.0"?>
<a:root xmlns:a="url:a" xmlns:b="url:b">
<b:x></b:x>
<b:y></b:y>
</a:root>
The constraint is we want to create the x and y elements separately and insert them into the root element. .eg:
Imports <xmlns:a="url:a">
Imports <xmlns:b="url:b">
.....
Dim el1 = <b:x></b:x>
Dim el2 = <b:y></b:y>
Dim doc = <?xml version="1.0"?>
<a:root>
<%= el1 %>
<%= el2 %>
</a:root>
However that doesn't work. The output for that repeats the namespace of the b.
<a:root xmlns:a="url:a">
<b:x xmlns:b="url:b"></b:x>
<b:y xmlns:b="url:b"></b:y>
</a:root>
So I thought I would try declaring the b namespace in the root element:
Dim doc = <?xml version="1.0"?>
<a:root xmlns:b="url:b">
<%= el1 %>
<%= el2 %>
</a:root>
Now that works. That gives the correct output of :
<a:root xmlns:a="url:a" xmlns:b="url:b">
<b:x></b:x>
<b:y></b:y>
</a:root>
But what is going on here you may ask ? Why does this work ? Well VB removes xmlns attributes from the added elements for any declarations that are used in the containing node. So the minimal fix to get my word document have the namespace declarations at the start requires the correct injection of namespaces to the parent they are added to. For example :
Dim thirdParagraph = <w:p
xmlns:dm="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:dp="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<w:r><%= drawing %></w:r>
</w:p>
I also had to re-declare 2 xml namespaces in the drawing variables; for the document I had to add 5. And that is in just a simple example. What's really annoying is those namespaces were Imported at the module level in the first place. It would have been nice to write that XML literal using the imported namespaces, e.g:
Dim thirdParagraph = <w:p <%= dm %><%= wp %><%= dp %><%= r %>>
<w:r><%= drawing %></w:r>
</w:p>
Alas that doesn't work. Nor does and combination of GetXMLNamespace that I could think of.
In the need I decided it was a lot easier to just add the namespaces once to the root document and remove all of them from added elements, using a modified extension method.
<Runtime.CompilerServices.Extension()> _
Function RemoveAllNS(ByVal el As XElement, Optional ByVal includeChildNodes As Boolean = False) As XElement
Dim current = el.LastAttribute
Do While current IsNot Nothing
Dim temp = current.PreviousAttribute
If current.IsNamespaceDeclaration Then
current.Remove()
End If
current = temp
Loop
If includeChildNodes Then
For Each child In el.Descendants
RemoveAllNS(child)
Next
End If
Return el
End Function
It's a shame this doesn't work easier than this :(