XML persistence of objects with Delphi RTTI
[Mental Studio] Raptor [blog]
Last year I spent a lot of time attempting to develop XML-based web application development with Delphi. The initial imagination is very beautiful, but the things that make it are very simple. Part of the reason is that the data binding between XML to Object is too much trouble (the other part is because it is not familiar with XSLT, and learning it has spent a lot of time).
I have always made XML Data Binding provided by Delphi, the basic practice is to do an XML Schema (XSD) with tools (such as XMLSPY) and then generate Delphi interfaces and classes with XML Data Binding. Of course, once it is good, it is very convenient. In the program, I just have to operate this interface, all of which are becomes attribute, and the type is also as defined in XSD. But the problem is that the program will always have some changes during the development process. In this case, I have to modify the XSD in this case, and then reuse the XML Data Binding Wizard once, very trouble.
So after I think the object of the data set, I immediately thought that I can use RTTI to implement Object's XML persistence - in fact, the SOAP implementation started by Delphi6 is to implement Object to SOAP data (already XML). Conversion. Obviously I was already very late, I said that when I said in "Strong Delphi RTTI", I need to understand a variety of development languages. "When I planned me, my friend Lex Chow replied to me said that he was about one I have done this work before the year, I immediately came to him with him. Lexlib is a library that he written is a library having a lot of functions. It looks a bit like .NET's basic class library (of course not so big ^ o ^), Object's XML persistence is just a small part. Because I only need this part, I don't have to use this whole library so trouble, so I have made a simple one in the part of Lexlib and combined with my simple object of Delphi's RTTI to achieve data set. achieve:
TMXMLPERSISTENT = Class (TOBJECT)
public
Class Procedure Loadobjfromxml (anode: IXMLNode; AOBJ: TPERSISTENT);
Class Procedure SaveObjtoxml (anode: IXMLNode; AOBJ: TPERSISTENT);
END;
Const
Defaultfilter: TTYPEKINDS = [Tkinteger, Tkchar, TkenuMeration,
Tkfloat, Tkstring, Tkset, Tkwchar, Tklstring, Tkwstring, Tkint64];
{TMXMLPERSISTENT}
Class Procedure TMXMLPERSISTENT.LOADOBJFROMXML (anode: IXMLNode;
AOBJ: TPERSISTENT);
VAR
i: integer;
PList: tmproplist;
PINFO: PPROPINFO;
Tmpobj: TOBJECT;
Begin
IF (aobj is tmdatasetproxy)
(AOBJ As TmdataSetProxy) .loadFromxml (anode)
Else
Begin
PList: = TMProplist.create (AOBJ);
Try
For i: = 0 to PLIST.PROPCOUNT - 1 DO
Begin
Pinfo: = plist.props [i]; if (pinfo ^ .proptype ^ .kind = tkclass) THEN
Begin
Tmpobj: = TOBJECT (Integer (getPropValue (Aobj, Pinfo ^ .name));
IF (Assigned (Tmpobj) And (Tmpobj IS TPERSIStent).
Loadobjfromxml (Anode.childNodes [WideString (Pinfo ^ .name),
Tmpobj as tpersistent;
End
Else if (pinfo ^ .proptype ^ .kind in defaultfilter) THEN
SetPropValue (aobj, pinfo ^ .name,
String (Anode.childNodes [WideString (Pinfo ^ .Name)]. Text);
END;
Finally
Plist.free;
END;
END;
END;
Class Procedure TMXMLPERSISTENT.SAVEOBJTOML (anode: IXMLNode;
AOBJ: TPERSISTENT);
VAR
i: integer;
PList: tmproplist;
PINFO: PPROPINFO;
Tmpobj: TOBJECT;
Begin
IF (aobj is tmdatasetproxy)
(AOBJ As TmdataSetProxy) .savetoxml (anode)
Else
Begin
PList: = TMProplist.create (AOBJ);
Try
For i: = 0 to PLIST.PROPCOUNT - 1 DO
Begin
Pinfo: = pList.props [i];
IF (Pinfo ^ .propType ^ .kind = tkclass) THEN
Begin
Tmpobj: = TOBJECT (Integer (getPropValue (Aobj, Pinfo ^ .name));
IF (Assigned (Tmpobj) And (Tmpobj IS TPERSIStent).
SaveObjToxml (anode.addchild (WideString (pinfo ^ .name),
Tmpobj as tpersistent;
End
Else if (pinfo ^ .proptype ^ .kind in defaultfilter) THEN
Anode.addchild (WideString (Pinfo ^ .Name) .Text: =
GetPropValue (AOBJ, PINFO ^ .Name);
END;
Finally
Plist.free;
END;
END;
END;
This implementation should be said to be very simple. Mainly three parts (LOAD and SAVE structure are similar):
First, it is specially handled for TMDataSetProxy, entrusting this class to handle its implementation, because it is different from the general class, it is necessary to pass all records through Foreach, which is actually the XML persistence of the data set.
The second is to recursively handle Class, which is only supported from TPERSISTENT.
Third, the general Field is simply converted to String Save, which draws on the Filter of Lexlib, only dealing with those data types that can simply turn into string, filter out the type that may cause conversion error.
The TMProplist used in the above code is shown in the implementation of "Simple Objecttization" with Delphi's RTTI to implement the data set. Below is the XML persistence of the data set implemented with TMDataSetProxy. It is necessary to use the TClientDataSet, and use the Node record field, .NET is also used in such a way that the way TclientDataSet is used in the way. Although this generated XML file will be slightly large, it is also an obvious, especially I am ready to use the XML recorded in the web application, which is more convenient to use XSLT.
Procedure tmdatasetProxy.LoadFromxml (anode: ixmlnode);
VAR
I, J: Integer;
PINFO: PPROPINFO;
Prow: ixmlnode;
Begin
For j: = 0 to anode.childnodes.count - 1 do
Begin
FDataSet.Append;
Prow: = anode.childnodes [j];
For i: = 0 to fproplist.propcount - 1 do
Begin
Pinfo: = fproplist.props [i];
IF (pinfo ^ .proptype ^ .kind in defaultfilter) THEN
Setvariant (i,
String (ProW.childNodes [WideString (Pinfo ^ .Name)]. Text));
END;
Endedit;
END;
FDataSet.first;
END;
Procedure TmdataSetProxy.savetoxml (anode: ixmlnode);
VAR
i: integer;
PINFO: PPROPINFO;
Prow: ixmlnode;
Begin
While foreach do
Begin
Prow: = anode.addchild ('row');
For i: = 0 to fproplist.propcount - 1 do
Begin
Pinfo: = fproplist.props [i];
IF (pinfo ^ .proptype ^ .kind in defaultfilter) THEN
Prow.addchild (WideString (pinfo ^ .name) .Text
: = GetVariant (i);
END;
END;
END;
Below is a simple DEMO, including XML persistence for the data set. Note that the Employee member is adodataset2 when LOAD is connected, which is connected to a table containing these fields, the types of fields are the same as the Employee table, but the content is empty, and remove the Identity of EMPLOYEEID. After the LOAD is complete, the content of these fields in the Employee table will be copied to this table.
TDemoCompany = Class (TPERSIStent)
Private
Femployee: tdspemployee;
Fcompany: string;
Fcode: integer;
Published
Property Employee: TDSPEMPLOYEEEEEEEE: TDSPEMPLOYEEEEEEEEE: TDSPEMPLOYEEEEEEEE: TDSPEMPLOYEEEEEEE:
Property Company: String Read FCompany Write Fcompany;
Property Code: Integer Read Fcode Write Fcode; End;
Procedure TFORM1.SAVECLICK (Sender: TOBJECT);
VAR
Demo: tdemocompany;
Begin
Demo: = tdemocompany.create;
Demo.employee: = tdspemployee.create (adodataset1);
Demo.company: = 'DEMO Company';
DEMO.CODE: = 987654;
Try
XmlDocument1.active: = true;
TMXMLPERSISTENT.SAVEOBJTOML (XmlDocument1.Addchild ('Demo'), Demo;
XmlDocument1.savetofile ('Temp.xml');
XMLDocument1.act: = false;
Finally
Demo.employee.free;
Demo.employee: = NIL;
Demo.Free;
END;
END;
Procedure TFORM1.LOADCLICK (Sender: TOBJECT);
VAR
Demo: tdemocompany;
Begin
Demo: = tdemocompany.create;
Demo.employee: = tdspemployee.create (adodataset2);
Try
XmlDocument1.active: = true;
XmlDocument1.LoadFromfile ('Temp.xml');
TMXMLPERSISTENT.LOADOBJFROMXML (XmlDocument1.childNodes.last, Demo);
XMLDocument1.act: = false;
Edit1.Text: = DEMO.COMPANY;
Edit2.Text: = INTTOSTR (DEMO.CODE);
While (demo.employee.Foreach) DO
With listview1.items.add do
Begin
CAPTION: = INTOSTR (Demo.employee.employeeid);
Subitems.Add (demo.employe.firstname);
Subitems.Add (demo.employee.lastname);
Subitems.Add (Formator-mm-DD ', Demo.employee.birthdate);
END;
Finally
Demo.employee.free;
Demo.employee: = NIL;
Demo.Free;
END;
END;
Finally, I can bid farewell to the trouble XML Data Binding, and I don't have to write XSD in the future - although there is a tools, it is good to save things.
[Mental Studio] Raptor JAN.29-05