[ACCEPTED]-Issue building an XML document using TXMLDocument-txmldocument

Accepted answer
Score: 12

Disclaimer: Tested with D2007.

Your code does indeed create the XML (<label/>) as shown 17 in this modified function:

function createXMLDocument(): TXMLDocument;
var
  res: TXMLDocument;
  rootNode: IXMLNode;
  sl : TStringList;
begin
  res := TXMLDocument.Create(nil);
  res.Active := true;
  rootNode := res.AddChild('label');
  // create string for debug purposes
  sl := TStringList.Create; // not needed
  sl.Assign(res.XML);  // Not true: sl is empty after this assignment
  ShowMessage(sl.text);// sl is NOT empty!
  sl.Free;             // don't forget to free it! use try..finally.. to guarantee it!
  //add more elements
//  generateDOM(rootNode);
  Result := res;
end;

But it calls 16 for a lot of remarks:
- You don't need a local res variable, just 15 use the Result.
- You don't need an extra 14 StringList to see the XML: Result.Xml.Text
- Don't 13 forget to Free the sl StringList if you create 12 one.
- The XmlDocument you return is unusable outside the function and gives an AV if you try.

Why?
It's because an XMLDocument is 11 intended to be used as a Component with an Owner, or as an Interface otherwise, in 10 order to manage its lifetime.
The fact that you 9 use an Interface to hold rootNode causes 8 it to be destroyed at the end of the CreateXmlDocument 7 function. And if you look at the code in 6 TXMLNode._Release, you'll see that it triggers TXMLDocument._Release which calls 5 Destroy unless there is an Owner for the 4 XMLDocument (or an interface holding a reference 3 to it).
This is why the XMLDocument is valid 2 and populated inside the CreateXMLDocument 1 function but not available outside it unless you return an Interface or provide an Owner.

See the alternate solutions below:

function createXMLDocumentWithOwner(AOwner: TComponent): TXMLDocument;
var
  rootNode: IXMLNode;
begin
  Assert(AOwner <> nil, 'createXMLDocumentWithOwner cannot accept a nil Owner');
  Result := TXMLDocument.Create(AOwner);
  Result.Active := True;
  rootNode := Result.AddChild('label');
  OutputDebugString(PChar(Result.Xml.Text));
  //add more elements
//  generateDOM(rootNode);
end;

function createXMLDocumentInterface(): IXMLDocument;
var
  rootNode: IXMLNode;
begin
  Result := TXMLDocument.Create(nil);
  Result.Active := True;
  rootNode := Result.AddChild('label');
  OutputDebugString(PChar(Result.Xml.Text));
  //add more elements
//  generateDOM(rootNode);
end;


procedure TForm7.Button1Click(Sender: TObject);
var
  doc: TXmlDocument;
  doc2: IXMLDocument;
begin
  ReportMemoryLeaksOnShutdown := True;

  doc := createXMLDocument;
  // ShowMessage( doc.XML.Text ); // cannot use it => AV !!!!
  // already freed, cannot call doc.Free;

  doc := createXMLDocumentWithOwner(self);
  ShowMessage( doc.XML.Text );

  doc2 := createXMLDocumentInterface;
  ShowMessage( doc2.XML.Text );
end;
Score: 4

The Delphi Help of TXMLDocument.AddChild method says 11 (at the bottom):

Note: Do not call AddChild 10 to add a child to the document element of 9 this document. When adding data to the XML 8 document, use the AddChild method of the 7 document element or of the node in the hierarchy 6 that should be the parent of the new node.

And 5 this is what you are doing right? :-)

Here 4 is an introduction article about Delphi XML Document Programming and shows 3 how you can work with the TXMLDocument.DocumentElement 2 property instead of your definition of the 1 rootnode variable in your code.

Score: 2

In my similar implementation, I declare 1 res as IXMLDocument instead of TXMLDocument.

var
   XMLDoc: IXMLDocument;
.
.
   XMLDoc := TXMLDocument.Create(nil);
   XMLDoc.Active := True;
.
.
   XMLDoc.SaveToFile(Filename);
   XMLDoc.Active := False;

More Related questions