NcSerializer

{ **************************************************************************** }
{                                                                              }
{ Serializer Classes                                                           }
{                                                                              }
{ Copyright (C) 2010 Janos Janka - All rights reserved!                        }
{                                                                              }
{ **************************************************************************** }

unit NcSerializer;

interface

uses
  SysUtils, Classes, Forms, ActiveX, Contnrs, TypInfo,
  InvokeRegistry, OPConvert, OPToSOAPDomConv, XmlDoc, XmlIntf, XmlDom;

type
  /// <summary>
  ///   Represents a serializer interface abstraction.
  /// </summary>
  INcSerializer = interface
    ['{502DDBE8-D962-405E-AE7C-A0329B583BB3}']
    procedure Serialize(AObject: TObject; Stream: TStream);
    function Deserialize(Stream: TStream): TObject; overload;
    function Deserialize(AClass: TClass; Stream: TStream): TObject; overload;
  end;

  /// <summary>
  ///   Represents a serializer abstraction.
  /// </summary>
  TNcSerializer = class abstract(TInterfacedObject, INcSerializer)
  public
    procedure Serialize(AObject: TObject; Stream: TStream); virtual; abstract;
    function Deserialize(Stream: TStream): TObject; overload; virtual; abstract;
    function Deserialize(AClass: TClass; Stream: TStream): TObject; overload; virtual; abstract;

    class procedure RegisterClass(AClass: TClass); static;
    class procedure UnregisterClass(AClass: TClass); static;
  end;

  /// <summary>
  ///   Represents a SOAP/XML serializer.
  /// </summary>
  TNcXMLSerializer = class(TNcSerializer)
  private
    FObjConverter: IObjConverter;
    FXMLEncoding: string;
    FXMLEnvelope: string;
    FXMLOptions: TXMLDocOptions;
    FSOAPOptions: TSOAPConvertOptions;
    FObjectConverterOptions: TObjectConvertOptions;
  public
    constructor Create;

    procedure Serialize(AObject: TObject; Stream: TStream); overload; override;
    procedure Serialize(AObject: TObject; out XML: string); reintroduce; overload;

    function Deserialize(Stream: TStream): TObject; overload; override;
    function Deserialize(const XML: string): TObject; overload;
    function Deserialize(AClass: TClass; Stream: TStream): TObject; overload; override;
    function Deserialize(AClass: TClass; const XML: string): TObject; overload;

    property Converter: IObjConverter read FObjConverter write FObjConverter;
    property XMLEncoding: string read FXMLEncoding write FXMLEncoding;
    property XMLEnvelope: string read FXMLEnvelope write FXMLEnvelope;
    property XMLOptions: TXMLDocOptions read FXMLOptions write FXMLOptions;
    property SOAPOptions: TSOAPConvertOptions read FSOAPOptions write FSOAPOptions;
    property ObjectConverterOptions: TObjectConvertOptions read FObjectConverterOptions write FObjectConverterOptions;
  end;

  TNcClassList = class(TClassList)
  public
    function IndexOf(const AClassName, Namespace: string): Integer; overload;
  end;

implementation

uses
  NcHelpers, NcExceptions, NcResources;

var
  RegisteredClasses: TNcClassList = nil;

{ **************************************************************************** }
{ TNcSerializer                                                                }
{ **************************************************************************** }

class procedure TNcSerializer.RegisterClass(AClass: TClass);
begin
  NcContract.ArgumentIsNotNull('AClass', AClass);
  RegisteredClasses.Add(AClass);
end;

//------------------------------------------------------------------------------

class procedure TNcSerializer.UnregisterClass(AClass: TClass);
begin
  NcContract.ArgumentIsNotNull('AClass', AClass);
  RegisteredClasses.Remove(AClass);
end;

{ **************************************************************************** }
{ TNcXMLSerializer                                                             }
{ **************************************************************************** }

