Creating XML files with Delphi and FireMonkey on Mac

Let's share the knowledge with your friends

I got some questions about how to manipulate XML files in FireMonkey applications when running on Mac.

One of the solution for that is to use the Mac API (Cocoa), there are several class and different ways to work with XML on Mac, in this post I’m going to show one way to create an XML file, if you are interested to learn more about the XML class available for Mac, visit the Apple documentation website.

Cocoa provide several classes/interfaces to work with XML, the sample project used on this post use NSXMLDocument, NSXMLElement, NSXMLNode and NSData interfaces and related classes, all of them can be found in the Macapi.Foundation unit.

The NSXMLDocument interface and TNSXMLDocument class are the starting point to create the XML Document, where we define the version and encoding for the XML file.

procedure TFrmXml.CreateXMLButtonClick(Sender: TObject);
var
  XmlDoc: NSXMLDocument;
  Root, Book, Author, Publisher: NSXMLElement;
  Data: NSData;
begin

  // Create Xml Document
  XmlDoc := TNSXMLDocument.Create;
  XmlDoc.setVersion(NSSTR('1.0'));
  XmlDoc.setCharacterEncoding(NSSTR('UTF-8'));

After that we need to define what XML elements (nodes) will be added to the XML, since I’m going to create several elements I created three helper methods (CreateElement and Attribute) to help me with that.

The CreateElement method has two implementations, the first one is very simple, just create a new TNSXMLElement based on the name and value parameters.

function TFrmXml.CreateElement(Name, Value: String): NSXMLElement;
begin
  Result := TNSXMLElement.Create;
  Result.setName(NSSTR(Name));
  Result.setStringValue(NSSTR(Value));
end;

The second CreateElement implementation creates a new element which can contain attributes, in order to pass a NSXMLNode that represent a attribute, we will use the Attribute helper method.

function TFrmXml.CreateElement(Name: String; Attr: NSXMLNode): NSXMLElement;
begin
  Result := TNSXMLElement.Create;
  Result.initWithName(NSSTR(Name));

  if Assigned(Attr) then
    Result.addAttribute(Attr);
end;

Below a short overview about the NSXMLNode class extracted from Apple documentation:

Objects of the NSXMLNode class are nodes in the abstract, logical tree structure that represents an XML document. Node objects can be of different kinds, corresponding to the following markup constructs in an XML document: element, attribute, text, processing instruction, namespace, and comment. In addition, a document-node object (specifically, an instance of NSXMLDocument) represents an XML document in its entirety. NSXMLNode objects can also represent document type declarations as well as declarations in Document Type Definitions (DTDs). Class factory methods of NSXMLNode enable you to create nodes of each kind. Only document, element, and DTD nodes may have child nodes.

Among the NSXML family of classes—that is, the Foundation classes with the prefix “NSXML” (excluding NSXMLParser)—the NSXMLNode class is the base class. Inheriting from it are the classes NSXMLElement, NSXMLDocument, NSXMLDTD, and NSXMLDTDNode. NSXMLNode specifies the interface common to all XML node objects and defines common node behavior and attributes, for example hierarchy level, node name and value, tree traversal, and the ability to emit representative XML markup text.

NSXMLNode could be a comment, text, element and other types in Cocoa, the Attribute helper method ensure we will receive a NSXMLNode of type attribute.

function TFrmXml.Attribute(Name, Value: String): NSXMLNode;
var
  Node: Pointer;
begin
  Node := TNSXMLNode.OCClass.attributeWithName(NSSTR(Name), NSSTR(Value));
  Result := TNSXMLNode.Wrap(Node);
end;

 

Now is time to use the helper methods to create the XML document and at the end use the NSData object to format and save the XML file. This is what the following code does:

  // Create the root doc element including one attribute
  Root := CreateElement('BookStore', Attribute('url', 'http://www.amazon.com'));
  XmlDoc.initWithRootElement(Root);

  // Create the first Book node
  Book := CreateElement('Book', Attribute('Name', 'Steve Jobs'));

  // Create the Author and Publisher elements
  Author := CreateElement('Author', 'Walter Isaacson');
  Publisher := CreateElement('Publisher', 'Simon Schuster (October 24, 2011)');

  // Add the elements to the XML
  Root.addChild(Book);
  Book.addChild(Author);
  Book.addChild(Publisher);

  // Create the second Book node
  Book := CreateElement('Book', Attribute('Name',
    'Clean Code: A Handbook of Agile Software Craftsmanship'));
  Author := CreateElement('Author', 'Robert C. Martin');
  Publisher := CreateElement('Publisher',
    'Prentice Hall; 1 edition (August 11, 2008)');

  // Add the elements from the second Book node to the XML
  Root.addChild(Book);
  Book.addChild(Author);
  Book.addChild(Publisher);

  // Makes the Xml output more human-readable inserting
  // carriage returns and indenting nested elements
  Data := XmlDoc.XMLDataWithOptions(NSXMLNodePrettyPrint);
  Data.writeToFile(NSSTR(XMLLocation.Text), true);

  XmlContent.Lines.LoadFromFile(XMLLocation.Text);

