Search code examples
xmldelphitxmldocument

Cannot enumerate XML Tree via TXMLDocument


I try to use TXMLDocument in Delphi to enumerate a XML file. But whenever I exit the ProcessFile procedure, there will be an "Access violation" exception.

I declare XMLFile as an interface object IXMLDocument, instead of TXMLDocument, and do not call free it manually.

Below is the code:

//  Invoke ProcessFile in the main app
ProcessFile('C:\Myfile.xlf', True);

//  Process the node recursively
procedure ProcessNode1(Node: IXMLNode);
var
  Index: Integer;
begin
  if (Node.NodeName = 'trans-unit') then
  begin
    //  Process 'trans-unit'
  end
  else
    for Index := 0 to Node.ChildNodes.Count - 1 do
      ProcessNode1(Node.ChildNodes[Index]);
end;

procedure ProcessFile(const SrcFileName: string; const FixMode: Boolean);
var
  XmlFile: IXMLDocument;
  MainNode, FileNode: IXMLNode;
  OriginalFileName, SrcLang, DstLang: string;
begin
  //  Initialize the COM, otherwise we cannot use MSXML
  CoInitialize(nil);

  //  Open the XML document
  XmlFile := TXMLDocument.Create(nil);
  try
    XmlFile.LoadFromFile(SrcFileName);
    //  XmlFile.Active := True;

    MainNode := XmlFile.DocumentElement;
    FileNode := MainNode.ChildNodes['file'];

    OriginalFileName := FileNode.GetAttribute('original');
    SrcLang := FileNode.GetAttribute('source-language');
    DstLang := FileNode.GetAttribute('target-language');

    //  Output the information
    //  If removing the following statement, then everything will be OK.
    Writeln(Format('Call TranslateFile. OriginFile: %s. SrcFile: %s. SrcLang: %s. DstLang: %s. FixMode: %s.',
          [OriginalFileName, ExtractFileName(SrcFileName), SrcLang, DstLang, BoolToStr(FixMode, True)]));

    //  Enumerate the DOM tree
    ProcessNode1(FileNode);
  finally
    CoUninitialize;
  end;
end;

Blow is the XML document

<?xml version="1.0" encoding="utf-16"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd" xmlns:soluling="http://www.soluling.com">
  <file original="a.rc" source-language="en-US" target-language="bg-BG" datatype="plaintext">
    <body>
      <group id="String" datatype="winres">
        <group id="IDS_TEST">
          <trans-unit id="String.IDS_TEST1">
            <source>a</source>
            <target state="translated" state-qualifier="fuzzy-match">b</target>
            <soluling:datatype>string</soluling:datatype>
            <soluling:rowstatus>rsInUse</soluling:rowstatus>
          </trans-unit>
        </group>
      </group>
    </body>
  </file>
</xliff>

Solution

  • You are calling CoUninitialize() while the XmlFile MainNode, and FileNode objects are still active, so the RTL can't release them properly when they go out of scope when ProcessFile() exits, hence the Access Violation.

    You need to clear those references before unloading the COM library, eg:

    procedure ProcessFile(const SrcFileName: string; const FixMode: Boolean);
    var
      XmlFile: IXMLDocument;
      MainNode, FileNode: IXMLNode;
      ...
    begin
      //  Initialize the COM, otherwise we cannot use MSXML
      CoInitialize(nil);
      try
        //  Open the XML document
        XmlFile := LoadXMLDocument(SrcFileName);
        try
          MainNode := XmlFile.DocumentElement;
          FileNode := MainNode.ChildNodes['file'];
          ...
        finally
          // ADD THIS!
          FileNode := nil;
          MainNode := nil;
          XmlFile := nil;
        end;
      finally
        CoUninitialize;
      end;
    end;
    

    Alternatively, separate the loading/unloading of the COM library from your tree processing code, eg:

    procedure DoProcessFile(const SrcFileName: string; const FixMode: Boolean);
    var
      XmlFile: IXMLDocument;
      MainNode, FileNode: IXMLNode;
      ...
    begin
      //  Open the XML document
      XmlFile := LoadXMLDocument(SrcFileName);
      MainNode := XmlFile.DocumentElement;
      FileNode := MainNode.ChildNodes['file'];
      ...
    end;
    
    procedure ProcessFile(const SrcFileName: string; const FixMode: Boolean);
    begin
      //  Initialize the COM, otherwise we cannot use MSXML
      CoInitialize(nil);
      try
        DoProcessFile(SrcFileName, FixMode);
      finally
        CoUninitialize;
      end;
    end;