Link to home
Start Free TrialLog in
Avatar of Mike Littlewood
Mike LittlewoodFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Creating class objects from an xml document

I am interested to see if anyone knows of a generic solution to creating objects from an xml document.
I have a base object which all my objects inherit from, and currently they can all save themselves as xml with their class type as a node heading, ie TMyObject.
What I want is the ability to go backwards now and create the objects from the xml document, but obviously you cannot create an object directly just from a string. Anyone got an optional idea on a method or techinique I could use to do this?
Avatar of 2266180
2266180
Flag of United States of America image

why not use the SOAP api? save objects to xml and load them back. sure, the framework is too big for what you want, but it does what you want, it's there in delphi so use it.
Avatar of Geert G
creating objects from a string =

read string from a file
read object from string

or read form from string that's read from dfm file

i'm still wondering when Delphi will put the .dfm in XML format
Avatar of Mike Littlewood

ASKER

I'm assuming Ciuly that you need to have the objects created to load them?
I want an xml file to be able to create the correct object it should be loading as well as loading it.
Avatar of chgtj001
chgtj001

XML Data Binding can be used to generate a class from a xml file. You can create object by using NewFoo,LoadFoo methods provided by the class. It's quite easy to bind a object with xml. The limitation is performance. And you can not use your base object.
ASKER CERTIFIED SOLUTION
Avatar of Geert G
Geert G
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
actually geert, for this kind of approach you would use a solution like the one provided by the tpersitent delphi streaming mechanism: registerclass ;) (which will add teh class to a list).

>> I'm assuming Ciuly that you need to have the objects created to load them?

no. you never used soap? then look around here: http://delphi.about.com/od/webservices/Developing_Web_Services_with_Delphi.htm
ciuly,
i'm an "old" fashioned programmer :)

i'll go read your link too now ;)
I wrote my own framework for this (serialization) using RTTI. supports both xml and binary and is extendable to any format. I also made about 90% of it usable from FPC (free pascal). but it's for my own use only for now, as many other usefull stuff. I was planning to come up with some kind of package and sell it for a few bucks, but people don't seem to be interested. obviously there are free alternatives and that's probably the reason why.
what I want to say is that I almost wanted to make it available for free in this questions, but when I saw that it's only 125 points ... well, I changed my mind :D
so, long story short, would any of you guys pay say ... 5$ for something like this? or less? or more?
(getting a little off-topic, I know :P )
I'll keep this question open for now and let you know what road I go down.
Is it a native Delphi or Delphi .Net?
In .Net you can use the standard XmlSerializer on System.Xml.Serialization.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
On another tought, I like this solution more...
it's less dependent on TPersistent, but still, you have to register the classes...

type
  TMyObject = class;
 
  TMyObjectClass = class of TMyObject;
 
  TMyObjectClassArray = array of TMyObjectClass;
 
  TMyObject = class
  private
    class var FClasses: TMyObjectClassArray;
  public
    class procedure RegisterClass(AClass: TMyObjectClass); static;
    class function CreateByName(AClassName: string): TMyObject; static;
  end;
 
  TBook = class(TMyObject)
  end;
 
  TKeyboard = class(TMyObject)
  end;
 
implementation
 
uses
  SysUtils,
  StrUtils;
 
{ TMyObject }
 
class function TMyObject.CreateByName(AClassName: string): TMyObject;
var
  MyClass: TMyObjectClass;
begin
  Result := nil;
  for MyClass in FClasses do
  begin
    if SameText(AClassName, MyClass.ClassName) then
    begin
      Result := MyClass.Create;
      Break;
    end;
  end;
end;
 
class procedure TMyObject.RegisterClass(AClass: TMyObjectClass);
begin
  SetLength(FClasses, Length(FClasses)+1);
  FClasses[Length(FClasses)-1] := AClass;
end;
 
initialization
  TMyObject.RegisterClass(TBook);
  TMyObject.RegisterClass(TKeyboard);
 
end.

Open in new window

did you try serializing that? you'll notice it doens't work. because in order to use serialization you must enable rtti fro that specific class and it's descendants. that is done by usiong the $M switch, which is off by default (RTTI gives some overhead). TPersistent has it already that's why people suggest extending from there.
So would the first example by huferry be the right way to do it if it has to depend on TPersistent?
yes, it is one way to do it. but that code only handles the registration and creation, not loading. for that you will still need to use the stream's read/writecomponent methods (if going with delphi's component streaming mechanism) or RTTI if going for something xml, as you want it.
there are many sites that deal with delphi and rtti on the net, all you need to do is google :)
http://www.blong.com/Conferences/BorConUK98/DelphiRTTI/CB140.htm
http://delphi.about.com/od/oopindelphi/a/delphirtti.htm
just to name a couple.
well, you don't have actually to depend on TPersistent. After you are able to create an instance of a descendant of TMyObject like.. in my example TBook, you can pass the XML to the object to be deserialize.

