
{******************************************}
{                                          }
{             FastReport v5.0              }
{         RaveReport -> FastReport         }
{                                          }
{         Copyright (c) 1998-2010          }
{          by Anton Khayrudinov            }
{            Fast Reports Inc.             }
{                                          }
{******************************************}

unit ConverterRR2FR;

interface

uses

  { Delphi }

  Classes,
  SysUtils,
  Graphics,
  Printers,
  Forms,
  FileCtrl,
  Dialogs,
  StdCtrls,
  Controls,
  ComCtrls,

  { FastReport }

  frxClass,
  frxBarCode,
  frxBarcod,
  frxADOComponents,
  frxBDEComponents,
  frxDBXComponents,
  frxIBXComponents,
  frxCustomDB,

  { RaveReport }

  RpDefine,
  RpRave,
  RpBase,
  RpSystem,
  RpBars,
  RvClass,
  RvProj,
  RvCsRpt,
  RvDefine,
  RvCsStd,
  RvCsBars,
  RvCsDraw,
  RvCsData,
  RvDatabase,
  RvDriverDataView;

type

  TRaveLoaderForm = class(TForm)

    lbTop: TLabel;
    pb: TProgressBar;
    bt: TButton;
    lbBottom: TLabel;

    procedure btClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);

  private

    FRunning: Boolean;
    FPage: string;
    FReport: string;

    procedure Stop;
    procedure UpdateLabel;
    procedure SetReport(const s: string);
    procedure SetPage(const s: string);
    procedure SetItemsCount(n: Integer);
    procedure SetComponentName(const s: string);
    function IsRunning: Boolean;

  public

    property Report: string write SetReport;
    property Page: string write SetPage;
    property Component: string write SetComponentName;
    property ItemsCount: Integer write SetItemsCount;

    { It's true if a user hasn't pressed the 'Cancel' button. }

    property Running: Boolean read IsRunning;

    { It's called when an item has been processed. }

    procedure NextItem;

    procedure Reset;

  end;

  { This function accepts two components:

      rc    Some Rave component that need be converted to an FR component.
      fc    Some FR component that's currently the parent. For example,
            fc can be a TfrxPage and then the function must create an FR component
            that'll be a child of fc and will be an FR analogue of rc.

    The function creates an FR analogue of rc Rave's component,
    adds this component to children of fc and returns the created component. }

  TRaveAddComponent = function(rc: TRaveComponent; fc: TfrxComponent):
    TfrxComponent of object;

  { This class contains Load function that loads a Rave report into FR.
    This Load function is needed for FR importing subsystem. }

  TRaveLoader = class(TObject)

    FOnUnknownComponent: TRaveAddComponent;
    FForm: TRaveLoaderForm;
    FLoadQueries: Boolean;

    { Converts the given Rave component to an FR component and
      does the same thing for all its children. }

    procedure EnumComponents(rc: TRaveComponent; fc: TfrxComponent);

    function HAlign(pj: TPrintJustify): TfrxHAlign;
    function FrameStyle(ps: TPenStyle): TfrxFrameStyle;

    { Rave designer allows to set the color of the brush fill pattern,
      but FR designer doesn't allow that. All brush fills will be black. }

    function BrushStyle(fs: TRaveFillStyle): TBrushStyle;

    procedure AssignBounds(rc: TRaveControl; fc: TfrxComponent);
    procedure AssignStyle(rc: TRaveSurface; fc: TfrxView);

    { Routine AddXXX(rc, fc) creates an FR equivalent of the given Rave rc
      component and makes the created component a child of the given FR fc
      component. If rc is a container, then the routine returns the newly
      created FR component. Otherwise, the routine returns fc. }

    function AddPage(rc: TRavePage; fc: TfrxComponent): TfrxComponent;
    function AddMemo(rc: TRaveMemo; fc: TfrxComponent): TfrxComponent;
    function AddText(rc: TRaveText; fc: TfrxComponent): TfrxComponent;
    function AddCustomText(rc: TRaveCustomText; fc: TfrxComponent): TfrxComponent;
    function AddRect(rc: TRaveRectangle; fc: TfrxComponent): TfrxComponent;
    function AddEllipse(rc: TRaveEllipse; fc: TfrxComponent): TfrxComponent;
    function AddBarCode(rc: TRaveBaseBarCode; fc: TfrxComponent): TfrxComponent;
    function AddImage(rc: TRaveGraphicImage; fc: TfrxComponent): TfrxComponent;
    function AddDataBand(rc: TRaveDataBand; fc: TfrxComponent): TfrxComponent;
    function AddBand(rc: TRaveBand; fc: TfrxComponent): TfrxComponent;
    function AddRegion(rc: TRaveRegion; fc: TfrxComponent): TfrxComponent;
    function AddDatabase(rc: TRaveDatabase; fc: TfrxComponent): TfrxComponent;
    function AddDataview(rc: TRaveDriverDataView; fc: TfrxComponent): TfrxComponent;
    function AddStub(rc: TRaveControl; fc: TfrxComponent): TfrxComponent;
    function AddUnknown(rc: TRaveComponent; fc: TfrxComponent): TfrxComponent;

    { There's a bug in Rave v5.0 - it doesn't save the style of lines to .rav files. }

    function AddLine(rc: TRaveLine; fc: TfrxComponent): TfrxComponent;

    { Returns the Tag of the component if it's not nil,
      or returns the default value if it's nil. }

    function GetTag(c: TComponent; default: Integer = 0): Integer;

    { Every string from a Rave report goes through this function (String Gate).
      This function simply returns its result. }

    function SG(const s: string): string; inline;

  public

    constructor Create;
    destructor Destroy; override;

    function Load(Report: TfrxReport; Stream: TStream): Boolean;

    { While analysing a Rave report, the import may find a component that
      is unknown to it. In this case it can call a handler for this situation. }

    property OnUnknownComponent: TRaveAddComponent
      read FOnUnknownComponent write FOnUnknownComponent;

    { Specifies whether to load SQL queries or not.
      By default this option is disabled, since some Rave builds corrupts
      memory while processing queries. }

    property LoadQueries: Boolean read FLoadQueries write FLoadQueries; {default False}

  end;

{ Rave classes hierarchy:

  TComponent - belongs to VCL
    TRaveComponent
      TRaveDataField
        TRaveStringField
        TRaveDateTimeField
        TRaveFloatField
        TRaveFormattedField
          TRaveIntegerField

      TRaveContainerComponent
        TRaveProjectItem
          TRavePage
          TRaveReport
          TRaveDataObject
            TRaveDatabase
            TRaveBaseDataView
              TRaveDriverDataView

          TRaveProjectManager

      TRaveControl
        TRaveBaseImage
          TRaveGraphicImage
            TRaveBitmap
            TRaveMetafile

        TRaveContainerControl
          TRaveBand
            TRaveIterateBand
              TRaveDataBand

          TRaveRegion
          TRaveSection
            TRaveDataMirrorSection

        TRaveBaseBarCode
        TRaveCustomText
          TRaveText
            TRaveDataText
            TRaveCalcText

          TRaveMemo
            TRaveDataMemo

        TRaveGraphicBase
          TRaveLine
            TRaveHLine
            TRaveVLine

          TRaveSurface
            TRaveRectangle
              TRaveSquare

            TRaveEllipse
              TRaveCircle
}

{ This routine adds to FR the ability to open rav files.
  After the returned TRaveLoader is no longer needed,
  it must be deleted by its Free method. }

function RegisterLoader: TRaveLoader;

implementation

{$R *.dfm}

uses
  frxRes;

var
  Loader: TRaveLoader;

function ResStr(const Index: string): string;
begin
  Result := frxResources.Get(Index);
end;

function RegisterLoader: TRaveLoader;
begin
  Result := TRaveLoader.Create;
  frxFR2Events.OnLoad := Result.Load;
  frxFR2Events.Filter := '*.rav';
end;

{ TRaveLoader }

constructor TRaveLoader.Create;
begin
  FForm := TRaveLoaderForm.Create(nil);
  FLoadQueries := False;
end;

destructor TRaveLoader.Destroy;
begin
  FForm.Free;
end;

function TRaveLoader.AddDataBand(rc: TRaveDataBand; fc: TfrxComponent): TfrxComponent;
var
  b: TfrxDataBand;
begin
  b := TfrxMasterData.Create(fc);
  AssignBounds(rc, b);
  b.CreateUniqueName;
  b.DataSet := TfrxDataSet(GetTag(rc.DataView));
  Result := b;
end;

function TRaveLoader.AddBand(rc: TRaveBand; fc: TfrxComponent): TfrxComponent;
var
  b: TfrxBand;
begin
  b := TfrxBand.Create(fc);
  AssignBounds(rc, b);
  b.CreateUniqueName;
  Result := b;
end;

function TRaveLoader.AddDatabase(rc: TRaveDatabase; fc: TfrxComponent): TfrxComponent;
var
  db: TfrxCustomDatabase;
  link: string;
begin
  link := SG(rc.LinkType);
  db := nil;

  { It's possible that one of these constructors will raise
    an exception. For instance, if ibnstall.dll doesn't exist
    and there's an attempt to create an IBX database. }

  try
    if link = 'ADO' then
      db := TfrxADODatabase.Create(fc)
    else if link = 'BDE' then
      db := TfrxBDEDatabase.Create(fc)
    else if link = 'DBX' then
      db := TfrxDBXDatabase.Create(fc)
    else if link = 'IBX' then
      db := TfrxIBXDatabase.Create(fc);
  except
  end;

  if db = nil then
  begin
    Result := AddUnknown(rc, fc);
    Exit;
  end;

  { Path to a database may be incorrect
    when loading the report on another system. }

  db.DatabaseName := SG(rc.AuthRun.DataSource);
  db.LoginPrompt := False;
  db.CreateUniqueName;
  rc.Tag := Integer(db);
  Result := fc;
end;

function TRaveLoader.AddStub(rc: TRaveControl; fc: TfrxComponent): TfrxComponent;
var
  m: TfrxMemoView;
begin
  m := TfrxMemoView.Create(fc);
  m.CreateUniqueName;
  m.Text := SG(rc.Name) + ':' + SG(rc.ClassName);
  m.Frame.Width := 1;
  m.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
  AssignBounds(rc, m);
  Result := m;
end;

function TRaveLoader.AddDataview(rc: TRaveDriverDataView; fc: TfrxComponent): TfrxComponent;

  function CreateADOQuery: TfrxADOQuery;
  var
    q: TfrxADOQuery;
  begin
    q := TfrxADOQuery.Create(fc);
    q.Database := TfrxADODatabase(GetTag(rc.Database));
    q.Query.SQL.Add(SG(rc.Query));
    Result := q;
  end;

  function CreateBDEQuery: TfrxBDEQuery;
  var
    q: TfrxBDEQuery;
  begin
    q := TfrxBDEQuery.Create(fc);
    q.Query.SQL.Add(SG(rc.Query));
    Result := q;

    if rc.Database = nil then
      q.DatabaseName := ''
    else
      q.DatabaseName := TfrxBDEDatabase(rc.Database.Tag).DatabaseName;
  end;

  function CreateDBXQuery: TfrxDBXQuery;
  var
    q: TfrxDBXQuery;
  begin
    q := TfrxDBXQuery.Create(fc);
    q.Database := TfrxDBXDatabase(GetTag(rc.Database));
    q.Query.SQL.Add(SG(rc.Query));
    Result := q;
  end;

  function CreateIBXQuery: TfrxIBXQuery;
  var
    q: TfrxIBXQuery;
  begin
    q := TfrxIBXQuery.Create(fc);
    q.Database := TfrxIBXDatabase(GetTag(rc.Database));
    q.Query.SQL.Add(SG(rc.Query));
    Result := q;
  end;

var
  s: TfrxDataSet;
  link: string;
begin
  if rc.Database = nil then
    link := ''
  else
    link := SG(rc.Database.LinkType);

  s := nil;

  if LoadQueries then
  try
    if link = 'ADO' then
      s := CreateADOQuery
    else if link = 'BDE' then
      s := CreateBDEQuery
    else if link = 'DBX' then
      s := CreateDBXQuery
    else if link = 'IBX' then
      s := CreateIBXQuery;
  except
  end;

  if s = nil then
  begin
    Result := AddUnknown(rc, fc);
    Exit;
  end;

  s.CreateUniqueName;
  fc.Report.DataSets.Add(s);
  rc.Tag := Integer(s);
  Result := fc;
end;

function TRaveLoader.AddBarCode(rc: TRaveBaseBarCode; fc: TfrxComponent): TfrxComponent;
var
  b: TfrxBarCodeView;
begin
  b := TfrxBarCodeView.Create(fc);
  b.CreateUniqueName;

  if rc is TRavePostNetBarCode then
    b.BarType := bcCodePostNet
  else if rc is TRaveI2of5BarCode then
    b.BarType := bcCode_2_5_interleaved
  else if rc is TRaveCode39BarCode then
    b.BarType := bcCode39
  else if rc is TRaveCode128BarCode then
    b.BarType := bcCode128A
  else if rc is TRaveUPCBarCode then
    b.BarType := bcCodeUPC_A
  else if rc is TRaveEANBarCode then
    b.BarType := bcCodeEAN13
  else
    b.BarType := bcCodeMSI;

  b.Text := SG(rc.Text);
  b.ShowText := False;
  AssignBounds(rc, b);
  Result := b;
end;

function TRaveLoader.AddPage(rc: TRavePage; fc: TfrxComponent): TfrxComponent;
var
  p: TfrxReportPage;
begin
  p := TfrxReportPage.Create(fc);
  p.CreateUniqueName;
  p.SetDefaults;

  p.LeftMargin := 0;
  p.RightMargin := 0;
  p.TopMargin := 0;
  p.BottomMargin := 0;

  if rc.Orientation = poLandScape then
    p.Orientation := TPrinterOrientation.poLandscape
  else
    p.Orientation := TPrinterOrientation.poPortrait;

  p.PaperWidth := rc.PageWidth * 25.4;
  p.PaperHeight := rc.PageHeight * 25.4;
  Result := p;
end;

function TRaveLoader.AddCustomText(rc: TRaveCustomText; fc: TfrxComponent): TfrxComponent;

  function IsWord(s: WideString): Boolean;
  var
    i: Integer;
  begin
    for i := 1 to Length(s) do
      if not CharInSet(s[i], ['a'..'z', 'A'..'Z']) then
      begin
        Result := False;
        Exit;
      end;

    Result := True;
  end;

var
  m: TfrxMemoView;
  ds: TfrxDataSet;
  s: WideString;
begin
  m := TfrxMemoView.Create(fc);
  m.CreateUniqueName;
  m.Text := SG(rc.Text);
  m.Font.Assign(rc.Font);
  m.HAlign := HAlign(rc.FontJustify);
  AssignBounds(rc, m);

  ds := nil;

  if rc is TRaveDataText then
    ds := TfrxDataSet(GetTag(TRaveDataText(rc).DataView))
  else if rc is TRaveDataMemo then
    ds := TfrxDataSet(GetTag(TRaveDataMemo(rc).DataView));

  if ds <> nil then
  begin
    m.DataSet := ds;

    { Convert Rave datatext into FR datatext. }

    { todo -oDraeden: Rave datatext can be a simple table field from a dataview
      or an expression involving several table fields. This code processes
      only simple fields. }

    s := Trim(m.Text);

    if (Length(s) > 2) and (s[1] = '[') and (s[Length(s)] = ']') then
    begin
      s := Copy(s, 2, Length(s) - 2);

      if IsWord(s) then
        m.Text := Format('[%s."%s"]', [ds.UserName, s]);
    end;
  end;

  Result := m;
end;

function TRaveLoader.AddMemo(rc: TRaveMemo; fc: TfrxComponent): TfrxComponent;
begin
  Result := AddCustomText(rc, fc);
end;

function TRaveLoader.AddText(rc: TRaveText; fc: TfrxComponent): TfrxComponent;
begin
  Result := AddCustomText(rc, fc);
end;

function TRaveLoader.AddUnknown(rc: TRaveComponent; fc: TfrxComponent): TfrxComponent;
begin
  if not Assigned(OnUnknownComponent) then
  begin
    Result := fc;
    Exit;
  end;

  Result := OnUnknownComponent(rc, fc);

  if (Result = nil) or not (Result is TfrxComponent) then
    raise Exception.CreateFmt('%s.OnUnknownComponent returned a non-FR component',
      [ClassName]);
end;

function TRaveLoader.AddLine(rc: TRaveLine; fc: TfrxComponent): TfrxComponent;
var
  c: TfrxLineView;
begin
  c := TfrxLineView.Create(fc);
  c.CreateUniqueName;
  AssignBounds(rc, c);
  c.Diagonal := (rc.Width <> 0) or (rc.Height <> 0);
  c.Frame.Width := rc.LineWidth;
  c.Frame.Style := FrameStyle(rc.LineStyle);
  c.Frame.Color := rc.Color;
  Result := c;
end;

function TRaveLoader.AddRect(rc: TRaveRectangle; fc: TfrxComponent): TfrxComponent;
var
  r: TfrxShapeView;
begin
  r := TfrxShapeView.Create(fc);
  r.CreateUniqueName;
  r.Shape := skRectangle;
  AssignBounds(rc, r);
  AssignStyle(rc, r);
  Result := r;
end;

function TRaveLoader.AddRegion(rc: TRaveRegion; fc: TfrxComponent): TfrxComponent;
var
  s: TfrxSubreport;
  p: TfrxReportPage;
begin
  p := TfrxReportPage.Create(fc.Parent);
  p.CreateUniqueName;
  s := TfrxSubreport.Create(fc);
  s.CreateUniqueName;
  AssignBounds(rc, s);
  s.Page := p;
  Result := p;
end;

function TRaveLoader.AddEllipse(rc: TRaveEllipse; fc: TfrxComponent): TfrxComponent;
var
  r: TfrxShapeView;
begin
  r := TfrxShapeView.Create(fc);
  r.CreateUniqueName;
  r.Shape := skEllipse;
  AssignBounds(rc, r);
  AssignStyle(rc, r);
  Result := r;
end;

function TRaveLoader.AddImage(rc: TRaveGraphicImage; fc: TfrxComponent): TfrxComponent;
var
  p: TfrxPictureView;
begin
  p := TfrxPictureView.Create(fc);
  p.CreateUniqueName;
  AssignBounds(rc, p);

  if rc.Image <> nil then
    p.Picture.Assign(rc.Image);

  Result := p;
end;

procedure TRaveLoader.AssignBounds(rc: TRaveControl; fc: TfrxComponent);

  { Returns True if the Rave component is represented in FR
    as a container. This function is needed because some Rave
    containers, like Section, don't have any representation in FR. }

  function IsFRContainer(rc: TRaveComponent): Boolean;
  begin
    Result := (rc is TRavePage) or (rc is TRaveBand);
  end;

  { Converts a point in Rave coordinate system
    into a point in FR coordinate system. }

  function Pos(p: TfrxPoint): TfrxPoint;
  var
    c: TRaveComponent;
  begin
    c := rc.Parent;

    while (c <> nil) and (c is TRaveControl) and not IsFRContainer(c) do
    begin
      p.Y := p.Y + TRaveControl(c).Top;
      p.X := p.X + TRaveControl(c).Left;
      c := c.Parent;
    end;

    Result.X := p.X * 2.54 * fr1cm;
    Result.Y := p.Y * 2.54 * fr1cm;
  end;

var
  lt, rb: TfrxPoint;
begin
  lt.X := rc.Left;
  lt.Y := rc.Top;
  rb.X := rc.Right;
  rb.Y := rc.Bottom;

  lt := Pos(lt);
  rb := Pos(rb);

  fc.Left   := lt.X;
  fc.Top    := lt.Y;
  fc.Width  := rb.X - lt.X;
  fc.Height := rb.Y - lt.Y;
end;

procedure TRaveLoader.AssignStyle(rc: TRaveSurface; fc: TfrxView);
begin
  fc.Frame.Style := FrameStyle(rc.BorderStyle);
  fc.Frame.Width := rc.BorderWidth;
  fc.Frame.Color := rc.BorderColor;

  case rc.FillStyle of
    fsClear:
    begin
      fc.BrushStyle := bsSolid;
      fc.Color := clNone;
    end;

    fsSolid:
    begin
      fc.BrushStyle := bsSolid;
      fc.Color := rc.FillColor;
    end

    else
    begin
      fc.BrushStyle := BrushStyle(rc.FillStyle);
      fc.Color := clNone;
    end;
  end;

  if rc.BorderStyle = psClear then
    fc.Frame.Color := clNone
  else
    fc.Frame.Typ := [ftLeft, ftTop, ftRight, ftBottom];
end;

function TRaveLoader.HAlign(pj: TPrintJustify): TfrxHAlign;
begin
  case pj of
    pjCenter: Result := haCenter;
    pjLeft:   Result := haLeft;
    pjRight:  Result := haRight;
    pjBlock:  Result := haBlock;
    else      Result := haLeft;
  end;
end;

function TRaveLoader.BrushStyle(fs: TRaveFillStyle): TBrushStyle;
begin
  case fs of
    fsSolid:      Result := bsSolid;
    fsClear:      Result := bsClear;
    fsHorizontal: Result := bsHorizontal;
    fsVertical:   Result := bsVertical;
    fsFDiagonal:  Result := bsFDiagonal;
    fsBDiagonal:  Result := bsBDiagonal;
    fsCross:      Result := bsCross;
    fsDiagCross:  Result := bsDiagCross;
    fsNone:       Result := bsClear;
    else          Result := bsClear;
  end;
end;

function TRaveLoader.FrameStyle(ps: TPenStyle): TfrxFrameStyle;
begin
  case ps of
    psDash:       Result := TfrxFrameStyle.fsDash;
    psDot:        Result := TfrxFrameStyle.fsDot;
    psDashDot:    Result := TfrxFrameStyle.fsDashDot;
    psDashDotDot: Result := TfrxFrameStyle.fsDashDotDot;
    else          Result := TfrxFrameStyle.fsSolid;
  end;
end;

function TRaveLoader.GetTag(c: TComponent; default: Integer): Integer;
begin
  if c = nil then
    Result := default
  else
    Result := c.Tag;
end;

procedure TRaveLoader.EnumComponents(rc: TRaveComponent; fc: TfrxComponent);

  procedure EnumChildren;
  var
    i: Integer;
    bands, items: TList;
  begin
    bands := TList.Create;
    items := TList.Create;

    { All bands in Rave have the top coordinate Y = 0.
      This means that Rave bands must be traversed in the
      backward order, from the bottom-most to the top-most,
      so as bands will appear in FR in the same order as in Rave. }

    try
      with rc do
        for i := 0 to ChildCount - 1 do
          if Child[i] is TRaveBand then
            bands.Add(Child[i])
          else
            items.Add(Child[i]);

      for i := bands.Count - 1 downto 0 do
        EnumComponents(bands[i], fc);

      for i := 0 to items.Count - 1 do
        EnumComponents(items[i], fc);
    finally
      items.Free;
      bands.Free;
    end;
  end;

begin
  if not FForm.Running then
    Exit;

  { Display on the form what component is being processed. }

  FForm.Component := SG(rc.Name) + ' : ' + SG(rc.ClassName);

  if rc is TRaveReport then
    FForm.Report := SG(rc.Name);

  if rc is TRavePage then
  begin
    FForm.Page := SG(rc.Name);
    FForm.NextItem;
  end;

  Application.ProcessMessages;

  { datamirror }

  if rc is TRaveDataMirrorSection then
    fc := AddStub(rc as TRaveControl, fc)

  { page }

  else if rc is TRavePage then
    fc := AddPage(rc as TRavePage, fc)

  { memo }

  else if rc is TRaveMemo then
    fc := AddMemo(rc as TRaveMemo, fc)

  { text }

  else if rc is TRaveText then
    fc := AddText(rc as TRaveText, fc)

  { rectangle }

  else if rc is TRaveRectangle then
    fc := AddRect(rc as TRaveRectangle, fc)

  { line }

  else if rc is TRaveLine then
    fc := AddLine(rc as TRaveLine, fc)

  { ellipse }

  else if rc is TRaveEllipse then
    fc := AddEllipse(rc as TRaveEllipse, fc)

  { barcode }

  else if rc is TRaveBaseBarCode then
    fc := AddBarCode(rc as TRaveBaseBarCode, fc)

  { picture }

  else if rc is TRaveGraphicImage then
    fc := AddImage(rc as TRaveGraphicImage, fc)

  { region }

  else if rc is TRaveRegion then
    fc := AddRegion(rc as TRaveRegion, fc)

  { databand }

  else if rc is TRaveDataBand then
    fc := AddDataBand(rc as TRaveDataBand, fc)

  { band }

  else if rc is TRaveBand then
    fc := AddBand(rc as TRaveBand, fc)

  { database }

  else if rc is TRaveDatabase then
    fc := AddDatabase(rc as TRaveDatabase, fc)

  { dataview }

  else if rc is TRaveDriverDataView then
    fc := AddDataview(rc as TRaveDriverDataView, fc)

  { unknown object }

  else
    fc := AddUnknown(rc, fc);

  { enumerate all children }

  EnumChildren;
end;

{ Rave file contains a tree of components arranged as following:

    TRaveProjectManager       The Rave file
      TRavePage               Global page #1 - it's shared between all other pages
      TRavePage               Global page #2 - it's shared between all other pages
      ...                     ...

      TRaveReport             Report #1 - it corresponds to one fr3 file
        TRavePage             Page #1
        TRavePage             Page #2
        ...                   ...

      TRaveReport             Report #2 - it corresponds to one fr3 file
        TRavePage             Page #1
        TRavePage             Page #2
        ...

      ... }

function TRaveLoader.Load(Report: TfrxReport; Stream: TStream): Boolean;

  function ReadStr(Stream: TStream; Count: Cardinal): AnsiString;
  var
    Pos: Int64;
  begin
    SetLength(Result, Count);
    Pos := Stream.Position;
    Stream.Read(Result[1], Count);
    Stream.Position := Pos;
  end;

  { Adds to FR a TRaveReport and a list of TRaveComponent items. }

  procedure EnumReport(rr: TRaveReport; gi: TList);
  var
    i: Integer;
  begin
    EnumComponents(rr, Report);

    i := 0;
    while (i < gi.Count) and FForm.Running do
    begin
      EnumComponents(TRaveComponent(gi[i]), Report);
      Inc(i);
    end;
  end;

var
  rvp: TRvProject;
  reports, items: TList;
  i, j, rn, pn: Integer;
  s: string;
begin
  if ReadStr(Stream, 3) <> 'RAV' then
  begin
    Result := False;
    Exit;
  end;

  rvp := TRvProject.Create(nil);
  reports := TList.Create;
  items := TList.Create;

  try
    FForm.Reset;

    { Some versions of Rave has TRvProject.LoadFromStream method
      that has a bug and corrupts memory. This may lead to TfrxReport's
      destruction. }

    rvp.LoadFromStream(Stream);

    { Separate TRaveReport objects from other stuff, like global pages.
      The stuff must be added to FR after reports. }

    with rvp.ProjMan do
      for i := 0 to ChildCount - 1 do
        if Child[i] is TRaveReport then
          reports.Add(Child[i])
        else
          items.Add(Child[i]);

    { Count the number of pages that will be added to FR.
      Some pages, like global pages, will be added several times. }

    pn := 0;

    for i := 0 to reports.Count - 1 do
    begin
      with TRaveReport(reports[i]) do
        for j := 0 to ChildCount - 1 do
          if Child[j] is TRavePage then
            Inc(pn);

      for j := 0 to items.Count - 1 do
        if TRaveComponent(items[j]) is TRavePage then
          Inc(pn);
    end;

    FForm.ItemsCount := pn;
    rn := reports.Count;

    if rn = 0 then
      raise Exception.Create(ResStr('rave1'));

    if rn = 1 then
    begin
      FForm.Show;
      Report.Clear;
      EnumReport(TRaveReport(reports[0]), items);
      Result := True;
    end
    else if not SelectDirectory(Format(ResStr('rave2'), [rn]), '', s, [sdNewUI, sdShowEdit]) then
      Result := True // the user cancels loading - return 'success' and exit
    else
    begin
      if not SysUtils.DirectoryExists(s) and not CreateDir(s) then
        raise EInOutError.Create(Format(ResStr('rave3'), [s]));

      FForm.Show;

      i := 0;
      while (i < rn) and FForm.Running do
      begin
        Report.Clear;
        EnumReport(TRaveReport(reports[i]), items);
        Report.SaveToFile(Format('%s\%d.fr3', [s, i + 1]));
        Inc(i);
      end;

      Result := True;
    end;
  except
    Result := False;
  end;

  FForm.Hide;
  items.Free;
  reports.Free;
  rvp.Free;
end;

function TRaveLoader.SG(const s: string): string;
begin
  Result := s;
end;

{ TRaveLoaderForm }

procedure TRaveLoaderForm.btClick(Sender: TObject);
begin
  Stop;
end;

procedure TRaveLoaderForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Stop;
end;

procedure TRaveLoaderForm.FormCreate(Sender: TObject);
begin
  Caption := ResStr('rave0');
  bt.Caption := 'Stop';
end;

function TRaveLoaderForm.IsRunning: Boolean;
begin
  Result := FRunning;
end;

procedure TRaveLoaderForm.NextItem;
begin
  pb.Position := pb.Position + 1;
end;

procedure TRaveLoaderForm.Reset;
begin
  FRunning := True;
  FPage := '';
  FReport := '';
  pb.Position := 0;
  lbTop.Caption := '';
  lbBottom.Caption := '';
  bt.Enabled := True;
end;

procedure TRaveLoaderForm.SetComponentName(const s: string);
begin
  lbBottom.Caption := s;
end;

procedure TRaveLoaderForm.SetItemsCount(n: Integer);
begin
  pb.Max := n;
end;

procedure TRaveLoaderForm.SetPage(const s: string);
begin
  FPage := s;
  UpdateLabel;
end;

procedure TRaveLoaderForm.SetReport(const s: string);
begin
  FReport := s;
  UpdateLabel;
end;

procedure TRaveLoaderForm.Stop;
begin
  FRunning := False;
  bt.Enabled := False;
end;

procedure TRaveLoaderForm.UpdateLabel;
begin
  lbTop.Caption := Format(ResStr('rave4'), [FReport, FPage]);
end;

initialization

Loader := RegisterLoader;

finalization

Loader.Free;

end.