function TNcXMLSerializer.Deserialize(Stream: TStream): TObject;
var
  XMLDoc: TXMLDocument;
  EnvelopeNode: IXMLNode;
  ObjectNode: IXMLNode;
  TypeNode: IXMLNode;
  Converter: IObjConverter;
  SOAPConverter: TSOAPDomConv;
  ClassItemIndex: Integer;
  ClassTypeName: string;
  ClassNamespace: string;
begin
  inherited;
 
  NcContract.ArgumentIsNotNull('Stream', Stream);

  XMLDoc := TXMLDocument.Create(Application);
  try
    XMLDoc.LoadFromStream(Stream);   
    XMLDoc.Active := True;

    // Check the envelope (root) node.
    EnvelopeNode := XMLDoc.DocumentElement;
    if (EnvelopeNode = nil) or (EnvelopeNode.ChildNodes.Count = 0) then
      raise NcInvalidOperationException.Create(NC_MSG_INVALID_XML_FORMAT);

    // Create a new SOAP converter.
    if Converter = nil then
    begin
      SOAPConverter := TSOAPDomConv.Create(Application);
      SOAPConverter.Options := FSOAPOptions;
      Converter := SOAPConverter as IObjConverter;
    end
    else // Set the custom converter.
      Converter := Self.Converter;

    ObjectNode := XMLDoc.DocumentElement.ChildNodes[0];

    // Search for the type attribute.
    TypeNode := ObjectNode.AttributeNodes.FindNode('type');
    if TypeNode = nil then
      raise NcInvalidOperationException.Create(
        NC_MSG_XML_TYPE_ATTRIBUTE_NOT_FOUND);

    // Specify the ClassTypeName/ClassNamespace.
    ClassTypeName := ExtractLocalName(TypeNode.Text);
    ClassNamespace := ExtractLocalName(
      EnvelopeNode.FindNamespaceURI(ExtractPrefix(TypeNode.Text)));

    // Where is the registered class type?
    ClassItemIndex := RegisteredClasses.IndexOf(ClassTypeName, ClassNamespace);
    if ClassItemIndex = -1 then
      raise NcInvalidOperationException.Create(
        Format(NC_MSG_TYPE_IS_NOT_REGISTERED,
          [ClassNamespace + ':' + ClassTypeName])
      );

    // Create a new instance.
    Result := RegisteredClasses.Items[ClassItemIndex].Create;
    try
      Converter.InitObjectFromSOAP(Result, ObjectNode, ObjectNode);
    except
      FreeAndNil(Result);
      raise;
    end;
  finally
    XMLDoc.Free;
  end;
end;

//------------------------------------------------------------------------------

function TNcXMLSerializer.Deserialize(const XML: string): TObject;
var
  StringStream: TStringStream;
begin
  NcContract.ArgumentIsNotWhiteSpace('XML', XML);

  StringStream := TStringStream.Create(XML);
  try
    Result := Deserialize(StringStream);
  finally
    StringStream.Free;
  end;
end;

//------------------------------------------------------------------------------

function TNcXMLSerializer.Deserialize(AClass: TClass; Stream: TStream): TObject;
var
  XMLDoc: TXMLDocument;
  EnvelopeNode: IXMLNode;
  ObjectNode: IXMLNode;
  Converter: IObjConverter;
  SOAPConverter: TSOAPDomConv;
begin
  inherited;
 
  NcContract.ArgumentIsNotNull('AClass', AClass);
  NcContract.ArgumentIsNotNull('Stream', Stream);

  XMLDoc := TXMLDocument.Create(Application);
  try
    XMLDoc.LoadFromStream(Stream);   
    XMLDoc.Active := True;

    // Check the envelope (root) node.
    EnvelopeNode := XMLDoc.DocumentElement;
    if (EnvelopeNode = nil) or (EnvelopeNode.ChildNodes.Count = 0) then
      raise NcInvalidOperationException.Create(NC_MSG_INVALID_XML_FORMAT);

    // Create a new SOAP converter.
    if Converter = nil then
    begin
      SOAPConverter := TSOAPDomConv.Create(Application);
      SOAPConverter.Options := FSOAPOptions;
      Converter := SOAPConverter as IObjConverter;
    end
    else // Set the custom converter.
      Converter := Self.Converter;

    ObjectNode := XMLDoc.DocumentElement.ChildNodes[0];

    // Create a new instance.
    Result := AClass.Create;
    try
      Converter.InitObjectFromSOAP(Result, ObjectNode, ObjectNode);
    except
      FreeAndNil(Result);
      raise;
    end;
  finally
    XMLDoc.Free;
  end;
