Last year I spent a lot of time trying to develop xml-based WEB application with DELPHI. The initial idea was beautiful, but the result was that the things were very simple. Part of the reason is that the implementation of data binding between XML and Object is too troublesome (the other part is that it is not familiar with XSLT and it takes a lot of time to learn it).
I have always used XML Data binding provided by DELPHI. The basic method is: first use tools (such as XMLSPY) to make an XML Schema (XSD), and then use XML Data binding to generate DELPHI interfaces and classes. Of course, it is very convenient once it is generated. In the program, I just need to operate this interface. Each field will be turned into a property, and the types are as defined in XSD. But the problem is that there will always be some changes during the development process of the program. In this case, I have to open XMLSPY to modify XSD at the same time, and then run it again with the XML Data binding Wizard, which is very troublesome.
So when I think of objectification of the data set, I immediately think that I can also use RTTI to implement XML persistence of Object - in fact, the SOAP implementation started with DELPHI6 is to use RTTI to implement the conversion of Object to SOAP data (that is, XML). Obviously I was already very late. When I mentioned my plans in the article "The Powerful DELPHI RTTI - A Party-to-Link Required to Understand Multiple Development Languages", my friend Lex CHow replied to me that he was about one I did this work a few years ago, and I immediately asked him for his source code. lexlib is a library with many functions that looks a bit like the basic class library of .net (of course not that big ^O^). Object's XML persistence is only a small part of it. Because I only need this part, there is no need to use this entire library so much trouble. So I referred to lexlib and combined with the part I have implemented in "Using Simple Objectification of Data Sets with RTTI of DELPHI" to make a simple accomplish:
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 ) Then ( 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 ) ) Then 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.SaveObjToXML(aNode: IXMLNode; aObj: TPersistent);Var i: Integer; pList: TMPropList; pInfo: PPropInfo; tmpObj: TObject;begin If ( aObj Is TMDataSetProxy ) Then ( 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 ) ) Then 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. There are mainly three parts (the structure of Load and Save is similar):
First, special processing is made to TMDataSetProxy and entrust this class to handle its implementation itself. Because it is different from general classes, it requires traversing all records through ForEach, which is actually to realize the XML persistence of the data set at the same time.
The second is to recursively process Class, and of course only supports class derived from TPersistent.
Third, the general Field is simply converted to String and saved, which borrows the Filter of lexlib, and only processes data types that can be simply converted to String, filtering out those types that may cause conversion errors.
For the TMPropList used in the above code, see the implementation in "Using DELPHI's RTTI to implement data sets".
Below is the XML persistence of the dataset implemented with TMDataSetProxy. It eliminates the trouble of using TClientDataSet, and uses the method of recording fields with Node. .net also uses this method, which is different from the method of recording fields with Attribute used by TClientDataSet. Although the XML file generated in this way will be slightly larger, the benefits are also obvious, especially the XML I am planning to use in web applications, and recorded in Node will be much more convenient when using 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 that includes XML persistence of the dataset. Note that when Load, the Employee member is connected to ADODataSet2, which is connected to a table containing these fields. Each field type is the same as the Employee table, but the content is empty, and the Identity of EmployeeID is removed. After Load is completed, the contents 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 : TDSPEmployee Read FEmployee Write FEmployee; 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.SaveObjToXML( XMLDocument1.AddChild( 'Demo' ), demo ); XMLDocument1.SaveToFile( 'temp.xml' ); XMLDocument1.Active := 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.Active := false; Edit1.Text := demo.Company; Edit2. Text := IntToStr( demo.Code ); While ( demo.Employee.ForEach ) Do With ListView1.Items.Add Do Begin Caption := IntToStr( demo.Employee.EmployeeID ); SubItems.Add( demo.Employee.FirstName ); SubItems.Add( demo.Employee.LastName ); SubItems.Add( FormatDateTime( 'yyyy-mm-dd', demo.Employee.BirthDate ) ); End; Finally demo.Employee.Free; demo.Employee := Nil; demo .Free; End; end;
Finally, I can say goodbye to the troublesome XML Data binding, and I don’t have to write XSD in the future - although there are useful tools, it is better to save some trouble.