Delphi控件的拿来主义(二)

来源:互联网 发布:北京西服定制店 知乎 编辑:程序博客网 时间:2024/04/27 16:23

  

   这个例子是关于如何将一个文本数据导入导出控件作一些简单的修改之后拿来使用的。

◆程序功能

    将图书馆的ISO文件中的部分数据转到Oracle8数据库中。

◆设计要求

    1.显示导入进度条。

    2.在导入过程中,如果某条纪录导入失败,不显示异常,而将导入失败的纪录记入日志。

◆设计思路

程序的关键在文本数据字段的分离。通常的做法,都是先将字符串进行处理(RegulateString),然后把串中每个字符同分割符(可以是空格,逗号等)比较,将不是分割符的字符追加到一个串中(GetRecordItem),得到一个字段的内容。通过一个循环(循环次数由GetItemNum来定),就可以将一个字符串分成几个字段。最后的工作就是将分离出来的数据对号入座加入数据库.

按照上面的思路,利用Delphi提供的已有函数和过程,实现起来应该不难,但问题是,我可不想每次编文本导入程序的时候,都把什么这啊那的函数过程重新定义一遍,哎,最烦的就是重复性的工作了.那么有没有现成的控件将上述过程都封装起来呢?PS:又不用我编呢?

    答案是肯定的!前几天刚刚下了一个免费控件TPgCSV,据说可以实现文本的导入和导出.翻出来一看,正是我想要的.

   在深入到下面的内容之前,有必要对该控件的类声明部分作一定了解

  (经作者Khashayar Sadjadi(khashi@pragena.8m.com)同意发表):

//中文部分为笔者所作的注释

//注意:

//在该控件中,Export代表将文本数据导入到数据库,Import代表从数据库导出到文本。??? 怎么和我理解的

//导入导出概念刚好是反的 :)

type

  //在处理数据产生异常时,可选择继续还是中止

  TPgCSVErrorResponse     = (pgcsvAbort, pgcsvIgnore);

  //进程监控事件声明,可以将导入/导出的进度作为参数传出

  TPgCSVProgressEvent     = procedure (Sender : TObject; AProgress: LongInt; var StopIt: Boolean) of object;

  //发生异常时的事件处理声明,异常信息通过该接口传给程序员。

  TPgCSVExportErrorEvent  = procedure (Sender : TObject; Mess: string; RecNo: LongInt; var Response:TPgCSVErrorResponse) of object;

  TPgCSV = class(TComponent)

  private

   FDataset         : TDataset;

   FCSVMap,

   FCSVFile,

   FDateFormat,

   FIgnoreStr       : string;

   FSeprator,

   FDelimiter,

   FFieldIndicator  : Char;

   FAutoOpen,

   FUseDelimiter,

   FSilentExport,

   FTrimData,

   FStop,

   FEmptyTable      : Boolean;

   FBeforeOpenTable,

   FAfterOpenTable,

   FBeforeCloseTable,

   FAfterCloseTable,

   FBeforeEmptyTable,

   FAfterEmptyTable,

   FBeforeExport,

   FAfterExport,

   FBeforeImport,

   FAfterImport,

   FOnAddRecord     : TNotifyEvent;

   FExportProgress,

   FImportProgress  : TPgCSVProgressEvent;

   FExportError     : TPgCSVExportErrorEvent;

   FMapItems,       

   FDefaultInt      : Integer;

   FBufferSize      : LongInt;

   FFieldCache      : TList;

  protected

   FFile            : TextFile;

   //以下就是我所说的希望封装的部分

   function CountMapItems:Integer;//计算映射字符串的字段个数

   function GetMapItem(ItemIndex:Integer;var AField:Boolean):string;//提取映射字符串的字段

   function GetCSVRecordItem(ItemIndex:Integer;CSVRecord:string):string;//提取CSV文件字符串中的某一字段

   function BuildMap:string;//自动创建映射,如果CSVMap一栏为空的话,会由它来产生映射字符串

   function ExtractWord(Item: Integer;S, WordDelim: string): string;//提取文本数据字符串/映射字符串中的某一字段

   function WordCount(const S ,WordDelim: string): Integer;//计算文本数据字符串/映射字符串中的字段数目

   function WordPosition(Item: Integer; const S, SubStr: string): Integer;//计算子字符串在字符串中的位置

  public

   constructor Create(AOwner: TComponent); override;

  published

   //properties

   property Dataset          : TDataset               read FDataset          write FDataset;

   //设置要导入或导出的目标数据集.

   property CSVMap           : string                 read FCSVMap           write FCSVMap;

  //CSV 文本数据文件到数据库字段值的映射字符串.控件通过该映射决定文本中的哪些数据要导入及要导入哪个字段.

   property CSVFile          : string                 read FCSVFile          write FCSVFile;

   //CSV 文件格式,其实就是文本数据文件。CSV代表什么意思?呵呵,我也不知道

   property Seprator         : Char                   read FSeprator         write FSeprator;

   //分隔符,可以是空格,也可以是,、;、#等符号

   property FieldIndicator   : Char                   read FFieldIndicator   write FFieldIndicator;

  //字段标识符.

   property AutoOpen         : Boolean                read FAutoOpen         write FAutoOpen;

   //将AutoOpen设为True可以在处理数据前自动打开要导入的数据表并在操作完毕后自动关掉它。

   property IgnoreString     : string                 read FIgnoreStr        write FIgnoreStr;

   //忽略纪录的标识串.

   //举例来说

   //IgnoreString:='(ignore)';

   //CSVMap:='$Name,(ignore),$Age';

   //在这种情况下,CSVToDataSet方法,即导入数据方法将忽略文本文件中的第二列的字段。

   property Delimiter        : Char                   read FDelimiter        write FDelimiter;

   //在某些CSV文档中标识字符串纪录的标识符,比如,"john","boy",12中的",在这种情况下,TPgCSV