end;

//------------------------------------------------------------------------------

function TNcXMLSerializer.Deserialize(AClass: TClass;
  const XML: string): TObject;
var
  StringStream: TStringStream;
begin
  NcContract.ArgumentIsNotNull('AClass', AClass);
  NcContract.ArgumentIsNotWhiteSpace('XML', XML);

  StringStream := TStringStream.Create(XML);
  try
    Result := Deserialize(AClass, StringStream);
  finally
    StringStream.Free;
  end;
end;

//------------------------------------------------------------------------------

constructor TNcXMLSerializer.Create;
begin
  FXMLEncoding := 'utf-8';
  FXMLEnvelope := 'Envelope';
  FXMLOptions := [doNodeAutoCreate, doAttrNull, doAutoPrefix, doNamespaceDecl];
  FSOAPOptions := [soSendMultiRefObj, soSendMultiRefArray, soRootRefNodesToBody,
    soTryAllSchema, soCacheMimeResponse, soUTF8EncodeXML];
end;

//------------------------------------------------------------------------------

procedure TNcXMLSerializer.Serialize(AObject: TObject; Stream: TStream);
var
  RefId: WideString;
  XMLDoc: TXMLDocument;
  EnvelopeNode: IXMLNode;
  Converter: IObjConverter;
  SOAPConverter: TSOAPDomConv;
begin
  inherited;
 
  NcContract.ArgumentIsNotNull('AObject', AObject);
  NcContract.ArgumentIsNotNull('Stream', Stream);

  // Create a new XML document.
  XMLDoc := TXMLDocument.Create(Application);
  try
    XMLDoc.Active := True;
    XMLDoc.Options := FXMLOptions;
    XMLDoc.Encoding := FXMLEncoding;

    // Create a new SOAP converter.
    if Converter = nil then
    begin
      SOAPConverter := TSOAPDomConv.Create(Application);
      SOAPConverter.Options := FSOAPOptions;
      Converter := SOAPConverter as IObjConverter;
    end
    else // Set the custom converter.
      Converter := Self.Converter;

    // Create an envelope (root) node.
    EnvelopeNode := XMLDoc.AddChild(FXMLEnvelope);

    // Serialize the object to SOAP/Custom format.
    Converter.ObjInstanceToSOAP(
      AObject,                                 // Object reference.
      EnvelopeNode,                            // Root node.
      nil,                                     // Parent node.
      AObject.ClassName,                       // Class name.
      GetTypeData(AObject.ClassInfo).UnitName, // Unit namespace.
      FObjectConverterOptions,                 // Object converter options.
      RefId                                    // Reference identity.
    );

    // Save to stream.
    XMLDoc.SaveToStream(Stream);
  finally
    XMLDoc.Free;
  end;
end;

//------------------------------------------------------------------------------

procedure TNcXMLSerializer.Serialize(AObject: TObject; out XML: string);
const
  BufferSize = 4096;
var
  StringStream: TStringStream;
  ReadSize: Longint;
  Buffer: array[0..BufferSize - 1] of Char;
