delphimsxml

Delphi IXMLDOMDocument2 could not load XML file with % in the file name


I have encountered a strange bug while loading a file into IXMLDOMDocument2. The method load returns False, if the file name contains % and sets parseError to:

The system cannot locate the resource specified.

The file exists and when remove % from its name, it works just fine.

Is there any way to load XML file with % in its name?


Solution

  • The documentation for IXMLDOMDocument.load() says that the xmlSource source parameter accepts one of the following values:

    You are trying to pass a file name, which the method treats as a URL (the first option in the list above). Character % in URLs is reserved for percent-encoding. The IXMLDOMDocument.load() method is trying to decode the URL from the string you passed as an argument, which may result in a slightly different file name. The tricky part is that this doesn't happen when the percent character isn't followed by a pair of hexadecimal digits, meaning that: Test%.xml will be decoded as Test%.xml, but Test%21.xml will be decoded as Test!.xml, or Test%4A.xml will be decoded as TestJ.xml.

    You should be careful what you pass to the IXMLDOMDocument.load() method. You have several options.

    Encode the file name as a URL

    This is easy. You can use TNetEncoding.URL from the System.NetEncoding unit:

    var FileName := 'C:\test%21.xml';
    var Doc := CoDOMDocument.Create;
    Doc.load(TNetEncoding.URL.Encode(FileName));
    

    Pass an IStream as an argument:

    Open a file using TFileStream (or TFile.OpenRead() from the System.IOUtils unit) and wrap it in a TStreamAdapter from the System.CLasses unit, which implements the IStream interface declared in the Winapi.ActiveX unit.

    var FileName := 'C:\test%21.xml';
    var Stream: IStream := TStreamAdapter.Create(TFile.OpenRead(FileName), soOwned);
    var Doc := CoDOMDocument.Create;
    Doc.load(Stream);
    

    Use the loadXML() method instead:

    There is an IXMLDOMDOcument.loadXML() method that allows you to load XML content from a string. You can use TFile.ReadAllText() (unit System.IOUtils) to read the content of the file and pass it to the loadXML() method.

    var FileName := 'C:\test%21.xml';
    var Doc := CoDOMDocument.Create;
    Doc.loadXML(TFile.ReadAllText(FileName));
    

    I don't recommend loading XML using this option, because it loads the whole content of the file into memory.