home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 September / Chip_2002-09_cd1.bin / zkuste / delphi / kolekce / d56 / XMLCOMP.ZIP / DnXml.pas < prev    next >
Pascal/Delphi Source File  |  2002-06-16  |  40KB  |  1,103 lines

  1. unit DnXml;
  2.  
  3. interface
  4.  
  5. {$INCLUDE DnXml.inc}
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, TypInfo, 
  9.   {$IFDEF DN_D6_UP} Variants, {$ENDIF}
  10.   MSXML2_TLB,
  11.   DnXmlPropMapper, DnXmlListMapper;
  12.  
  13. type
  14.   TGetIdEvent = function(aObject: TObject): string of object;
  15.   TGetObjectEvent = function(aID: string): TObject of object;
  16.   TGetClassEvent = function(aClassName: string): TClass of object;
  17.   TMarkEvent = procedure(aObject: TObject; aId: string) of object;
  18.  
  19.   IXmlNodeType = IXmlDomNode;
  20.  
  21.   {:Use the TDnXmlDictionary class to connect component trees which are loaded and saved by
  22.     (different) xml- readers and writers. Simply drop it on your form and connect the reader(s) and
  23.     writer(s) to it. You can also create it runtime, using the Create method.}
  24.   TDnXmlDictionary = class(TComponent)
  25.   private
  26.     FMax: Integer;
  27.     FObjects: TStringList;
  28.     function GetCount: Integer;
  29.     function GetObjects(aIndex: Integer): TObject;
  30.     function GetIds(aIndex: Integer): string;
  31.   public
  32.     {:Constructor, in order to create the object at runtime (aOwner maybe nil).}
  33.     constructor Create(aOwner: TComponent); override;
  34.     {:Plain old destructor.}
  35.     destructor Destroy; override;
  36.     {:Plain old notification. Do not call this externally.}
  37.     procedure Notification(aComponent: TComponent; aOperation: TOperation); override;
  38.     {:Returns the internal id of the object aObject.}
  39.     function IdOfObject(aObject: TObject): string;
  40.     {:Returns the object that belongs to the id aId.}
  41.     function ObjectOfId(aId: string): TObject;
  42.     {:Adds an object to the dictionary. Usually only used by the reader and writer. If the ID already
  43.       exists, its associated component is replace by the new aObject value.}
  44.     procedure Add(aObject: TComponent; aId: string);
  45.     {:Clears the whole dictionary.}
  46.     procedure Clear;
  47.     {:The dictionary is able to generate new id, based on the contents of the dictionary. The new id
  48.       is the next integer value of the highest known integer id (non-integer id's are ignored).}
  49.     function GetNewId: string;
  50.     {:Number of objects in the dictionary.}
  51.     property Count: Integer read GetCount;
  52.     {:Retrieve an object by it's index. 0 >= aIndex < Count. }
  53.     property Objects[aIndex: Integer]: TObject read GetObjects;
  54.     {:Retrieve an id by it's index. 0 >= aIndex < Count. }
  55.     property Ids[aIndex: Integer]: string read GetIds;
  56.   end;
  57.  
  58.   {:The TDnXmlWriter class is able to convert an object tree to an xml representation. Only TComponent
  59.     objects are written to xml, except for list objects which don't have to be TComponent's (like TList
  60.     and such). }
  61.   TDnXmlWriter = class(TComponent)
  62.   private
  63.     FInternalDictionary: TDnXmlDictionary;
  64.     FTodoNodes: TInterfaceList;
  65.     FTodoObjs: TList;
  66.     FOnExternalRef: TGetIdEvent;
  67.     FStoreReadOnlyProps: Boolean;
  68.     FOnGetId: TGetIdEvent;
  69.     FDictionary: TDnXmlDictionary;
  70.     FTopNodeName: string;
  71.     FPropKinds: TTypeKinds;
  72.     FStoreTagProperty: Boolean;
  73.     FStoreNameProperty: Boolean;
  74.     procedure AddTodo(aNode: IXmlNodeType; aObject: TObject);
  75.     procedure SetRef(aNode: IXmlNodeType; aObject: TObject);
  76.     procedure FixTodos;
  77.     procedure ComponentToXmlImp(aObject: TComponent; aNode: IXmlNodeType; aOwner: TComponent);
  78.     procedure MarkWritten(aObject: TComponent; aId: string);
  79.     function ActiveDictionary: TDnXmlDictionary;
  80.     function GetId(aObject: TObject): string;
  81.     procedure SetDictionary(const Value: TDnXmlDictionary);
  82.     procedure ListItemsToXml(aObject: TObject; aNode: IXmlNodeType;
  83.       aOwner: TComponent);
  84.   public
  85.     {:Plain old constructor.}
  86.     constructor Create(aOwner: TComponent); override;
  87.     {:Plain old destructor.}
  88.     destructor Destroy; override;
  89.     {:Plain old TComponent.Notification.}
  90.     procedure Notification(aComponent: TComponent; aOperation: TOperation); override;
  91.     {:Converts the object aObject into an xml string.}
  92.     function ToXml(aObject: TComponent): string;
  93.     {:Converts the objectt aObject into xml and saves that to aXmlFile.}
  94.     procedure ToXmlFile(aXmlFile: string; aObject: TComponent);
  95.   published
  96.     {:Identifier to use for the top element of the resulting xml. Defaults to 'topobj'.}
  97.     property TopNodeName: string read FTopNodeName write FTopNodeName;
  98.     {:Whether to store read-only properties too. Remember read-only properties can be written but not read
  99.       by TDnXmlReader.}
  100.     property StoreReadOnlyProps: Boolean read FStoreReadOnlyProps write FStoreReadOnlyProps default False;
  101.     {:Whether to store the TComponent.Tag property.}
  102.     property StoreTagProperty: Boolean read FStoreTagProperty write FStoreTagProperty default True;
  103.     {:Whether to store the TComponent.Name property.}
  104.     property StoreNameProperty: Boolean read FStoreNameProperty write FStoreNameProperty default True;
  105.     {:Use this property to setup which kind of properties are written and which aren't. Property types
  106.       which are not in the list aren't written to xml.}
  107.     property PropKinds: TTypeKinds read FPropKinds write FPropKinds default tkProperties;
  108.     {:This event gets called for each object that is outside of the root object tree that is being
  109.       written.}
  110.     property OnExternalRef: TGetIdEvent read FOnExternalRef write FOnExternalRef;
  111.     {:This event is called whenever a object is written of which the id is not yet defined. If not
  112.       supplied, the id generation is handled by the dictionary, see <see method="TDnXmlDictionary.GetNewId"
  113.       text="TDnXmlDictionary.GetNewId"/> for details.}
  114.     property OnGetNewId: TGetIdEvent read FOnGetId write FOnGetId;
  115.     {:Use this property to specify which dictionary to use for attaching id to objects written. This
  116.       is usefull if you want to preserve the id's of objects between saving and loading. }
  117.     property Dictionary: TDnXmlDictionary read FDictionary write SetDictionary;
  118.   end;
  119.  
  120.   TClearListMode = (clmClear, clmFreeChildrenAndClear, clmFreeAllAndClear, clmFreeChildren, clmFreeAll);
  121.  
  122.   TSetObjPropMode = (sopmSet, sopmFreeAndSet, sopmTryAssignElseSet, sopmTryAssignElseFreeAndSet);
  123.  
  124.   {:As <see class="TDnXmlWriter" text="TDnXmlWriter"/> but converts xml to components. See
  125.     <see class="TDnXmlWriter" text="TDnXmlWriter"/> for important usage information.
  126.  
  127.     <p><b>Warning:</b> The reader clears the lists before reading it from xml. Be sure the contained
  128.     objects are freed if the list is the only one referencing to them (list is called the owner in
  129.     that case). You achieve this by using a TObjectList instead of a TList object for instance. The
  130.     <see property=TDnXmlReader.ClearListMode text="ClearListMode"/> property can be used
  131.     (with caution) to make the reader free the list objects as well. Note that in that case the
  132.     objects are only freed if they are children of the list/listowner or all objects regardless of
  133.     their owner. This means that you mix references to sibling objects in the same class. To have
  134.     this kind of flexability, create your own list class, which implements a Clear method with more
  135.     intelligence.
  136.  
  137.     <p>Same goes for object properties. The reader sets objects properties for objects readed from xml.
  138.     Be sure that the reader cannot write to properties which contain the only reference to an object
  139.     else that object would be "dangling" causing memory leaks. This can be achieved by providing a
  140.     setter method which frees the referenced object, or simply by making owned object properties
  141.     readonly by ommiting a write specifier from the property definition. Alternatively, the <see
  142.     property=TDnXmlReader.SetObjPropMode text="SetObjPropMode"/> to control how the child-object-
  143.     properties are set.}
  144.   TDnXmlReader = class(TComponent)
  145.   private
  146.     FInternalDictionary: TDnXmlDictionary;
  147.     FDictionary: TDnXmlDictionary;
  148.     FOnGetClass: TGetClassEvent;
  149.     FTodoObjs: TStringList; { of RefId, Object }
  150.     FTodoProp: TStringList; { of IntToStr(PropIndex), MapperClass }
  151.     FTodoList: TStringList; { of RefId, Object }
  152.     FTodoListXtra: TStringList; { of IntToStr(Index), MapperClass }
  153.     FExceptOnMissingProps: Boolean;
  154.     FOnExternalRef: TGetObjectEvent;
  155.     FClearListMode: TClearListMode;
  156.     FSetObjPropMode: TSetObjPropMode;
  157.     FReadNameProperty: Boolean;
  158.     FReadTagProperty: Boolean;
  159.     procedure FromXmlAssignImp(aObject: TComponent; aNode: IXmlNodeType; aOwner: TComponent = nil);
  160.     function FromXmlCreateImp(aOwner: TComponent; aNode: IXmlNodeType): TComponent;
  161.     procedure AddTodo(aRefId: string; aObject: TObject; aMapperClass: TDnXmlPropMapperClass; aPropIndex: Integer);
  162.     procedure AddListTodo(aRefId: string; aObject: TObject; aMapperClass: TDnXmlListMapperClass; aIndex: Integer);
  163.     procedure FixTodos;
  164.     procedure MarkRead(aObject: TComponent; aId: string);
  165.     function ActiveDictionary: TDnXmlDictionary;
  166.     procedure SetDictionary(const Value: TDnXmlDictionary);
  167.     procedure XmlToListItems(aObject: TObject; aNode: IXmlNodeType; aOwner: TComponent);
  168.   protected
  169.     function DoGetClass(const aClass: string): TClass; virtual;
  170.   public
  171.     {:Constructor, in order to create the object at runtime (aOwner maybe nil).}
  172.     constructor Create(aOwner: TComponent); override;
  173.     {:Plain old destructor.}
  174.     destructor Destroy; override;
  175.     {:Plain old TComponent.Notification.}
  176.     procedure Notification(aComponent: TComponent; aOperation: TOperation); override;
  177.     {:Assign the xml in aXml to aObject.}
  178.     procedure FromXmlAssign(aXml: string; aObject: TComponent);
  179.     {:Converts an xml string into a new object.}
  180.     function FromXmlCreate(aOwner: TComponent; aXml: string): TComponent;
  181.     {:Assigns the xml in aXmlFile to aObject.}
  182.     procedure FromXmlFileAssign(aXmlFile: string; aObject: TComponent);
  183.     {:Converts an xml file into a new object.}
  184.     function FromXmlFileCreate(aOwner: TComponent; aXmlFile: string): TComponent;
  185.     {:Returns the id of a loaded object. Returns empty string if the object was not loaded by the reader.
  186.       If a Dictionary is connected using the Dictionary property, you can use the IdOfObject of the
  187.       dictionary (<see method="TDnXmlDictionary.IdOfObject" text="TDnXmlDictionary.IdOfObject"/>) to
  188.       retrieve the id.}
  189.     function IdOfObject(aObject: TComponent): string;
  190.   published
  191.     {:If you're not using the delphi-vcl method of registering classes (RegisterClass and RegisterClasses
  192.       routines), then provide your own class resolving mechanism here. If you do not supply this code
  193.       unregistered objects cause the readed to raise an exception.}
  194.     property OnGetClass: TGetClassEvent read FOnGetClass write FOnGetClass;
  195.     {:Use the OnExternalRef event to resolve objects that are defined outside of the xml. If this
  196.       object was read through another xml reader, you can also use an external dictionary (see
  197.       Dictionary property).}
  198.     property OnExternalRef: TGetObjectEvent read FOnExternalRef write FOnExternalRef;
  199.     {:Use this property to specify which dictionary to use for storing id's of objects that were read.
  200.       This is useful if you want to preserve the id's when writing to xml later (to do that assign
  201.       the same dictionary to TDnXmlWriter.Dictionary before writing with it). The dictionary is also
  202.       used to retrieve external objects that need be resolved during the load of the object tree.}
  203.     property Dictionary: TDnXmlDictionary read FDictionary write SetDictionary;
  204.     {:Set this property to false if you want the reader to accept missing properties in the xml.}
  205.     property ExceptOnMissingProps: Boolean read FExceptOnMissingProps write FExceptOnMissingProps default True;
  206.     {:Use this property to setup the way lists are cleared while reading them from xml.}
  207.     property ClearListMode: TClearListMode read FClearListMode write FClearListMode default clmClear;
  208.     {:Use this property to setup the way object properties are set while reading them from xml.}
  209.     property SetObjPropMode: TSetObjPropMode read FSetObjPropMode write FSetObjPropMode default sopmSet;
  210.     {:Whether to load the TComponent.Name property. Counterpart of <see property="TDnXmlWriter.StoreNameProperty" text="TDnXmlWriter.StoreNameProperty"/>.}
  211.     property ReadNameProperty: Boolean read FReadNameProperty write FReadNameProperty default True;
  212.     {:Whether to load the TComponent.Tag property. Counterpart of <see property="TDnXmlWriter.StoreTagProperty" text="TDnXmlWriter.StoreTagProperty"/>.}
  213.     property ReadTagProperty: Boolean read FReadTagProperty write FReadTagProperty default True;
  214.   end;
  215.  
  216.   {:Provides a baseclass for classes that are able to resolve classes for the xml reader. If not used
  217.     the classes registered using RegisterClass and RegisterClasses routines are used, and/of the
  218.     TDnXmlReader.OnGetclass event. The order in which these class retrieval mechanisms are invoked is:
  219.     <ol>
  220.     <li>Registered TDnXmlClassResolver subclass
  221.     <li>OnGetClass event if provided
  222.     <li>Delphi's GetClass, classes which were registered using the RegisterClass(es) routines.
  223.     </ol> }
  224.   TDnXmlClassResolver = class(TObject)
  225.   protected
  226.     {:Override this method and provide your class resolving mechanism. If you cannot succeed, return nil.}
  227.     class function GetClass(aClassName: string): TClass; virtual; abstract;
  228.   end;
  229.  
  230.   TDnXmlClassResolverClass= class of TDnXmlClassResolver;
  231.   
  232. {:Use this routine to register your custom class which is able to resolve classtypes by their name as
  233.   found in the ClassName tags in the xml file.}
  234. procedure RegisterClassResolver(aResolver: TDnXmlClassResolverClass);
  235.  
  236. {:Use this routine to unregister the class registered using the <see routine=RegisterClassResolver
  237.   text="RegisterClassResolver"/> routine.}
  238. procedure UnregisterClassResolver(aResolver: TDnXmlClassResolverClass);
  239.  
  240. implementation
  241.  
  242. var
  243.   gClassResolvers: TList = nil;
  244.  
  245. procedure RegisterClassResolver(aResolver: TDnXmlClassResolverClass);
  246. begin
  247.   if gClassResolvers.IndexOf(aResolver) <> -1 then
  248.     raise Exception.CreateFmt('Classresolver %s was already registered', [aResolver.ClassName]);
  249.   gClassResolvers.Add(aResolver);
  250. end;
  251.  
  252. procedure UnregisterClassResolver(aResolver: TDnXmlClassResolverClass);
  253. begin
  254.   if gClassResolvers.IndexOf(aResolver) = -1 then
  255.     raise Exception.CreateFmt('Classresolver %s was not registered', [aResolver.ClassName]);
  256.   gClassResolvers.Remove(aResolver);
  257. end;
  258.  
  259. { helper funcs }
  260.  
  261. procedure SetAttribute(aNode: IXmlNodeType; const aName, aValue: string);
  262. var attr: IXMLDOMAttribute;
  263. begin
  264.   attr := aNode.ownerDocument.createAttribute(aName);
  265.   attr.value := aValue;
  266.   aNode.attributes.setNamedItem(attr);
  267. end;
  268.  
  269. function GetAttribute(aNode: IXmlNodeType; const aName: string): string;
  270. var attr: IXmlNodeType;
  271. begin
  272.   attr := aNode.attributes.getNamedItem(aName);
  273.   if attr = nil then
  274.     raise Exception.CreateFmt('Cannot find attribute %s', [aName]);
  275.   result := attr.text;
  276. end;
  277.  
  278. function HasAttribute(aNode: IXmlNodeType; const aName: string): Boolean;
  279. begin
  280.   result := aNode.attributes.getNamedItem(aName) <> nil;
  281. end;
  282.  
  283. function SetElement(aNode: IXmlNodeType; const aName: string; const aValue: string = ''): IXmlNodeType;
  284. var elem: IXMLDOMElement;
  285. begin
  286.   elem := aNode.ownerDocument.createElement(aName);
  287.   elem.text := aValue;
  288.   aNode.appendChild(elem);
  289.   result := elem;
  290. end;
  291.  
  292. function FindNode(aNode: IXmlNodeType; const aName: string): IXmlNodeType;
  293. var i: Integer;
  294. begin
  295.   result := nil;
  296.   for i := 0 to aNode.childNodes.length - 1 do
  297.   begin
  298.     if aNode.childNodes[i].nodeName = aName then
  299.     begin
  300.       result := aNode.childNodes[i];
  301.       break;
  302.     end;
  303.   end;
  304. end;
  305.  
  306. { TDnXmlWriter }
  307.  
  308. procedure TDnXmlWriter.SetRef(aNode: IXmlNodeType; aObject: TObject);
  309. var refid: string;
  310. begin
  311.   refid := ActiveDictionary.IdOfObject(aObject);
  312.   if refid <> '' then
  313.     SetAttribute(aNode, 'RefID', refid)
  314.   else
  315.     AddTodo(aNode, aObject);
  316. end;
  317.  
  318. procedure TDnXmlWriter.ListItemsToXml(aObject: TObject; aNode: IXmlNodeType; aOwner: TComponent);
  319. var
  320.   listmapper: TDnXmlListMapper;
  321.   listmapperclass: TDnXmlListMapperClass;
  322.   listowner: TComponent;
  323.   i: Integer;
  324.   node: IXmlNodeType;
  325. begin
  326.   listmapperclass := GetListMapper(aObject.ClassType);
  327.   if listmapperclass <> nil then
  328.   begin
  329.     if aObject is TComponent then
  330.       listowner := TComponent(aObject)
  331.     else
  332.       listowner := aOwner;
  333.     listmapper := listmapperclass.CreateWithObject(aObject, aOwner);
  334.     try
  335.       for i := 0 to listmapper.Count - 1 do
  336.       begin
  337.         node := SetElement(aNode, 'list-item');
  338.         if listmapper.StoresObjects then
  339.         begin
  340.           // if not yet in internal dictionary, it's not a ref...
  341.           if not listmapper.IsRef[i] and (FInternalDictionary.IdOfObject(listmapper.ObjectItem[i]) = '') then
  342.             ComponentToXmlImp(listmapper.ObjectItem[i] as TComponent, node, listowner)
  343.           else
  344.             SetRef(node, listmapper.ObjectItem[i]);
  345.         end
  346.         else
  347.         begin
  348.           node.Text := listmapper.StringItem[i];
  349.         end;
  350.       end;
  351.     finally
  352.       listmapper.Free;
  353.     end;
  354.   end;
  355. end;
  356.  
  357. procedure TDnXmlWriter.ComponentToXmlImp(aObject: TComponent; aNode: IXmlNodeType; aOwner: TComponent);
  358. var
  359.   i, iMapper: Integer;
  360.   node: IXmlNodeType;
  361.   mapperclass: TDnXmlPropMapperClass;
  362.   mapper: TDnXmlPropMapper;
  363.   id: string;
  364. begin
  365.   id := GetId(aObject);
  366.   Assert(id <> '', 'GetId failed');
  367.  
  368.   MarkWritten(aObject, id);
  369.  
  370.   SetAttribute(aNode, 'ID', id);
  371.   SetElement(aNode, 'ClassName', aObject.ClassName);
  372.  
  373.   for iMapper := 0 to GetXmlPropMapperCount(aObject.ClassType) - 1 do
  374.   begin
  375.     mapperclass := GetXmlPropMapper(aObject.ClassType, iMapper);
  376.     if mapperclass = nil then
  377.       raise Exception.CreateFmt('Cannot write xml for class %s (no property mapper(s) registered for it)', [aObject.ClassName]);
  378.  
  379.     mapper := mapperclass.CreateWithObject(aObject);
  380.     try
  381.       if mapper is TDnXmlRttiPropMapper then
  382.         TDnXmlRttiPropMapper(mapper).PropKinds := PropKinds;
  383.  
  384.       mapper.StoreReadOnlyProps := StoreReadOnlyProps;
  385.       for i := 0 to mapper.Count - 1 do
  386.       begin
  387.         if not StoreTagProperty and (mapper.PropName[i] = 'Tag') then
  388.           Continue;
  389.         if not StoreNameProperty and (mapper.PropName[i] = 'Name') then
  390.           Continue;
  391.  
  392.         if mapper.IsStored[i] then
  393.         begin
  394.           node := SetElement(aNode, mapper.PropName[i]);
  395.           if mapper.IsObject[i] then
  396.           begin
  397.             if mapper.ObjectProp[i] = nil then
  398.               node.Text := 'nil'
  399.             else
  400.             begin
  401.               if mapper.ObjectProp[i] is TComponent then
  402.               begin
  403.                 // no reference according to mapper and not yet loaded according to internal dictionary?
  404.                 if not mapper.IsRef[i] and (FInternalDictionary.IdOfObject(mapper.ObjectProp[i]) = '') then
  405.                   // it's a child, load it recursively
  406.                   ComponentToXmlImp(mapper.ObjectProp[i] as TComponent, node, aObject as TComponent)
  407.                 else
  408.                   // it's a reference, just set the refid attribute of the node
  409.                   SetRef(node, mapper.ObjectProp[i]);
  410.               end
  411.               else
  412.               begin
  413.                 // non TComponent objects are only supported for list item holders (e.g. TList)
  414.                 ListItemsToXml(mapper.ObjectProp[i], node, aOwner);
  415.               end;
  416.             end;
  417.           end
  418.           else
  419.             node.Text := mapper.Value[i];
  420.         end;
  421.       end;
  422.     finally
  423.       mapper.Free;
  424.     end;
  425.   end;
  426.  
  427.   ListItemsToXml(aObject, aNode, aOwner);
  428. end;
  429.  
  430. function TDnXmlWriter.ToXml(aObject: TComponent): String;
  431. var doc: IXMLDOMDocument2;
  432. begin
  433.   doc := CoDOMDocument.Create;
  434.   doc.documentElement := doc.createElement(TopNodeName);
  435.  
  436.   FInternalDictionary.Clear;
  437.   FTodoNodes.Clear;
  438.   FTodoObjs.Clear;
  439.   ComponentToXmlImp(aObject, doc.documentElement, nil);
  440.   FixTodos;
  441.  
  442.   result := doc.XML;
  443. end;
  444.  
  445. procedure TDnXmlWriter.AddTodo(aNode: IXmlNodeType; aObject: TObject);
  446. begin
  447.   FTodoNodes.Add(aNode);
  448.   FTodoObjs.Add(aObject);
  449. end;
  450.  
  451. function TDnXmlWriter.GetId(aObject: TObject): string;
  452. begin
  453.   result := ActiveDictionary.IdOfObject(aObject);
  454.   if result = '' then
  455.   begin
  456.     if Assigned(OnGetNewId) then
  457.     begin
  458.       result := OnGetNewId(aObject);
  459.       if result = '' then
  460.         raise Exception.CreateFmt('OnGetNewId event did not return a valid id for object of type %s', [aObject.ClassName]);
  461.       // check if the id is not already in use
  462.       if ActiveDictionary.ObjectOfId(result) <> nil then
  463.         raise Exception.CreateFmt('The id returned by the OnGetNewId event (id=%s) is already in use. OnGetNewId must return unique id''s', [result]);
  464.     end
  465.     else
  466.       // if event not connected, fall back to our own id generating mechanism
  467.       result := ActiveDictionary.GetNewId;
  468.   end;
  469. end;
  470.  
  471. constructor TDnXmlWriter.Create;
  472. begin
  473.   inherited;
  474.   FTopNodeName := 'topobj';
  475.   FTodoNodes := TInterfaceList.Create;
  476.   FTodoObjs := TList.Create;
  477.   FInternalDictionary := TDnXmlDictionary.Create(nil);
  478.   FPropKinds := tkProperties;
  479.   FStoreTagProperty := True;
  480.   FStoreNameProperty := True;
  481. end;
  482.  
  483. destructor TDnXmlWriter.Destroy;
  484. begin
  485.   FTodoNodes.Free;
  486.   FTodoObjs.Free;
  487.   FInternalDictionary.Free;
  488.  
  489.   inherited;
  490. end;
  491.  
  492. procedure TDnXmlWriter.FixTodos;
  493. var i: Integer;
  494.     refid: string;
  495. begin
  496.   try
  497.     i := 0;
  498.     while i < FTodoNodes.Count do
  499.     begin
  500.       refid := ActiveDictionary.IdOfObject(FTodoObjs[i]);
  501.       if (refid = '') and (Assigned(OnExternalRef)) then
  502.         refid := OnExternalRef(FTodoObjs[i]);
  503.       if refid <> '' then
  504.       begin
  505.         SetAttribute((FTodoNodes[i] as IXmlNodeType), 'RefID', refid);
  506.         FTodoNodes.Delete(i);
  507.         FTodoObjs.Delete(i);
  508.       end
  509.       else
  510.         inc(i);
  511.     end;
  512.  
  513.     if FTodoNodes.Count > 0 then
  514.       raise Exception.Create('One or more referenced objects could not be resolved');
  515.   finally
  516.     FTodoNodes.Clear;
  517.     FTodoObjs.Clear;
  518.   end;
  519. end;
  520.  
  521. procedure TDnXmlWriter.MarkWritten(aObject: TComponent; aId: string);
  522. begin
  523.   FInternalDictionary.Add(aObject, aId);
  524.   if FDictionary <> nil then
  525.     FDictionary.Add(aObject, aId);
  526. end;
  527.  
  528. function TDnXmlWriter.ActiveDictionary: TDnXmlDictionary;
  529. begin
  530.   if Assigned(FDictionary) then
  531.     result := FDictionary
  532.   else
  533.     result := FInternalDictionary;
  534. end;
  535.  
  536. procedure TDnXmlWriter.ToXmlFile(aXmlFile: string; aObject: TComponent);
  537. var list: TStringList;
  538. begin
  539.   list := TStringList.Create;
  540.   try
  541.     list.Text := ToXml(aObject);
  542.     list.SaveToFile(aXmlFile);
  543.   finally
  544.     list.Free;
  545.   end;
  546. end;
  547.  
  548. procedure TDnXmlWriter.SetDictionary(const Value: TDnXmlDictionary);
  549. begin
  550.   FDictionary := Value;
  551.   if FDictionary <> nil then
  552.     FDictionary.FreeNotification(Self);
  553. end;
  554.  
  555. procedure TDnXmlWriter.Notification(aComponent: TComponent;
  556.   aOperation: TOperation);
  557. begin
  558.   inherited;
  559.   if aOperation = opRemove then
  560.   begin
  561.     if aComponent = FDictionary then
  562.       FDictionary := nil;
  563.   end;
  564. end;
  565.  
  566. { TDnXmlReader }
  567.  
  568. procedure TDnXmlReader.AddListTodo(aRefId: string; aObject: TObject;
  569.   aMapperClass: TDnXmlListMapperClass; aIndex: Integer);
  570. begin
  571.   FTodoList.AddObject(aRefId, aObject);
  572.   FTodoListXtra.AddObject(IntToStr(aIndex), Pointer(aMapperClass));
  573. end;
  574.  
  575. procedure TDnXmlReader.AddTodo(aRefId: string; aObject: TObject;
  576.   aMapperClass: TDnXmlPropMapperClass; aPropIndex: Integer);
  577. begin
  578.   FTodoObjs.AddObject(aRefId, aObject);
  579.   FTodoProp.AddObject(IntToStr(aPropIndex), Pointer(aMapperClass));
  580. end;
  581.  
  582. constructor TDnXmlReader.Create;
  583. begin
  584.   inherited;
  585.  
  586.   FTodoObjs := TStringList.Create;
  587.   FTodoProp := TStringList.Create;
  588.   FTodoList := TStringList.Create;
  589.   FTodoListXtra := TStringList.Create;
  590.   FInternalDictionary := TDnXmlDictionary.Create(nil);
  591.   FExceptOnMissingProps := True;
  592.   FReadNameProperty := True;
  593.   FReadTagProperty := True;
  594. end;
  595.  
  596. destructor TDnXmlReader.Destroy;
  597. begin
  598.   FTodoObjs.Free;
  599.   FTodoProp.Free;
  600.   FTodoList.Free;
  601.   FTodoListXtra.Free;
  602.   FInternalDictionary.Free;
  603.  
  604.   inherited;
  605. end;
  606.  
  607. function TDnXmlReader.DoGetClass(const aClass: string): TClass;
  608. var i: Integer;
  609. begin
  610.   result := nil;
  611.   for i := 0 to gClassResolvers.Count - 1 do
  612.   begin
  613.     result := TDnXmlClassResolverClass(gClassResolvers[i]).GetClass(aClass);
  614.     if result <> nil then Break;
  615.   end;
  616.   if result = nil then
  617.   begin
  618.     if not Assigned(OnGetClass) then
  619.       result := GetClass(aClass)
  620.     else
  621.       result := OnGetClass(aClass);
  622.   end;
  623. end;
  624.  
  625. procedure TDnXmlReader.FixTodos;
  626. var i: Integer;
  627.     obj: TObject;
  628.     index: Integer;
  629.     mapper: TDnXmlPropMapper;
  630.     mapperclass: TDnXmlPropMapperClass;
  631.     listmapperclass: TDnXmlListMapperClass;
  632.     listmapper: TDnXmlListMapper;
  633.  
  634.   procedure _SetProp(aObject: TObject);
  635.   var
  636.     parent: TObject;
  637.   begin
  638.     parent := FTodoObjs.Objects[i];
  639.     index := StrToInt(FTodoProp[i]);
  640.     mapperclass := TDnXmlPropMapperClass(FTodoProp.Objects[i]);
  641.     mapper := mapperclass.CreateWithObject(parent);
  642.     try
  643.       mapper.ObjectProp[index] := aObject;
  644.     finally
  645.       mapper.Free;
  646.     end;
  647.     FTodoObjs.Delete(i);
  648.     FTodoProp.Delete(i);
  649.   end;
  650.  
  651.   procedure _SetList(aObject: TObject);
  652.   var
  653.     parent: TObject;
  654.   begin
  655.     parent := FTodoList.Objects[i];
  656.     index := StrToInt(FTodoListXtra[i]);
  657.     listmapperclass := TDnXmlListMapperClass(FTodoListXtra.Objects[i]);
  658.     listmapper := listmapperclass.CreateWithObject(parent);
  659.     try
  660.       listmapper.ObjectItem[index] := aObject;
  661.     finally
  662.       listmapper.Free;
  663.     end;
  664.     FTodoList.Delete(i);
  665.     FTodoListXtra.Delete(i);
  666.   end;
  667.  
  668. begin
  669.   // solve object property refs
  670.   i := 0;
  671.   while i < FTodoObjs.Count do
  672.   begin
  673.     obj := ActiveDictionary.ObjectOfId(FTodoObjs[i]);
  674.     if obj = nil then
  675.     begin
  676.       if Assigned(OnExternalRef) then
  677.         obj := OnExternalRef(FTodoObjs[i]);
  678.     end;
  679.     if obj <> nil then
  680.       _SetProp(obj)
  681.     else
  682.       inc(i);
  683.   end;
  684.  
  685.   // solve list refs
  686.   i := 0;
  687.   while i < FTodoList.Count do
  688.   begin
  689.     obj := ActiveDictionary.ObjectOfId(FTodoList[i]);
  690.     if obj = nil then
  691.     begin
  692.       if Assigned(OnExternalRef) then
  693.         obj := OnExternalRef(FTodoList[i]);
  694.     end;
  695.     if obj <> nil then
  696.       _SetList(obj)
  697.     else
  698.       inc(i);
  699.   end;
  700.  
  701.   if (FTodoObjs.Count > 0) or (FTodoList.Count > 0) then
  702.     raise Exception.Create('One or more referenced objects could not be resolved');
  703. end;
  704.  
  705. procedure TDnXmlReader.FromXmlAssign(aXml: string; aObject: TComponent);
  706. var doc: IXMLDOMDocument2;
  707. begin
  708.   FInternalDictionary.Clear;
  709.   doc := CoDOMDocument.Create;
  710.   doc.loadXML(aXml);
  711.  
  712.   FromXmlAssignImp(aObject, doc.DocumentElement);
  713.  
  714.   FixTodos;
  715. end;
  716.  
  717. function TDnXmlReader.FromXmlCreate(aOwner: TComponent; aXml: string): TComponent;
  718. var doc: IXMLDOMDocument2;
  719. begin
  720.   FInternalDictionary.Clear;
  721.   doc := CoDOMDocument.Create;
  722.   doc.loadXML(aXml);
  723.  
  724.   result := FromXmlCreateImp(aOwner, doc.DocumentElement);
  725.  
  726.   FixTodos;
  727. end;
  728.  
  729. procedure TDnXmlReader.FromXmlAssignImp(aObject: TComponent; aNode: IXmlDomNode; aOwner: TComponent);
  730. var node: IXmlNodeType;
  731.     i, iMapper: Integer;
  732.     mapper: TDnXmlPropMapper;
  733.     mapperclass: TDnXmlPropMapperClass;
  734.     obj: TObject;
  735. begin
  736.   node := FindNode(aNode, 'ClassName');
  737.   if node = nil then
  738.     raise Exception.Create('ClassName node not found in XML data');
  739.   if CompareText(node.text, aObject.ClassName) <> 0 then
  740.     raise Exception.CreateFmt('Class %s, specified in the xml data, does not match object class %s', [node.text, aObject.ClassName]);
  741.   if aNode.attributes.getNamedItem('ID') = nil then
  742.     raise Exception.Create('ID node not found in XML data');
  743.  
  744.   MarkRead(aObject, GetAttribute(aNode, 'ID'));
  745.  
  746.   // process properties for each registered property mapper
  747.   for iMapper := 0 to GetXmlPropMapperCount(aObject.ClassType) - 1 do
  748.   begin
  749.     mapperclass := GetXmlPropMapper(aObject.ClassType, iMapper);
  750.     if mapperclass = nil then
  751.       raise Exception.CreateFmt('Cannot write xml for class %s (no property mapper(s) registered for it)', [aObject.ClassName]);
  752.  
  753.     mapper := mapperclass.CreateWithObject(aObject);
  754.     try
  755.       for i := 0 to mapper.Count - 1 do
  756.       begin
  757.         if not ReadNameProperty and (mapper.PropName[i] = 'Name') then
  758.           Continue;
  759.         if not ReadTagProperty and (mapper.PropName[i] = 'Tag') then
  760.           Continue;
  761.  
  762.         if mapper.IsStored[i] then
  763.         begin
  764.           node := Findnode(aNode, (mapper.PropName[i]));
  765.           if node = nil then
  766.           begin
  767.             // property not found in xml
  768.             if ExceptOnMissingProps then
  769.               raise Exception.CreateFmt('Xml node %s not found while reading class %s', [mapper.PropName[i], aObject.ClassName])
  770.             else
  771.               Continue;
  772.           end;
  773.  
  774.           if mapper.IsObject[i] then
  775.           begin
  776.             // if the node has the (Ref)ID attribute, its a TComponent object, or if it's nil we cant
  777.             // determine so TComponent is assumed
  778.             if HasAttribute(node, 'ID') or HasAttribute(node, 'RefID') or (node.text = 'nil') then
  779.             begin
  780.               if mapper.IsAssignableObject[i] then
  781.               begin
  782.                 // its a reference
  783.                 if node.attributes.getNamedItem('RefID') <> nil then
  784.                 begin
  785.                   obj := ActiveDictionary.ObjectOfId(GetAttribute(node, 'RefID'));
  786.                   if obj = nil then
  787.                     AddTodo(GetAttribute(node, 'RefID'), aObject, mapperclass, i)
  788.                   else
  789.                     mapper.ObjectProp[i] := obj;
  790.                 end
  791.                 else
  792.                 begin
  793.                   if node.text = 'nil' then
  794.                   begin
  795.                     if SetObjPropMode in [sopmFreeAndSet, sopmTryAssignElseFreeAndSet] then
  796.                       mapper.ObjectProp[i].Free;
  797.                     mapper.ObjectProp[i] := nil
  798.                   end
  799.                   else
  800.                   begin
  801.                     // convert child object property
  802.                     if (SetObjPropMode in [sopmTryAssignElseSet, sopmTryAssignElseFreeAndSet]) and
  803.                        (CompareText(mapper.ObjectProp[i].ClassName, FindNode(node, 'ClassName').text) = 0)
  804.                     then
  805.                       FromXmlAssignImp(mapper.ObjectProp[i] as TComponent, node, aObject)
  806.                     else if SetObjPropMode in [sopmFreeAndSet, sopmTryAssignElseFreeAndSet] then
  807.                     begin
  808.                       if (mapper.ObjectProp[i] as TComponent).Owner = aObject then
  809.                       begin
  810.                         mapper.ObjectProp[i].Free;
  811.                         mapper.ObjectProp[i] := nil;
  812.                       end;
  813.                       mapper.ObjectProp[i] := FromXmlCreateImp(aObject as TComponent, node);
  814.                     end
  815.                     else
  816.                       mapper.ObjectProp[i] := FromXmlCreateImp(aObject as TComponent, node);
  817.                   end;
  818.                 end;
  819.               end
  820.               else
  821.               begin
  822.                 if (mapper.ObjectProp[i] = nil) and (node.text <> 'nil')then
  823.                   raise Exception.CreateFmt('Property %s.%s must be initialized because it is readonly and cannot be instantiated outside its owner object. It is also possible to make to property writable or non-stored', [aObject.ClassName, mapper.PropName[i]]);
  824.                 if mapper.ObjectProp[i] <> nil then
  825.                   FromXmlAssignImp(mapper.ObjectProp[i] as TComponent, node, aObject as TComponent);
  826.               end;
  827.             end
  828.             else
  829.             begin
  830.               obj := mapper.ObjectProp[i];
  831.               if (node.text = 'nil') and (obj <> nil) then
  832.                 raise Exception.Create('Non TComponent objects instances cannot have "nil" as their xml data');
  833.               if (node.text <> 'nil') and (obj = nil) then
  834.                 raise Exception.CreateFmt('Non TComponent objects cannot be instantiated from xml. Property %s was expected to have an object-instance assigned instead of nil', [mapper.PropName[i]]);
  835.               if (node.text <> 'nil') and (obj <> nil) then
  836.                 XmlToListItems(obj, node, aObject as TComponent);
  837.             end;
  838.           end
  839.           else
  840.             mapper.Value[i] := node.text;
  841.         end;
  842.       end;
  843.     finally
  844.       mapper.Free;
  845.     end;
  846.   end;
  847.  
  848.   XmlToListItems(aObject, aNode, aOwner);
  849. end;
  850.  
  851. procedure TDnXmlReader.XmlToListItems(aObject: TObject; aNode: IXmlNodeType; aOwner: TComponent);
  852. var listmapperclass: TDnXmlListMapperClass;
  853.     listmapper: TDnXmlListMapper;
  854.     listowner: TComponent;
  855.     i: Integer;
  856.     node: IXmlNodeType;
  857.     obj: TObject;
  858. begin
  859.   // process list-items
  860.   listmapperclass := GetListMapper(aObject.ClassType);
  861.   if listmapperclass <> nil then
  862.   begin
  863.     listmapper := listmapperclass.CreateWithObject(aObject);
  864.     try
  865.       if aObject is TComponent then
  866.         listowner := TComponent(aObject)
  867.       else
  868.         listowner := aOwner;
  869.  
  870.       if ClearListMode <> clmClear then
  871.       begin
  872.         for i := listmapper.Count - 1 downto 0 do
  873.         begin
  874.           obj := listmapper.ObjectItem[i];
  875.           if ClearListMode in [clmFreeChildren, clmFreeChildrenAndClear] then
  876.           begin
  877.             if TComponent(obj).Owner = listowner then
  878.               obj.Free;
  879.           end
  880.           else if ClearListMode in [clmFreeAll, clmFreeAllAndClear] then
  881.             obj.Free;
  882.         end;
  883.       end;
  884.  
  885.       if ClearListMode in [clmClear, clmFreeChildrenAndClear, clmFreeAllAndClear] then
  886.         listmapper.Clear;
  887.  
  888.       for i := 0 to aNode.ChildNodes.length - 1 do
  889.       begin
  890.         if aNode.ChildNodes[i].NodeName = 'list-item' then
  891.         begin
  892.           node := aNode.ChildNodes[i];
  893.           if listmapper.StoresObjects then
  894.           begin
  895.             if not HasAttribute(node, 'RefID') then
  896.               listmapper.Add(FromXmlCreateImp(listowner, node))
  897.             else
  898.             begin
  899.               // reference item
  900.               obj := ActiveDictionary.ObjectOfId(GetAttribute(node, 'RefID'));
  901.               listmapper.Add(obj);
  902.               if obj = nil then
  903.                 AddListTodo(GetAttribute(node, 'RefID'), aObject, listmapperclass, listmapper.Count - 1);
  904.             end;
  905.           end
  906.           else
  907.             listmapper.Add(node.Text);
  908.         end;
  909.       end;
  910.     finally
  911.       listmapper.Free;
  912.     end;
  913.   end;
  914. end;
  915.  
  916. function TDnXmlReader.FromXmlCreateImp(aOwner: TComponent; aNode: IXmlNodeType): TComponent;
  917. var
  918.   node: IXmlNodeType;
  919.   cls: TClass;
  920. begin
  921.   node := FindNode(aNode, 'ClassName');
  922.   if node = nil then
  923.     raise Exception.Create('ClassName node not found in XML data');
  924.  
  925.   cls := DoGetClass(node.text);
  926.  
  927.   if cls = nil then
  928.     raise Exception.CreateFmt('Could not resolve class %s, register this class using the ' +
  929.       'RegisterClass or RegisterClasses routines, or resolve the classes in the TDnXmlReader.' +
  930.       'OnGetClass event or register a TDnXmlClassResolver implementation', [node.text]);
  931.  
  932.   if not cls.InheritsFrom(TComponent) then
  933.     raise Exception.CreateFmt('Xml reader can only create TComponent objects. %s does not descent ' +
  934.       'from TComponent. To work with non TComponent objects use readonly properties and instantiate ' +
  935.       'the objects in the constructor', [cls.ClassName]);
  936.  
  937.   result := TComponentClass(cls).Create(aOwner as TComponent);
  938.  
  939.   FromXmlAssignImp(result, aNode, aOwner);
  940. end;
  941.  
  942. function TDnXmlReader.IdOfObject(aObject: TComponent): string;
  943. begin
  944.   result := ActiveDictionary.IdOfObject(aObject);
  945. end;
  946.  
  947. procedure TDnXmlReader.MarkRead(aObject: TComponent; aId: string);
  948. begin
  949.   FInternalDictionary.Add(aObject, aId);
  950.   if FDictionary <> nil then
  951.     FDictionary.Add(aObject, aId);
  952. end;
  953.  
  954. procedure TDnXmlReader.FromXmlFileAssign(aXmlFile: string; aObject: TComponent);
  955. var list: TStringList;
  956. begin
  957.   list := TStringList.Create;
  958.   try
  959.     list.LoadFromFile(aXmlFile);
  960.     FromXmlAssign(list.Text, aObject);
  961.   finally
  962.     list.Free;
  963.   end;
  964. end;
  965.  
  966. function TDnXmlReader.FromXmlFileCreate(aOwner: TComponent; aXmlFile: string): TComponent;
  967. var list: TStringList;
  968. begin
  969.   list := TStringList.Create;
  970.   try
  971.     list.LoadFromFile(aXmlFile);
  972.     result := FromXmlCreate(aOwner, list.Text);
  973.   finally
  974.     list.Free;
  975.   end;
  976. end;
  977.  
  978. function TDnXmlReader.ActiveDictionary: TDnXmlDictionary;
  979. begin
  980.   if FDictionary <> nil then
  981.     result := FDictionary
  982.   else
  983.     result := FInternalDictionary;
  984. end;
  985.  
  986. procedure TDnXmlReader.SetDictionary(const Value: TDnXmlDictionary);
  987. begin
  988.   FDictionary := Value;
  989.   if FDictionary <> nil then
  990.     FDictionary.FreeNotification(Self);
  991. end;
  992.  
  993. procedure TDnXmlReader.Notification(aComponent: TComponent;
  994.   aOperation: TOperation);
  995. begin
  996.   inherited;
  997.   if aOperation = opRemove then
  998.   begin
  999.     if aComponent = FDictionary then
  1000.       FDictionary := nil;
  1001.   end;
  1002. end;
  1003.  
  1004. { TDnXmlDictionary }
  1005.  
  1006. procedure TDnXmlDictionary.Add(aObject: TComponent; aId: string);
  1007. var idx, value, code: Integer;
  1008. begin
  1009.   idx := FObjects.IndexOf(aId);
  1010.   if idx <> -1 then
  1011.     FObjects.Objects[idx] := aObject
  1012.   else
  1013.   begin
  1014.     Assert(FObjects.IndexOfObject(aObject) = -1, 'Xml Dictionary failure, the object associated with id ' + aId + ' was already in the dictionary');
  1015.     FObjects.AddObject(aId, aObject);
  1016.     aObject.FreeNotification(Self);
  1017.   end;
  1018.  
  1019.   // keep track of the highest used id
  1020.   Val(aId, value, code);
  1021.   if (code = 0) and (Value > FMax) then
  1022.     FMax := value;
  1023. end;
  1024.  
  1025. procedure TDnXmlDictionary.Clear;
  1026. begin
  1027.   FObjects.Clear;
  1028. end;
  1029.  
  1030. constructor TDnXmlDictionary.Create(aOwner: TComponent);
  1031. begin
  1032.   inherited;
  1033.   FObjects := TStringList.Create;
  1034. end;
  1035.  
  1036. destructor TDnXmlDictionary.Destroy;
  1037. begin
  1038.   FObjects.Free;
  1039.   inherited;
  1040. end;
  1041.  
  1042. function TDnXmlDictionary.GetCount: Integer;
  1043. begin
  1044.   result := FObjects.Count;
  1045. end;
  1046.  
  1047. function TDnXmlDictionary.GetIds(aIndex: Integer): string;
  1048. begin
  1049.   result := FObjects[aIndex];
  1050. end;
  1051.  
  1052. function TDnXmlDictionary.GetNewId: string;
  1053. begin
  1054.   inc(FMax);
  1055.   result := IntToStr(FMax);
  1056. end;
  1057.  
  1058. function TDnXmlDictionary.GetObjects(aIndex: Integer): TObject;
  1059. begin
  1060.   result := FObjects.Objects[aIndex];
  1061. end;
  1062.  
  1063. function TDnXmlDictionary.IdOfObject(aObject: TObject): string;
  1064. var idx: Integer;
  1065. begin
  1066.   idx := FObjects.IndexOfObject(aObject);
  1067.   if idx <> -1 then
  1068.     result := FObjects[idx]
  1069.   else
  1070.     result := '';
  1071. end;
  1072.  
  1073. procedure TDnXmlDictionary.Notification(aComponent: TComponent;
  1074.   aOperation: TOperation);
  1075. var idx: Integer;
  1076. begin
  1077.   inherited;
  1078.   if aOperation = opRemove then
  1079.   begin
  1080.     idx := FObjects.IndexOfObject(aComponent);
  1081.     if idx <> -1 then
  1082.       FObjects.Delete(idx);
  1083.   end;
  1084. end;
  1085.  
  1086. function TDnXmlDictionary.ObjectOfId(aId: string): TObject;
  1087. var idx: Integer;
  1088. begin
  1089.   idx := FObjects.IndexOf(aId);
  1090.   if idx <> -1 then
  1091.     result := FObjects.Objects[idx]
  1092.   else
  1093.     result := nil;
  1094. end;
  1095.  
  1096. initialization
  1097.   gClassResolvers := TList.Create;
  1098.  
  1099. finalization
  1100.   gClassResolvers.Free;
  1101.  
  1102. end.
  1103.