Delphi中对Oracle存取RTF文档(作者:苏涌)

关系数据库都提供大文档的存储和提取。对于视频资料、音频资料、图象资料等大文档,一般需要 另外开辟字段用于存储摘要信息,因此在查询和检索时并不访问大字段,而只是在存储和提取时才操作 大字段。例如,你不能对Oracle中的LONG RAW类型进行LIKE介词的查询,更不能使用等号“ =”查询。 这对于存储大段文本(容量超过2K)同时又需要全文检索是相当不便的。本文将介绍如何利用数据库的 字符串数据类型存取和查询大段文本。这里以Oracle数据库和Delphi应用程序为例,重点介绍如何在数据库中存取 RTF文档。

对于纯文本,可以简单地将其分割成若干个串,分别存储到VARCHAR(2000)字段中即可。在查询时 可以使用LIKE比较,从而达到全文检索的目的。为了保留换行等段落信息,应当将回车换行(#13#10) 也作为串的一部分进行保存。数据录入时可以提供Memo控件(不是DBMemo)进行录入,然后顺序连接各行,当连接成的串临近2000个字符(单字节字符)长度时,就存入一条记录,然后对剩余的行重复上述操作。这样,最终将纯文本存成若干长度不超过2000的VARCHAR(2000) 字段中。这里需要另外开辟字段 用于存储文本编号和子序号,以便区分不同的文本和读取文本时顺序连接所有的子串。查询纯文本时, 就可以象查询普通的VARCHAR 字段一样,可以使用LIKE,也可以使用等号“ =”(几乎不需要使用)。 需要注意的是,可能用户提供的关键字正好被存储在不同的子串中,这时是查询不到的。因此,在设计时应当考虑存储重复的串。例如,每个子串中仅有前1900个字符是有效字符,最后100 个字符用于存储 下一个子串的前100 个字符。这样就避免了关键字被分开的情况。唯一的不足是,必须限制用户输入的 查询关键字长度不得超过100 个字符(50个汉字),但这很正常,算不上不足。

事实上,同样可以利用这一技巧对 RTF文档进行存取和查询。这时,用于录入和显示 RTF文档的是 RichEdit控件(不是DBRichEdit),而不再是Memo控件。对于 RTF文档的存取,不能象存取纯文本那样 通过Memo的属性Lines.Strings[Index]进行操作(尽管RichEdit控件具有相同的属性),因为这样做就 无法保存文档的格式了。需要利用的是RichEdit的两个方法:SaveToFile和LoadFromFile。需要了解的 是, RTF文件中用纯字符描述字体、字号、文本等各种格式信息和内容信息。因此,存储和提取时可以 视为纯文本进行操作。但对于查询,就不能直接用LIKE加关键字的方式进行。因为 RTF文档中的每一个 汉字都是用特殊的表示方法存储,只有单字节字符是原样存储。所以在查询时要对关键字进行处理才能 用在查询语句中。

在测试这个例子之前,必须有如下的数据结构,这里以Oracle创建表的 SQL语句形式给出:

(*
CREATE TABLE TEST(                { 表名为 TEST }
  DOCID   NUMBER NOT NULL,        { 文档编号    }
  DOCNAME VARCHAR(40) NOT NULL,   { 文档标题    }
  SUBID   NUMBER NOT NULL,        { 文档子编号  }
  TEXT    VARCHAR(2000) NOT NULL, { 子文档内容  }
  PRIMARY KEY(DOCID, SUBID));     { 联合主键    }
*)

    下面是程序实例中的主要部分:

{ ... ... }

const
  BufSize = 2000;                               { 串的最大容量 }

type
  TBuffer     = array [1..BufSize] of Char;     { 串缓存       }
  TFileOfChar = file of Char;                   { 字符类型文件 }

  TChnChar = string[2];                         { 汉字字符类型 }

{ SQL查询,返回首记录首字段的值  }
function  SelectSQL(S: string): Variant;
begin
  Result := NULL;
  with TADOQuery.Create(Application) do try
    Connection := FMain.ADOConnection1;
    SQL.Append(S);
    SQL.SaveToFile('c:/a.txt');
    Open;
    Result := Fields[0].AsVariant;
  finally
    Free;
  end;
end;

{ 下面的函数将RTF文档存入数据库 }
function  RTFToDB(ARichEdit: TRichEdit; { 文档容器 }
                  DocName: string;      { 文档标题 }
                  ATable: TADOTable     { 操作的表 }
                  ): Boolean;           { 返回类型 }
const
  TmpFileName = 'c:/x.rtf';        { 临时文档 }
var
  DocID, SubID, L: Integer;        { 局部变量 }
  S: string;                       { 串       }
  F: TFileOfChar;                  { 字符文件 }
  Buf: TBuffer;                    { 文本缓存 }
begin
  ARichEdit.Lines.SaveToFile(TmpFileName);{ 先存入文件 }
  AssignFile(F, TmpFileName);             { 打开文件   }
  Reset(F);
  try
    DocID :=                              { 产生新的文档编号      }
      SelectSQL('SELECT NVL(MAX(DOCID) + 1, 101) FROM TEST');
    with ATable do if not Active then Active := True;{ 确认表打开 }
    SubID := 0;                           { 初始化子编号          }
    while not EOF(F) do begin
      Inc(SubID);
      BlockRead(F, Buf, BufSize, L);      { 读取两千个字符        }
      S := Buf;
      SetLength(S, L);                    { 取实际读取到的字节数  }
      with ATable do begin                { 增加一条子文档        }
        Append;
        FieldByName('DOCID').AsInteger := DocID;
        FieldByName('DOCNAME').AsString := DocName;
        FieldByName('SubID').AsInteger := SubID;
        FieldByName('TEXT').AsString := S;
        Post;
      end;
    end;
    Result := True; { 存储成功 }
  except
    Result := False;{ 存储失败 }
  end;
  CloseFile(F);           { 关闭文件 }
  DeleteFile(TmpFileName);{ 删除文件 }
end;


{ 下面的函数从数据库中读取RTF文档,并在指定的容器中显示 }
function  RTFFromDB(ARichEdit: TRichEdit;{ RTF文档容器  }
                    DocName: string;     { 文档标题     }
                    AQuery: TADOQuery    { 操作的数据集 }
                    ): Boolean;          { 返回类型     }
const
  TmpFileName = 'c:/temp/x.rtf';         { 临时文件     }
var
  S: string;                             { 局部串变量   }
  F: TFileOfChar;                        { 字符文件     }
  Buf: TBuffer;                          { 串缓存       }
  I, L: Integer;                         { 局部变量     }
begin
  ARichEdit.Clear;              { 清除当前显示的内容    }
  AssignFile(F, TmpFileName);   { 关联文件              }
  try
    Rewrite(F);         { 打开文件,准备写入从数据库读出的数据 }
    with AQuery do begin
      Active := False;  { 关闭数据集  }
      SQL.Clear;        { 重建SQL语句 }
      SQL.Append('SELECT SUBID, TEXT FROM TEST WHERE DOCNAME = ''' +
                 DocName + ''' ORDER BY SUBID');
      Open;             { 打开数据集  }
      if RecordCount <> 0 then begin  { 确认数据集非空           }
        First;                        { 移到首记录-子文档        }
        repeat                        { 读出一条子文档并写入文件 }
          S := FieldByName('TEXT').AsString;
          L := Length(S);
          for I := 1 to L do Buf[I] := S[I];
          BlockWrite(F, Buf, L);
          Next;
        until EOF;
      end;
    end;
    CloseFile(F);{ 关闭文件 }   
    ARichEdit.Lines.LoadFromFile(TmpFileName);{ 从文件中装入RTF文档 }
    Result := True; { 读取成功 }
  except            { 读取失败 }
    try CloseFile(F); except end;
    Result := False;
  end;
  DeleteFile(TmpFileName); { 删除临时文件 }
end;

{ 下面的函数将汉字单字转换成RTF中表示的形式。                   }
{ 如表示汉字“国”的是ASCII(b9)和ASCII(fa),这里是十六进制;    }
{ 那么在 RTF文件中对“国”字的表示占用了 8个字节:              }
{               /'b9/'fa                                        }
{ 因此,需要在查询之前进行转换。由于表示方法中含有Delphi用于    }
{ 字符串的分解符:单撇号“'”,因此在转换时需要考虑这一点,     }
{ 否则就不能构造出正确的 SQL查询语句                            }
function ChnCharToRTFCode(Ch: TChnChar): string;
var
  C1, C2: Char;
  O1, O2: Byte;
  S: string;
begin
  C1 := Ch[1];
  C2 := Ch[2];
  O1 := Ord(C1);
  O2 := Ord(C2);
  S := Format('/''''%2X', [O1]) + Format('/''''%2X', [O2]);
  Result := Lowercase(S);{ 转换为小写 }
end;

{ 根据需要检索的关键字转换成LIKE中使用的串。     }
{ 这里用于区别汉字的方法是根据编码。             }
{ 按照Windows 中的双字节编码规则,对于双字节字符 }
{ 如汉字字符,是由两个字节构成,其中第一个字节是 }
{ 引导字符。汉字引导字符的ASCII 码大于 127,因此 }
{ 可以根据此特点来区分汉字和单字节字符。         }
function  MakeLikeRTFString(StrToFind: string): string;
var
  I: Integer;
  ChnChar: TChnChar;
  S: string;
begin
  S := '';
  I := 0;
  while I < Length(StrToFind) do begin
    Inc(I);
    if Integer(StrToFind[I]) >= $80 then begin{ 汉字的首字节一定不小于128 }
      ChnChar := StrToFind[I] + StrToFind[I + 1];
      Inc(I);
      S := S + ChnCharToRTFCode(ChnChar);
    end else begin{ 单字节字符 }
      S := S + StrToFind[I];
      if StrToFind[I] = '''' then S := S + StrToFind[I];{ 单撇号的特殊处理 }
    end;
  end;
  Result := S;
end;

{ 构造对关键字进行全文检索的查询语句 }
function  MakeLikeString(StrToFind: string): string;
var
  S: string;
begin
  S := MakeLikeRTFString(StrToFind);
  S := 'SELECT DISTINCT DOCNAME FROM TEST WHERE TEXT LIKE ''%' + S + '%''';
  Result := S;
end;

{ ... ... }
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值