begin
  NcContract.ArgumentIsNotNull('AObject', AObject);

  StringStream := TStringStream.Create(EmptyStr);
  try
    // Serialize the object to XML.
    Serialize(AObject, StringStream);

    // Read data (block size: 4096 bytes).
    StringStream.Seek(0, soFromBeginning);
    while StringStream.Position < StringStream.Size do
    begin
      ReadSize := StringStream.Read(Buffer, BufferSize);
      if ReadSize = BufferSize then        XML := XML + Buffer      else        XML := XML + Copy(Buffer, 0, ReadSize);
    end;
  finally
    StringStream.Free;
  end;
end;

{ **************************************************************************** }
{ TNcClassList                                                                 }
{ **************************************************************************** }

function TNcClassList.IndexOf(const AClassName, Namespace: string): Integer;
var
  I: Integer;
  C: TClass;
begin
  for I := 0 to Count - 1 do
  begin
    C := Items[I];
    if (C <> nil) and SameStr(C.ClassName, AClassName)
      and SameStr(GetTypeData(C.ClassInfo).UnitName, Namespace) then
    begin
      Result := I;
      Exit;
    end;
  end;

  Result := -1;
end;

initialization
  RegisteredClasses := TNcClassList.Create;

finalization
  RegisteredClasses.Free;

end.An example:

program SerializerTest;

// {$DEFINE TREMOTABLE}

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, ActiveX, InvokeRegistry, XMLIntf, NcSerializer;

type
  {$TYPEINFO ON}

  /// <summary>
  ///   Represents a person.
  /// </summary>
  TPerson = class {$IFDEF TREMOTABLE} (TRemotable) {$ENDIF}
  strict private
    FUserId: string; // TGUID is not supported.
    FUserName: string;
    FAddress: string;
  published
    property UserId: string read FUserId write FUserId;
    property UserName: string read FUserName write FUserName;
    property Address: string read FAddress write FAddress;
  end;

  {$TYPEINFO OFF}

  TConsole = class sealed
  public
    class procedure Main; static;
  end;

class procedure TConsole.Main;
var
  Person: TPerson;
  Serializer: INcSerializer;
  MemoryStream: TMemoryStream;
begin
  // Create a new XML serializer.
  Serializer := TNcXMLSerializer.Create as INcSerializer;

  MemoryStream := TMemoryStream.Create;
  try
    // Serialization -----------------------------------------------------------

    Person := TPerson.Create;
    try
      Person.UserId := '{6842EA05-3E51-4571-B25C-FDF86B80F10E}';
      Person.Address := 'Hungary';
      Person.UserName := 'Janos Janka';
      Serializer.Serialize(Person, MemoryStream);
    finally
      Person.Free;
    end;

    // Deserialization 1 -------------------------------------------------------

    Person := TPerson(Serializer.Deserialize(TPerson, MemoryStream));
    try
      Writeln('Deserialization 1', sLineBreak,
        Format('UserId: %s%sUserName: %s%sAddress: %s%s',
          [Person.UserId, sLineBreak,
           Person.UserName, sLineBreak,
           Person.Address, sLineBreak]));
    finally
      Person.Free;
    end;

    // Deserialization 2 -------------------------------------------------------

    TNcSerializer.RegisterClass(TPerson);

    Person := TPerson(Serializer.Deserialize(MemoryStream));
    try
      Writeln('Deserialization 2', sLineBreak,
        Format('UserId: %s%sUserName: %s%sAddress: %s%s',
          [Person.UserId, sLineBreak,
           Person.UserName, sLineBreak,
           Person.Address, sLineBreak]));
    finally
      Person.Free;
    end;
  finally
    MemoryStream.Free;
  end;
end;

var
  Ret: HRESULT;
begin
  ReportMemoryLeaksOnShutdown := True;

  // Intialize COM.
  Ret := CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  if FAILED(Ret) then
    raise Exception.Create(Format(
      'Failed to initialize COM library. Error code = %d.', [Ret]));

  try
    TConsole.Main;
  except
    on E: Exception do
      WriteLn(E.ClassName + ': ' + E.Message);
  end;

  Write('Press ENTER to continue..');
  ReadLn;
end.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章