I include a rough example how to do this... I hope you'll get  the idea...
type
  
  TMyObject = class
  protected
    procedure DeserializeData(AXMLNode: IXMLNode); virtual; abstract;
    ...
  public
    class function Deserialize(AXml: string): TMyObject;
  end;
 
  TBook = class(TMyObject)
  private
    FTitle: string;
  protected
   procedure DeserializeData(AXMLNode: IXMLNode); override;
   ..
   
  end;
 
..
..
class function TMyObject.Deserialize(AXml: string): TMyObject;
var
  MyClassName: string;
  MyNode: IXmlNode;
begin
  // get the class name some how
  MyClassName := GetClassName(AXml);
 
  // use the method of the previous example
  Result := CreateByName(MyClassName);
 
  if Assigned(Result) then
  begin
    // convert it to xml node somehow
    MyNode := GetNode(AXml);
    Result.DeserializeData(MyNode);
  end;
end;
 
...
 
procedure TBook.DeserializeData(AXmlNode: IXmlNode); 
begin
  inherited;
  // get the value somehow...
  FTitle := GetPropertyValue(AXmlNode, 'Title');
end;

Open in new window

now there is a sample with a lot of somehow
i'm really intrested in seeing those somehows translated to code
...
>> i'm really intrested in seeing those somehows translated to code

that's what RTTI is for, in case I wasn't so obvious the last time.
here comes the some hows...
Unzip the attached file.. rename .jpg into .zip.
Unzip again the file, then you'll have the complete program.
unit uMyClass;
 
interface
 
uses
  XmlDoc,
  XmlIntf;
 
type
 
  TXmlProperty = class
  private
    FNode: IXMLNode;
  public
    constructor Create(ANode: IXmlNode);
    function GetClassName: string;
    function GetChildValue(AChildName: string): string;
  end;
 
  TMyClass = class;
 
  TMyClassClass = class of TMyClass;
 
  TMyClassClassArray = array of TMyClassClass;
 
  TMyClass = class
  private
    class var FClasses: TMyClassClassArray;
    class function CreateByName(AClassName: string): TMyClass; static;
  protected
    procedure DeserializeData(AXmlProp: TXmlProperty); virtual; abstract;
  public
    class procedure RegisterClass(AClass: TMyClassClass); static;
    class function Deserialize(ANode: IXMLNode): TMyClass; overload; static;
    class function Deserialize(AXml: string): TMyClass; overload; static;
  end;
 
  TBook = class(TMyClass)
  private
    FAuthor: string;
    FTitle: string;
  protected
    procedure DeserializeData(AXmlProp: TXmlProperty); override;
  public
    property Title: string read FTitle;
    property Author: string read FAuthor;
  end;
 
implementation
 
{ TMyClass }
 
class function TMyClass.CreateByName(AClassName: string): TMyClass;
var
  MyClass: TMyClassClass;
begin
  Result := nil;
  for MyClass in FClasses do
  begin
    if MyClass.ClassName = AClassName then
    begin
      Result := MyClass.Create;
      Break;
    end;
  end;
end;
 
class function TMyClass.Deserialize(ANode: IXMLNode): TMyClass;
var
  Prop: TXmlProperty;
begin
  Result := nil;
  Prop := TXmlProperty.Create(ANode);
  try
    Result := CreateByName(Prop.GetClassName);
    if Assigned(Result) then
      Result.DeserializeData(Prop);
  finally
    Prop.Free;
  end;
end;
 
class function TMyClass.Deserialize(AXml: string): TMyClass;
var
  XmlDoc: IXMLDocument;
begin
  Result := nil;
  XmlDoc := TXMLDocument.Create(nil);
  XmlDoc.XML.Text := AXml;
  XmlDoc.Active := True;
  Result := Deserialize(XmlDoc.ChildNodes.First);
end;
 
class procedure TMyClass.RegisterClass(AClass: TMyClassClass);
begin
  SetLength(FClasses, Length(FClasses)+1);
  FClasses[Length(FClasses)-1] := AClass;
end;
 
{ TXmlProperty }
 
constructor TXmlProperty.Create(ANode: IXmlNode);
begin
  inherited Create;
  FNode := ANode;
end;
 
function TXmlProperty.GetChildValue(AChildName: string): string;
begin
  Result := FNode.ChildValues[AChildName];
end;
 
function TXmlProperty.GetClassName: string;
begin
  Result := FNode.NodeName;
end;
 
{ TBook }
 
procedure TBook.DeserializeData(AXmlProp: TXmlProperty);
begin
  FAuthor := AXmlProp.GetChildValue('Author');
  FTitle := AXmlProp.GetChildValue('Title');
end;
 
initialization
  TMyClass.RegisterClass(TBook);
 
end.

Open in new window

xml-serial.zip
RTTI works only with published read/write properties. Sometime you also want to serialize some private members or read-only properties.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I found this:

http://www.simdesign.nl/xml.html

you can store,read and create any TPresistent to/from XML.
The component is not free though.
Sorry been a while since I saw this thread, forgot.
Going to spend a little time reading what has been written.

And sorry huferry, I should have said it was standard delphi not .net
This got me started.