xmldelphixml-formatting

How do I get each attribute on its own line when serializing XML?


I am writing config data to an XML file. But the XML looks very unstructured or how to say

<Configuration>
  <Ftp Host="LOCALHOST" Port="21"/>
  <Pop3 Host="LOCALHOST" Port="110" Interval="30000"/>
  <Smtp Host="LOCALHOST" Port="25"/>
</Configuration>

I would like it to look like

<Configuration>
  <Ftp 
        Host="LOCALHOST" 
        Port="21"
    />
  <Pop3 
        Host="LOCALHOST" 
        Port="110" 
        Interval="30000"
    />
  <Smtp 
        Host="LOCALHOST" 
        Port="25"
    />
</Configuration>

Is that possible in any way Here is a snippet of my Delphi code for this. I have functions/procedure for all types but just showing 2 here

constructor TConnXml.Create(const FileName: string);
begin
  inherited Create;
  fConfigfile     := FileName;
  fXMLDoc         := TXMLDocument.Create(Application);
  fXMLDoc.Options := [doNodeAutoIndent];
  if FileExists(fConfigfile) then
    fXMLDoc.LoadFromFile(fConfigfile)
  else
    begin
      fXMLDoc.Active := True;
      fXMLDoc.AddChild('Configuration');
      fXMLDoc.SaveToFile(fConfigfile);
    end;
end;

constructor TConnXml.Create;
begin
  Create(SettingsFileBuild);
end;

function TConnXml.ReadString(const Section, Key, Default: string): string;
var
  Node: IXMLNode;
begin
  Node := fXMLDoc.DocumentElement.ChildNodes.FindNode(Section);
  if Assigned(Node) and Node.HasAttribute(Key) then
    Result := Node.Attributes[Key]
  else
    Result := Default;
end;

procedure TConnXml.WriteString(const Section, Key, Value: string);
var
  Node: IXMLNode;
begin
  if ReadString(Section, Key, '') = Value then
    Exit;
  Node := fXMLDoc.DocumentElement.ChildNodes.FindNode(Section);
  if not Assigned(Node) then
    Node := fXMLDoc.DocumentElement.AddChild(Section);
  Node.Attributes[Key] := Value;
  fModified := True;
  Save;
end;

procedure TConnXml.Save;
begin
  if not fModified then
    Exit;
  if fBackup then
    CopyFile(PChar(fConfigfile), PChar(fConfigfile + '.bak'), False);
  fXMLDoc.Active := True;
  fXMLDoc.SaveToFile(fConfigfile);
  fModified := False;
end;

function TConnXml.ReadBoolean(const Section, Key: string; Default: Boolean): Boolean;
begin
  Result := Boolean(ReadInteger(Section, Key, Integer(Default)));
end;

procedure TConnXml.WriteBoolean(const Section, Key: string; Value: Boolean);
begin
  WriteInteger(Section, Key, Integer(Value));
end;

Solution

  • If you produce this XML and it is for configuration purposes, then making it more readable has its purpose. I use XML for configuration a lot and I only use attributes when really applicable.

    I would write it like this:

    <Configuration>
      <Ftp> 
        <Host>LOCALHOST</Host> 
        <Port>25</Port>
      </Ftp>
      <Pop3> 
        <Host>LOCALHOST/<Host> 
        <Port>110</Port>
        <Interval>30000</Interval>
      </Pop>
      <Smtp> 
        <Host>LOCALHOST</Host> 
        <Port>25</Port>
      </Smtp>
    </Configuration>
    

    Using some other format, then XML, is also a solution. But if you stick to XML then my answer is one way to organize XML in a human readable fasion. Also if you avoid attributes then conversion to JSON for instance is very simple.

    Even if XML is bloated with markup I find it readable if structured well. And although it was meant for computer data exchange I find it very good for configuration files. YAML looks fine, but for me it lacks that explicit structure :)

    Update:

    Due to the request for the code I updated the answer with additional info. To get XML like mine bellow, all you have to do is change one procedure. On the other hand this is basic XML handling so I advise you to learn it.

    function TConnXml.ReadString(const Section, Key, Default: string): string;
    var
      Node: IXMLNode;
      Child: IXMLNode;
    begin
      Node := fXMLDoc.DocumentElement.ChildNodes.FindNode(Section);
    
      if not Assigned(Node) then 
      begin
        Result := Default;
        Exit;
      end;
    
      Child:= Node.FindNode(Key);
    
      if not Assigned(Child) then 
      begin
        Result := Default;
        Exit;
      end;
    
      Result := Child.Text;
    end;
    
    procedure TConnXml.WriteString(const Section, Key, Value: string);
    var
      Node: IXMLNode;
      Child: IXMLNode;
    begin
      if ReadString(Section, Key, '') = Value then
        Exit;
    
      Node := fXMLDoc.DocumentElement.ChildNodes.FindNode(Section);
    
      if not Assigned(Node) then
        Node := fXMLDoc.DocumentElement.AddChild(Section);
    
      Child:= Node.ChildNodes.FindNode(Key);
    
      if not Assigned(Child) then
        Child:= Node.AddChild(Key);
    
      Child.Text := Value;
      fModified := True;
      Save;
    end;
    

    I wrote it without testing so there may be some mistakes in it, but that is the code you should use.