Delphi控制元件的拿來主義(二) (轉)

worldblog發表於2007-12-11
Delphi控制元件的拿來主義(二) (轉)[@more@]

 

  這個例子是關於如何將一個文字資料匯入匯出作一些簡單的修改之後拿來使用的。:namespace prefix = o ns = "urn:schemas--com::office" />

◆功能

  將圖書館的ISO中的部分資料轉到8中。

◆設計要求

  1.顯示匯入進度條。

  2.在匯入過程中,如果某條紀錄匯入失敗,不顯示異常,而將匯入失敗的紀錄記入日誌。

◆設計思路

程式的關鍵在文字資料欄位的分離。通常的做法,都是先將字串進行處理(RegulateString),然後把串中每個字元同分割符(可以是空格,逗號等)比較,將不是分割符的字元追加到一個串中(GetRecordItem),得到一個欄位的內容。透過一個迴圈(迴圈次數由GetItemNum來定),就可以將一個字串分成幾個欄位。最後的工作就是將分離出來的資料對號入座加入資料庫.

按照上面的思路,利用提供的已有和過程,實現起來應該不難,但問題是,我可不想每次編文字匯入程式的時候,都把什麼這啊那的函式過程重新定義一遍,哎,最煩的就是重複性的工作了.那麼有沒有現成的控制元件將上述過程都封裝起來呢?PS:又不用我編呢?

  答案是肯定的!前幾天剛剛下了一個免費控制元件TPgCSV,據說可以實現文字的匯入和匯出.翻出來一看,正是我想要的.

   在深入到下面的內容之前,有必要對該控制元件的類宣告部分作一定了解

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

//中文部分為筆者所作的註釋

//注意:

//在該控制元件中,Export代表將文字資料匯入到資料庫,Import代表從資料庫匯出到文字。??? 怎麼和我理解的

//匯入匯出概念剛好是反的 :)

type

  //在處理資料產生異常時,可選擇繼續還是中止

  TPgCSVErrorResponse  = (pgcsvAbort, pgcsvIgnore);

  //程式事件宣告,可以將匯入/匯出的進度作為引數傳出

  TPgCSVProgressEvent  = procedure (Sender : T; 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;

  FAuten,

  FUseDelimiter,

  FSilentExport,

  FTrimData,

  FStop,

  FEmptyTable  : Boolean;

  FBeforeOpenTable,

  FAfterOpenTable,

  FBeforeCloseTable,

  FAfterCloseTable,

  FBeforeEmptyTable,

  FAfterEmptyTable,

  FBeforeExport,

  FAfterExport,

  FBeforeImport,

  FAfterImport,

  FOnAddRecord  : TNotifyEvent;

  FExportProgress,

  FImportProgress  : TPgCSVProgressEvent;

  FExportError  : TPgCSVExportErrorEvent;

  FMtems, 

  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 Extract(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); overr;

  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.FielyName(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)).Aloat:=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這個事件,雙擊它就可以新增你的處理程式碼了。這樣一來,無論文字資料中有什麼樣怪異的字元或格式,我們都透過這個事件處理預先過濾一編,讓控制元件放心的處理匯入的操作。簡單吧(也太簡單了點,居然還寫了這麼長!汗)

但是,這樣就行了嗎?

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991437/,如需轉載,請註明出處,否則將追究法律責任。

相關文章