//会忽略这些标识符。

   property EmptyTable       : Boolean                read FEmptyTable       write FEmptyTable;

   //只在从数据库导出(DataSetToCSV)方法中有效,作用是创建一个新的CSV文件。

   property UseDelimiter     : Boolean                read FUseDelimiter     write FUseDelimiter;

   //是否有Delimiter。

   property SilentExport     : Boolean                read FSilentExport     write FSilentExport;

   //若该属性为True,应用程序将不显示数据操作时的异常,而将异常信息通过一个接口传给程序员处理.

   property DateFormat       : string                 read FDateFormat       write FDateFormat;

   //设定CSV文件中日期数据的格式。

   property TrimData         : Boolean                read FTrimData         write FTrimData;

   //是否去掉数据头尾的空格.

   property DefaultInt       : Integer                read FDefaultInt       write FDefaultInt;

   //整/实形数据转换出错后的默认值

   property BufferSize       : LongInt                read FBufferSize       write FBufferSize;

   //CSV 文件的缓冲值,以字节为单位,可以加快导入和导出数据的速度。

   //events

   property BeforeOpenTable  : TNotifyEvent           read FBeforeOpenTable  write FBeforeOpenTable;

   property AfterOpenTable   : TNotifyEvent           read FAfterOpenTable   write FAfterOpenTable;

   property BeforeCloseTable : TNotifyEvent           read FBeforeCloseTable write FBeforeCloseTable;

   property AfterCloseTable  : TNotifyEvent           read FAfterCloseTable  write FAfterCloseTable;

   property BeforeEmptyTable : TNotifyEvent           read FBeforeEmptyTable write FBeforeEmptyTable;

   property AfterEmptyTable  : TNotifyEvent           read FAfterEmptyTable  write FAfterEmptyTable;

   property BeforeImport     : TNotifyEvent           read FBeforeImport     write FBeforeImport;

   property AfterImport      : TNotifyEvent           read FAfterImport      write FAfterImport;

   property BeforeExport     : TNotifyEvent           read FBeforeExport     write FBeforeExport;

   property AfterExport      : TNotifyEvent           read FAfterExport      write FAfterExport;

   property ExportProgress   : TPgCSVProgressEvent    read FExportProgress   write FExportProgress;

   //进程监控事件。每完成一条文本数据的导入就触发该事件。

   property ImportProgress   : TPgCSVProgressEvent    read FImportProgress   write FImportProgress;

   property OnAddRecord      : TNotifyEvent           read FOnAddRecord      write FOnAddRecord;

   property ExportError      : TPgCSVExportErrorEvent read FExportError      write FExportError;

   //发生异常时交由该事件处理,异常信息通过该接口传给程序员。

   //methodes

    //整个控件的核心内容

   procedure CSVToDataset;//将文本导入到数据集的方法

   procedure DatasetToCSV;//将数据集的数据导入到文本的方法

  end;

 

