How to insert an XML node into an existing XMLDocument

I have two methods: One creates an XMLDocument. It calls another method that creates a node to insert into that document. I have these separate because there are situations where I need to handle the node creation a little differently.

The structure of my finished XML file is very simple:

<?xml version="1.0" encoding="UTF-8"?>
<RootNode version="1.0">
   <BurnIn name="Default">
      <Item  xloc="0.02" yloc="0.98"/>
   </BurnIn>
</RootNode>

This is the method that generates that file:

Public Function BuildScannerXMLFile() As XMLDocument {

Var xml As New XMLDocument

Var root As XMLNode
root = xml.AppendChild(xml.CreateElement("RootNode"))
root.SetAttribute("version", "1.0")

//Create a new XMLDocument with just a BurnIn node
var BurnInNode as XMLDocument 
BurnInNode = BuildXMLBurnInNode

//Import the BurnIn node from the new file into the master XML document
var importedNode as XMLNode
importedNode = xml.ImportNode(BurnInNode.DocumentElement, True)
root.AppendChild(importedNode)

return xml
}

And the node creation method:

Public Function BuildXMLBurnInNode(name as string = "", desc as string = "") As XMLDocument {

var xml as new XMLDocument

Var BurnIn As XMLNode
BurnIn = xml.AppendChild(xml.CreateElement("BurnIn"))
//Skipping the code that handles name and desc arguments, for brevity here

Var item as XMLNode
item = burnin.AppendChild(xml.CreateElement("item"))
item.SetAttribute("xloc", "0.02" )
item.SetAttribute("yloc", "0.98" )

return xml

This works perfectly: BuildScannerXMLFile generates a fully formed XML file that’s exactly what I want in that situation.

Now elsewhere, I want to re-use the BuildXMLBurnInNode method in a couple places. For example, when constructing a master XML file that contains multiple nodes:

In a button, where you assign a name and description to one of these BurnIn nodes:

if tLibraryItemName.Text <> ""  then
  //Put the current library of burnins into a local variable, for convenience
  var workingLibrary as XMLDocument
  workingLibrary = MainWindow.LibraryXML

  //Generate the Burnin Node, and add the name and description to it
  Var Burnin As New XmlDocument
  Burnin = BuildXMLBurnInNode(tLibraryItemName.Text, tLibraryItemDesc.Text)
      
  //Extract the BurnInNode for insertion into the library XML
  var importedNode as XMLNode
  importedNode = Burnin.ImportNode(Burnin.DocumentElement, True)
  
  //This is where it fails
  workingLibrary.AppendChild(importedNode)
  
  me.Close
else
  beep
  if tLibraryItemName.Text = "" then 
    tLibraryItemName.BackgroundColor = color.red
  end if
end if

This fails at workingLibrary.AppendChild(importedNode) – I keep getting an XMLDomException in the debugger (error code 4, "node used in a document that did not create it"). I’ve also tried using InsertNode, but I get the same error.

So in the BuildScannerXMLFile method I have access to the ā€œrootā€ element, and I’m appending to that. In the button I don’t, and I think that’s why it’s failing. So how do I insert the Burnin node into the workingLibrary XML document?

You write burnin.ImportNode, but need to write workingLibrary.importNode, since you want to import in this xml-doc. My guess.

You are indeed importing your Burnin node into itself. You should be importing it into the workinglibrary.

I would consider passing the main XMLDocument to the node creating functions, so that you don’t have to import all these nodes, but create them in the correct XMLDocument right away. You then return only the node that can be appended as child in one pass. Your BuildXMLBurnInNode would then look like this:

Public Function BuildXMLBurnInNode(xml As XMLDocument, name as string = "", desc as string = "") As XMLNode {

Var BurnIn As XMLNode = xml.CreateElement("BurnIn")
//Skipping the code that handles name and desc arguments, for brevity here

Var item as XMLNode = BurnIn.AppendChild(xml.CreateElement("item"))
item.SetAttribute("xloc", "0.02" )
item.SetAttribute("yloc", "0.98" )

Return BurnIn
}

Your main method would look like this:

Public Function BuildScannerXMLFile() As XMLDocument {

Var xml As New XMLDocument

Var root As XMLNode = xml.AppendChild(xml.CreateElement("RootNode"))
root.SetAttribute("version", "1.0")

//Create a new the BurnIn node
var BurnInNode as XMLNode = BuildXMLBurnInNode(xml)

// Add it to the root element
root.AppendChild(BurnInNode)

Return xml
}

Or maybe even better:

Public Function BuildScannerXMLFile() As XMLDocument {

Var xml As New XMLDocument

Var root As XMLNode = xml.AppendChild(xml.CreateElement("RootNode"))
root.SetAttribute("version", "1.0")

//Add new the BurnIn node
root.AppendChild(BuildXMLBurnInNode(xml))

Return xml
}

What I’m doing here is this:

  1. Generate a complete XMLDocument that contains a single node, by calling BuildXMLBurnInNode – what this returns is a full XML doc, that looks like this:

This code:

//Generate the Burnin Node, and add the name and description to it
  Var Burnin As New XmlDocument
  Burnin = BuildXMLBurnInNode(tLibraryItemName.Text, tLibraryItemDesc.Text)

results in this XML:

<?xml version="1.0" encoding="UTF-8"?>
   <BurnIn name="Name" description="Description">
      <item xloc="0.25" yloc="0.20" />
   </BurnIn>
  1. I am only interested in what’s between <BurnIn> and </BurnIn> so I am extracting that part into a new XMLNode called importedNode:
  //Extract the BurnInNode for insertion into the library XML
  var importedNode as XMLNode
  importedNode = Burnin.ImportNode(Burnin.DocumentElement, True)

Which results in this XML:

<BurnIn name="Name" description="Description">
   <item xloc="0.25" yloc="0.20"/>
</BurnIn>

Now I want to place the contents of importedNode into the larger XML document that’s inside workingLibrary

There is no ā€œmainā€ XMLDocument. The reason I’m building the nodes independently is that there are 2, possibly 3 different XML Documents that are created at different points in the application. Some of them contain nothing but a single <BurnIn> node. Some contain many. one will contain both burnin nodes and other stuff.

the ImportNode function works inside my BuildScannerXMLFile method, but I think that’s because in that code I have ready access to the root element of that XMLDocument, which is in scope as an XMLNode in that code. The BurnIn nodes live within that root node, so appending to root with root.AppendChild(importedNode) is straightforward.

In building the library of BurnIn nodes, which is a separate document containing multiple burnin nodes, I need to do the same thing, but appending a node to an already fully formed XML document.

That is exactly why you would handle BurnIn as an XMLNode and not as an XMLDocument, that contains the XML Header that you do not need and do not want.

If you want to create an XMLDocument containing only the BurnIn node, consider the following code:

Var xml As New XMLDocument
xml.DocumentElement.AppendChild(BuildXMLBurnInNode(xml))

Presto, you have created an XMLDocument containing only the BurnIn node…

There is always the XMLDocument that you want to place the new XMLNode in. Regardless of which XMLDocument that is, normally in the calling routine to you node creation function you ā€˜know’ the XMLDocument, so if you pass it to the node creation function, there will be no need for an import of the node into the XMLDocument. Sometimes, if your calling functions are nested, you may need to pass the XMLDocument to all the functions.

An XMLNode is always part of an XMLDocument, and only if you create it in another XMLDocument than the one that you want to place it in, you need to import it into the other XMLDocument.

Consider the following code that uses the BuildXMLBurnInNode in 4 different ways:

// first example xml
Var xml As New XMLDocument
Var root As XMLNode = xml.AppendChild(xml.CreateElement("RootNode"))
root.SetAttribute("version", "1.0")
root.AppendChild(BuildXMLBurnInNode(xml))

// second example xml2
Var xml2 As New XMLDocument
xml2.DocumentElement.AppendChild(BuildXMLBurnInNode(xml2))

// thirst example xml3
Var xml3 As New XMLDocument
Var othernode As XMLNode = xml3.AppendChild(xml3.CreateElement("OtherRootNode"))
Var stillanothernode As XMLNode = othernode.AppendChild(xml3.CreateElement("SomeOtherNode"))
othernode.AppendChild(BuildXMLBurnInNode(xml3))
stillanothernode.AppendChild(BuildXMLBurnInNode(xml3))

// fourth example xml4
Var xml4 As New XMLDocument
Var childNode As XMLNode = BuildXMLBurnInNode(xml4)
Var childNode2 As XMLNode = BuildXMLBurnInNode(xml4)
childNode.AppendChild(childnode2)
xml4.DocumentElement.AppendChild(childnode)

Here I use the BuildXmlBurnInNode function in 4 different XMLDocuments in one piece of code; the first creates your example, the second is a single node document containing only the BurnIn node, the third adds the BurnIn node on different levels and finally the fourth nests two BurnIn nodes.

To show the magic of passing the XMLDocument, look at the following code:

Public Function BuildXMLBurnInNode(xml As XMLDocument, nestedburnins As Integer = 0, name As String = "", desc As String = "") As XMLNode {

Var BurnIn As XMLNode = xml.CreateElement("BurnIn")
//Skipping the code that handles name and desc arguments, for brevity here

Var item As XMLNode = BurnIn.AppendChild(xml.CreateElement("item"))
item.SetAttribute("xloc", "0.02" )
item.SetAttribute("yloc", "0.98" )

// Add child burnin nodes when requested
If nestedburnins > 0 Then BurnIn.AppendChild(BuildXMLBurnInNode(xml, nestedburnins - 1))

Return BurnIn
}

And the calling code:

// fifth example xml5; adds 10 nested burnin nodes
Var xml5 As New XMLDocument
xml5.DocumentElement.AppendChild(BuildXMLBurnInNode(xml5, 10))

Anyway, if you do not want to use XMLNode and want to keep on importing foreign nodes into your working XML, here is the revised code that you can use:

If tLibraryItemName.Text <> ""  Then
  //Put the current library of burnins into a local variable, for convenience
  Var workingLibrary as XMLDocument = MainWindow.LibraryXML

  //Generate the Burnin Node, and add the name and description to it
  Var Burnin As XmlDocument = BuildXMLBurnInNode(tLibraryItemName.Text, tLibraryItemDesc.Text)
      
  //Extract the BurnInNode and import it in the workingLibrary DOM for insertion into the library XML
  Var importedNode as XMLNode = workingLibrary.ImportNode(Burnin.DocumentElement, True)
  
  workingLibrary.AppendChild(importedNode)
  
  Me.Close
Else
  beep
  If tLibraryItemName.Text = "" Then 
    tLibraryItemName.BackgroundColor = color.red
  End if
End if

I’ll look at your other examples a little later today. But this last example doesn’t work. I’m now getting a slightly different XMLDomException on the workingLibrary.AppendChild(importedNode)
line: Error 3, ā€œhierarchy request errorā€

I will be roasted for this, but if all you do is CREATE XML files, then it is faster to treat them as simple text files.
I had a need for a large XML document.
By swapping calls of Addnode etc etc , for a set of methods that append text to a text file, I was able to speed up the creation of a working XML output by a huge degree.

So for example, I might call

call AddXMLHeaders (TheTextVariable)
//other code
call AddXMLFooter (TheTextVariable)

then expand to

call AddXMLHeaders (TheTextVariable)
//other code
call AddpropertiesArea (TheTextVariable)
for x as integer = 0 to myitems.count
   call MyItem.Serialise(TheTextVariable)
   //MYItem is a class with properties
   //The Serialise  method adds a node header and footer, filled with attributes
next
call AddXMLFooter (TheTextVariable)

Thanks but speed isn’t a concern here. These files are all going to be relatively small, including the library file, which will be the biggest of them.

Perry,

Can you try to do the following:

workingLibrary.DocumentElement.AppendChild(importedNode)

Yup - that did it. Thanks!

There can only be one Root in XML, where exactly do you want to insert the node?

Inside root. The reason i mentioned that in my example above is that when you’re creating an XMLDocument from scratch, you have a root object (I have always called it ā€œrootā€ but you can call it whatever you want). Appending the imported node in that block of code worked fine for me, but that was because I could append it to the root element, which i had as an available object within that block of code.

Outside that code, in a button, I load up the XML file I wanted to add a node to, but I couldn’t specify the root object in the same way, because the ā€œrootā€ XMLNode I created was out of scope and doesn’t exist inside the button.

In any case, Robert’s code worked.

Not roasting …But if you build the xml as a string you need to make sure that you convert any xml entities that happens to be found within your node strings… For example any "<" must be converted to "&lt;"

If you do not you will have either invalid xml or not what you expect.

Indeed. And yes, I did. It was a small faff, but so worth it for speed.
(It doesn’t work the other way round of course - parsing XML is much harder than writing it.)

Glad the OP has a simple solution.
I oversold the speed aspect I think, but it was the ā€˜ease of inserting a node into an XML output’ aspect that I figured would be useful.
No worries.

1 Like

So maybe this is related to inserting a node, but I’m seeing something odd:

When my app launches, in the Opening Event, the Library XMLDocument property is populated from a text file on disk.

After I insert a new node, which also saves the changes to disk, I can verify that the Library XML property contains exactly what I want: the new child node is there at the end and is correctly formatted. In another method I walk through the child nodes of the Library XML to build a dictionary of the Name and Description attributes of each of the <burnin> nodes. It works for all nodes except the one I added - until I either load the XML in again from disk, or convert the library XMLDocument to a string and load that back in as a new XMLDocument.

If I step through the code, any of the nodes that were present before the new one was appended will show the Name and Description attribute values. But the new node shows both of those values as an empty string. It knows those new nodes are there: If I started with 3 nodes in the file I read in, and I added 2 new ones, the loop iterates 5 times. The last two have the correct LocalName (<Burnin>), but empty name and description attributes, even though the XMLDocument itself has name and description values.

Now, if I reload the previously saved XML file from disk into the Library XML property, all 5 nodes will be correctly read in that loop.

Similarly, if I create a new XMLDocument, convert the Library XML to a string, and then LoadXML(LibraryXMLString) it also works.

So obviously everything is there after I append those imported nodes, but there’s something weird about how the XMLDocument is behaving in that state. Any idea what that’s all about? Is that a bug?

Maybe it has something to do with the order you do things in the BuildXMLBurnInNode. You first append the burnin node to the XML and then add the item node. Maybe first completely build the BurnIn node before appending it to the XML; You could try this:

Public Function BuildXMLBurnInNode(name as string = "", desc as string = "") As XMLDocument {

var xml as new XMLDocument

Var BurnIn As XMLNode = xml.CreateElement("BurnIn")
//Skipping the code that handles name and desc arguments, for brevity here

Var item as XMLNode = xml.CreateElement("item")
item.SetAttribute("xloc", "0.02" )
item.SetAttribute("yloc", "0.98" )

BurnIn.AppendChild(item)
xml.AppendChild(BurnIn)

return xml

I’ll give that a try this morning. However, the XML itself, in the Library XML file is complete and correct. It’s when I read that back out and start walking through the nodes that the most recently appended ones don’t have attributes. If I first convert the Library XML file to a string, then load it back into a new XMLDocument, those attributes show up when I walk the nodes. That seems wrong.

I’ll see if I can reproduce it in a simple test app and will post that if so

Ok, Here’s a simple test project:

What this does is load an XMLDocument in from a constant, to a property called LibraryXML. It displays this XML in a text area. There are three buttons:

ā€œAdd Nodeā€
This button will create a node using the importNode method we’ve been discussing, then append it to the LibraryXML file. As you can see in the middle TextArea, the node is added. In the TextArea on the right (TextArea3), you will see the name/description attributes for all the nodes in the LibraryXML file. If you compare, you’ll see that the attributes are missing in this list, but they are in the libraryXML property, which you can see in the middle TextArea. In other words, everything is in LibraryXML at this point. Yet, the name/description attributes show up blank for all new nodes.

ā€œReset Library XMLā€
This just clears the LibraryXML property and the text areas, then resets the app it to its original state. Click this before using the string conversion.

ā€œAdd Node W/String Conversionā€
This button does exactly the same thing as ā€œAdd Nodeā€, except it adds an extra step: before walking through the nodes, it converts LibraryXML to a string, then loads it into a new XMLDocument, and then replaces the contents of LibraryXML with that. the loop that walks the code is the same. But now, the name/description attributes all appear.

If you copy the result of ā€œAdd Nodeā€ and ā€œAdd Node W/String Conversionā€ from the middle text area into text files and run a diff, they’re identical.

So what’s happening here?