XMLFile

XMLFile is a Lazarus/Free Pascal unit, which being added to an application allows creating and maintaining configuration data. It works similar to INI files with richer set of features provided by underlying XML format.

Note: this module is not compatible with Delphi because DOM implementations in Delphi and Lazarus/Free Pascal are different.

In order to use XMLFile in the project the file "XMLFile.pas" should be copied into location, where the Pascal compiler could find it (along with project's other .pas or .pp files, for example) and included in units "uses" clause, where it required.
It provides a class called "TXMLFile" with properties and methods discussed below.

The main difference between XMLFile and XMLConfig, found in Lazarus, is that XMLFile keeps values in text nodes while XMLConfig saves them as attributes. Using separate nodes for values allows saving complex structures inside configuration files, examples include icons, multi-line lists and texts, SQL statements and even complete XML or HTML documents.

Public properties

FileName: string;
- full path + file name of the XML configuration file on the disk.

ReadOnly: boolean;
- a flag which prevents modification of the external file on disk. The in-memory copy is still changeable, regardless of the state of this flag.

ReadError: boolean;
- internal flag indicating status of the last read or write operation. For "read" operations it indicates if data where obtained from the file or substituted by the default value. For writing it is set to true if disk error occurred.

SectionDelimiter: char;
(default is ".") - a character, which separates tag (section) names, when accessing nested tags. obviously, it must not be found in tag names.

Public methods

constructor Create(const FileName: string; const topNodeName : string; readOnly : boolean = true);

Creates an instance of TXMLFile class and reads the content of the file from disk. Parameter "FileName" is a full path to the existing file or the file you are about to create. If the file does not exist, the empty XML structure with a root node named as specified by "topNodeName" is created in memory and with "ReadOnly = false" the attempt to write it to the disk is made, otherwise the parameter "topNodeName" is just ignored. If a new file cannot be written onto disk or provided path is not accessible, then "ReadOnly" is set to "true" automatically.

Example (for MS Windows)

var
  xConf : TXMLFile;
....
 // read or create local per-user config file
xConf := TXMLFile.Create(GetEnvironmentVariable('LOCALAPPDATA') +
            ChangeFileExt(ExtractFileName(ParamStr(0)), '.cfg'),
        'Config', false);

Example (for Linux)

var
  xConf : TXMLFile;
....
 // read or create local per-user config file
xConf := TXMLFile.Create(GetAppConfigFile(False), 'Config', false);

function ReadString(aSection, aKey : string; aDefault : string = emptyString) : string;

This is the main method to read values, as XML is stored in a text file. In order to read the value "Ctrl+O" from the structure shown below

<?xml version="1.0"?> 
<Configuration> 
  <ShortCuts> 
    <frmMain> 
      <amActionList> 
        <!--menu File --> 
        <actOpen>Ctrl+O</actOpen> 
      </amActionList> 
    </frmMain> 
  </ShortCuts> 
</Configuration>

the parameters "aSection" and "aKey" must be as follows:

aSection := 'ShortCuts.frmMain.amActionlist'; 
aKey := 'actOpen';

assuming that SectionDelimiter is ".".
Note that root node "Configuration" is not included.

The full code for reading action shortcuts is show below:

procedure LoadActionShortCuts(amList: TActionList); 
var 
  secName : string; 
  i : integer; 
  scText : string; 
begin 
  secName := 'ShortCuts' + gConf.SectionsDelimiter + 
             amList.Owner.Name + 
             gConf.SectionsDelimiter + amList.Name; 
  if gConf.SectionExists(secName) then 
    begin 
      for i := 0 to amList.ActionCount - 1 do 
        begin 
          scText := gConf.ReadString(secName, 
                    amList.Actions[i].Name, emptyString); 
          if not Empty(scText) then 
            try // trying to prevent crash if shortcut text is invalid 
              TAction(amList.Actions[i]).ShortCut := 
                 TextToShortCut(scText); 
            except 
  { // debugging, for production don't do anything  
              on E : exception do 
                ShowMessage('Invalid Shortcut for ' + 
                            amList.Actions[i].Name + 
                            ': ' + scText); 
  }
            end; 
        end; 
    end; 
end;

The following methods use ReadString internally and just convert the output value to the appropriate format:

function ReadInteger(aSection, aKey : string; iDefault : integer = 0) : integer;

function ReadFloat(aSection, aKey : string; rDefault : double = 0) : double;

function ReadBoolean(aSection, aKey : string; bDefault : boolean = false; yesValue : string = '1') : boolean;

Function ReadBoolen has an extra parameter to define what text is considered as "Yes" or "True" value (the default is "1"), any other text value will return "False".

function ReadBinaryStream(aSection, aKey : string; aStream : TStream) : integer;

Function ReadBinaryStream allows reading any data into memory buffer. It employs internal Text-to-Hex conversion. The variable of TStream descendant class must be created before this function called.

The similar set for writing data into XML config file shown below.

procedure WriteString(aSection, aKey, aValue : string; suspend : boolean = false);

WriteString is the main method, used internally by most of the others. The extra parameter "suspend" governs flushing to the disk behaviour. If it is set to "False" (default) the data is written to the physical file immediately (unless the file is read-only), otherwise changes remained in memory buffer. Note, that any "write" operation re-writes the whole XML document on the disk, this ensures that no changes lost, but may cause quite high disk load, if the program saves many changes at once.

procedure WriteInteger(aSection, aKey : string; aValue : integer; suspend : boolean = false);

procedure WriteFloat(aSection, aKey : string; aValue : double; suspend : boolean = false);

procedure WriteBoolean(aSection, aKey : string; aValue : boolean;
                       yesValue : string = '1'; noValue : string = '0'; suspend : boolean = false);

WriteBoolean has two extra parameters to define text representing both "True" and "False" values.

procedure WriteBinaryStream(aSection, aKey : string; aStream : TStream);

Below is the very simplified example of using WriteBinaryStream to save an icon from TImage into XML configuration file

procedure SaveImage(img : TImage);
var
  aStream : TMemoryStream;
begin
  aStream := TMemoryStream.Create;
  try
    img..Picture.Icon.SaveToStream(aStream);
    xConf.WriteBinaryStream('Image', 'icon', aStream);
   finally
     aStream.Free;
    end;
 end;

Method DeleteKey removes the key from XML configuration file. Note, if the "key" has any child nodes, they will be removed as well.

procedure DeleteKey(aSection, aKey : string; suspend : boolean = false);

The following is the set of methods to deal with attributes, they are self-explanatory.

function ReadAttribute(aSection, aKey, aAttribute : string; aDefault : string = emptyString) : string;

procedure WriteAttribute(aSection, aKey, aAttribute : string; aValue : string; suspend : boolean = false);

procedure DeleteAttribute(aSection, aKey, aAttribute : string; suspend : boolean = false);

Some extra methods to utilize XML capabilities

procedure ListChildren(aSection : string; sChildren : TStrings);

Lists all child nodes in a TSrings descendant.

function SectionExists(aSection : string) : boolean;

Below is two methods to implement "frequently used" or "recently opened" lists.

procedure AddToList(aSection, aKey : string; aValue : string; capacity : integer = 0);

procedure ReadList(aSection, aKey : string; aList : TStrings);

Procedure AddToList creates, if necessary, and adds aValue on top of the list. Then it checks for and removes duplicate values, and trims the list to a given capacity (default is 9 elements). It also saves the capacity as an attribute of the node. The resulting list has a LIFO structure, last added element has 0 index.

 
Last edit: Victor 2012-11-28