procedure Register;

{略}

implementation

{略}

end.

   从声明部分中我们可以看到,TPgCSV将文本数据的导入导出全部封装到了CSVToDataSet(文本数据导入)DataSetToCSV(文本数据导出)两个方法中.开发者可以在设计阶段直接将文本文件同要导入/出的数据库相连,然后在程序运行当中调用这两个方法就可以了,根本不用理会那些函数什么的,相当的方便.

   那我是不是可以直接拿来使用呢?这里就出现问题了.

----问题一及解决方案

在该控件自带的Demo中,所使用的文本数据文件,格式如下


test.csv

"11","12","13","14"

"21","22","23","24"

"31","32","33","34"

...


 

ISO文件中的数据格式(部分)为


sm01632.ISO

... a7507310175­b特精装­d¥1893"‑1 ­a毛泽东珍品典藏(上、下册)­f中共中央文献研究室编著‑c中献­d011200"...

...­a7119029193­b平装­ d¥20"‑1 ­a中国:加入WTO与经济改革­f王 梦 奎编著‑  ­c外文­d011200"‑ ... ­


 

我们看到,同test.csv相比,sm01632.ISO文件中的纪录很不规则。

我们所需要的是这样的纪录


sm01632.ISO

... 7507310175,特精装,1893,1,毛泽东珍品典藏(上、下册),中共中央文献研究室编著,中献,011200...

...­7119029193,平装,20,1,中国:加入WTO与经济改革,王 梦 奎编著,外文,011200‑ ... ­


 

   TPgCSV能否对这种情况进行自动的处理呢?哇噻,这种万能的控件好像不大可能有吧!(事实上也不需要)既然直接用TPgCSV处理无法实现正确导入的,我们就需要在每一条从文件中读取出来的纪录导入数据库之前,用程序对它们进行处理,转换之后再交由TPgCSV进行操作。怎么样处理这里就不赘述了。我们所关心的是TPgCSV有没有开放出这样一个处理的接口呢?

   我们来看DataSetToCSV方法的实现代码(主要是注释部分):

procedure TPgCSV.CSVToDataSet;

var

 RecordString,

 Temp          : string;

 i             : Integer;

 C             : LongInt;

 D             : Boolean;

 F             : Real;

 ErrorResponse : TPgCSVErrorResponse;

 Buffer        : Pointer;

begin

 //create field cache

 FFieldCache:=TList.Create;

 //initiate map items

 FMapItems:=0;

 //allocate buffer size

 GetMem(Buffer,FBufferSize);

 //assign and open CSV file

 AssignFile(FFile,FCSVFile);

 SetTextBuf(FFile,Buffer^,FBufferSize);

 Reset(FFile);

 //open table if nessecary

 if FAutoOpen then

  begin

   if Assigned(FBeforeOpenTable) then

    FBeforeOpenTable(Self);

   FDataset.Open;

   if Assigned(FAfterOpenTable) then

    FAfterOpenTable(Self);

  end;

 //export to table from CSV file

 if Assigned(FBeforeExport) then

  FBeforeExport(Self);

 //set the counter to zero

 C:=0;

 Temp:=ShortDateFormat;

 ShortDateFormat:=FDateFormat;