end;

end.

Here the resulting app on Mac OS X:

You can download the source code directly from the RAD Studio demos repository here and it was tested with Delphi XE2 and XE3.

I originally mention that TXMLDocument was not available for Mac, and this is incorrect, thanks Chris Rolliston for the heads up and to migrate my sample to TXMLDocument.

When you drop a TXMLDocument component on your design the default DOMVendor MSXML, you have to change to ADOM XML v4 to support cross-platform. In case you have the unit Xml.Win.msxmldom added on your form you won’t be able to compile for Mac, after you set the DOMVendor to ADOM XML v4 remove that unit from your code and everything will be fine.

Here the version using TXMLDocument:

unit MainForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants, XML.XMLDoc, FMX.Dialogs,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Layouts,
  FMX.Memo, FMX.Edit, FMX.Effects, FMX.Objects, Xml.xmldom, Xml.XMLIntf;

type
  TFrmXml = class(TForm)
    CreateXMLButton: TButton;
    XmlContent: TMemo;
    XMLLocation: TEdit;
    Label1: TLabel;
    Image1: TImage;
    procedure CreateXMLButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  end;

var
  FrmXml: TFrmXml;

implementation

{$R *.fmx}

procedure TFrmXml.CreateXMLButtonClick(Sender: TObject);
var
  XmlDoc: IXMLDocument;
  Root, Book, Author, Publisher: IXMLNode;
begin
  // Create Xml Document
  XmlDoc := TXMLDocument.Create(nil);
  XmlDoc.Active := True;
  XmlDoc.Options := XmlDoc.Options + [doNodeAutoIndent];
  XmlDoc.Version := '1.0';

  // Create the root doc element with one attributes
  Root := XmlDoc.CreateNode('BookStore');
  Root.Attributes['url'] := 'http://www.amazon.com';
  XmlDoc.DocumentElement := Root;

  // Create the first Book node
  Book := XmlDoc.CreateNode('Book');
  Book.Attributes['Name'] := 'Steve Jobs';

  // Create the Author and Publisher elements
  Author := XmlDoc.CreateNode('Author');
  Author.Text := 'Walter Isaacson';
  Publisher := XmlDoc.CreateNode('Publisher');
  Publisher.Text := 'Simon Schuster (October 24, 2011)';

  // Add the elements to the XML
  Root.ChildNodes.Add(Book);
  Book.ChildNodes.Add(Author);
  Book.ChildNodes.Add(Publisher);

  // Create the second Book node
  Book := XmlDoc.CreateNode('Book');
  Book.Attributes['Name'] := 'Clean Code: A Handbook of Agile Software Craftsmanship';
  Author := XmlDoc.CreateNode('Author');
  Author.Text := 'Robert C. Martin';
  Publisher := XmlDoc.CreateNode('Publisher');
  Publisher.Text := 'Prentice Hall; 1 edition (August 11, 2008)';

  // Add the elements from the second Book node to the XML
  Root.ChildNodes.Add(Book);
  Book.ChildNodes.Add(Author);
  Book.ChildNodes.Add(Publisher);

  XmlDoc.SaveToFile(XMLLocation.Text);

  XmlContent.Lines.LoadFromFile(XMLLocation.Text);

end;

procedure TFrmXml.FormCreate(Sender: TObject);
begin
  XMLLocation.Text := IncludeTrailingPathDelimiter(GetHomePath) + 'create.xml';
end;

end.

With this you have everything you need to manipulate XML file on Windows and Mac.


Let's share the knowledge with your friends
3 replies
  1. Lester
    Lester says:

    This is also exactly what I need, for my first ever PC/Mac application.

    I should however mention that with XE2 Update 4, I require the following units:

    Xml.xmldom, Xml.XMLIntf, Xml.xmldoc

    Not having Xml.xmldoc errors when you try to use TXMLDocument saying that it is not defined.

    Reply
  2. Geanni
    Geanni says:

    Hi,
    Does anyone try to implement a xml parser on firemonkey c++? I tried to do something similiar with the delphi version but without success 🙁

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.