{*************************以下是文本数据导入的核心代码部分,也是我要关心的部分**************************}

 FDataset.DisableControls;

 while (not Eof(FFile)) and (not FStop) do

  begin

   //read from CSV

   Readln(FFile,RecordString);

   //注意,这里好像差了一点什么东西

   //add new record

   try

    FDataset.Append;

    for i:=1 to CountMapItems do

     if Uppercase(GetMapItem(i,D)) <> Uppercase(FIgnoreStr) then

     case FDataset.FieldByName(GetMapItem(i,D)).DataType of

      ftInteger:

       FDataset.FieldByName(GetMapItem(i,D)).AsInteger:=StrToIntDef(Trim(GetCSVRecordItem(i,RecordString)),FDefaultInt);

      ftFloat:

       begin

        try

         F:=StrToFloat(Trim(GetCSVRecordItem(i,RecordString)));

        except

         F:=FDefaultInt;

        end;

        FDataset.FieldByName(GetMapItem(i,D)).AsFloat:=F;

       end;

      else

       if FTrimData then

        FDataset.FieldByName(GetMapItem(i,D)).AsString:=Trim(GetCSVRecordItem(i,RecordString))

       else

        FDataset.FieldByName(GetMapItem(i,D)).AsString:=GetCSVRecordItem(i,RecordString);

     end;

    //post record

    FDataset.Post;

   except

    on E:Exception do

     if not FSilentExport then

      raise

     else

      if Assigned(FExportError) then

       begin

        FExportError(Self,E.Message,C,ErrorResponse);

        if ErrorResponse = pgcsvAbort then

         Break;

       end;

   end;

   if Assigned(FOnAddRecord) then

    FOnAddRecord(Self);

   if Assigned(FExportProgress) then

    FExportProgress(Self, C, FStop);

   Inc(C);

  end;

 FDataset.EnableControls;

{*************************以上是文本数据导入的核心代码部分**************************}

 if Assigned(FAfterExport) then

  FAfterExport(Self);

 //close table if nessecary

 if FAutoOpen then

  begin

   if Assigned(FBeforeCloseTable) then

    FBeforeCloseTable(Self);

   FDataset.Close;

   if Assigned(FAfterCloseTable) then

    FAfterCloseTable(Self);

  end;

 //close CSV file

 CloseFile(FFile);

 //disallocate buffer

 FreeMem(Buffer);

 ShortDateFormat:=Temp;

 //free cache

 for i:=FFieldCache.Count - 1 downto 0 do

  Dispose(FFieldCache.Items[i]);

 FFieldCache.Free;

end;

   (这么长!看得我眼都花了.好在找到了我所关心的核心代码,其他的?别管了吧,我这个懒惰的人.)差的是什么呢?很明显,我们希望把这个时候的RecordString开放出去,经过处理之后再回来进行导入的操作.所以,这里差的只是一个事件处理过程,在这个过程中,我们需要通过Delphi把RecordString的值传出去让用户处理.

    既然没有提供这个接口,那就自己动手添加了。由于需要传参数,这个事件不能用标准的TNotifyEvent来定义,而需要重新声明。

 新的事件声明和事件属性如下:

type

 ...

  TPgCSVRegulateStrEvent  = procedure (Sender : TObject; var ARecordString: string) of object;

 ...

TPgCSV = class(TComponent)

Published

   property RegulateString   : TPgCSVRegulateStrEvent read FRegulateString write FRegulateString;

  //写完后别忘了按一下ctrl+shift+c

 ...

End;

 

  好了,现在可以在我刚才注释的地方写事件调用方法的程序了.

...

 while (not Eof(FFile)) and (not FStop) do

  begin

   //read from CSV

   Readln(FFile,RecordString);

  //xm4014's modification

   if Assigned(FRegulateString) then

    FRegulateString(self,RecordString);

  //add new record

   try

    FDataset.Append;  

 ...

重新编译包文件通过后,你就会在TPgCSV控件的事件页面中发现RegulateString这个事件,双击它就可以添加你的处理代码了。这样一来,无论文本数据中有什么样怪异的字符或格式,我们都通过这个事件处理预先过滤一编,让控件放心的处理导入的操作。简单吧(也太简单了点,居然还写了这么长!汗)

但是,这样就行了吗?

 

原创粉丝点击