unit UDictionary;
{ ********************************************************************************* }
{ (c) GNU                                                                           }
{ author: Heiko Tietze                                                              }
{ version: 1.0                                                                      }
{ date: 11.12.2004                                                                  }
{ description: Routines to load and save a textfile with additional word meaning    }
{              and category informations; dictionay stores all words in a           }
{              stringlist; to access a word use CheckWord instead of IndexOf or     }
{              Find to enable the possibility to ask for including in the list      }
{              or ignoring                                                          }
{ ********************************************************************************* }

{$I Def.inc}

interface

uses Classes, Sysutils, StrUtils,
     {$IFDEF VCL}
       Dialogs, Controls,
     {$ELSE}
       QDialogs,QControls,
     {$ENDIF}
     UTools,ULanguage;

type TItem = class //used for Objects in StringList
               FKategorie: shortint;
               FMeaning: string;
             end;
     TProgress = procedure(Percent:Byte; ID:Byte=0) of Object;
     TDictionary=class(TStringList)
         private
            FDictFileName, FDictFileSize, FDictFileDate,FDictWordCount : string;
            FProgress : TProgress;
            FChanged  : boolean;
            function GetMeaning(index:integer):string;
            procedure SetMeaning(index:integer;Value:string);
            function GetIsWordInDictionary(Value:string): boolean;
         public
            constructor Create;
            destructor Destroy;override;
            procedure SaveAsText(const FileName: string); //ascii file, meanings are separated by ";"
            function LoadAsText(const FileName: string):boolean;

            //Find, IndexOf and Adds are overwritten to have fast access even if not sorted by default quicksort
            //sort and Find are done by > or < instead of AnsiCompareStr()
            function IndexOf(const s:string): Integer; override;
            function Find(const s:string; var Index: Integer): Boolean; override;
            function AddObject(const s:string; aObject: TObject): Integer; override;
            function Add(const s:string): Integer; override;
            procedure Delete(Index: Integer); override;

            property DictFileName:string read FDictFileName;
            property DictFileSize:string read FDictFileSize;
            property DictFileDate:string read FDictFileDate;
            property DictWordCount:string read FDictWordCount;
            property Changed: boolean read FChanged;

            property Meaning[index:integer]:string read GetMeaning write SetMeaning;
            property OnProgress:TProgress read FProgress write FProgress;
            property IsWordInDictionary[Value:string]:boolean read GetIsWordInDictionary;
         end;

var Dictionary : TDictionary;

implementation

constructor TDictionary.Create;
begin
   inherited;
   Sorted:=false;//no default sorting to ensure to have umlaut after letters (need in bruteforce)
   FDictFileName:='';
end;

destructor TDictionary.Destroy;
var i:integer;
begin
   if FChanged then
     if MessageDlg('Library was changed. Save?', mtConfirmation, [mbYes, mbNo],0)=mrYes then
        SaveAsText(FDictFileName);
   for i:=0 to Count-1 do Objects[i].Free;
   inherited;
end;

function TDictionary.LoadAsText(const FileName: string):boolean;
var fc  : file of Byte;
    f   : textfile;
    i   : integer;
    tmp : TItem;
    s   : string;
    z,zsize : LongWord;
begin
   if FileExists(FileName) then
   try
     //clear old entries
     for i:=0 to Count-1 do Objects[i].Free;
     Clear;
     //get infos
     assignfile(fc,FileName);
     reset(fc);
     zsize:=System.FileSize(fc);
     FDictFileName:=FileName;
     FDictFileSize:=FileSize2String(zsize);
     FDictFileDate:=DateTimeToStr(FileDateToDateTime(FileAge(FileName)));
     closefile(fc);
     try
       assignfile(f,FileName);
       reset(f);
       z:=0;
       while not eof(f) do
       begin
         readln(f,s);
         inc(z,length(s)+2); //length + CRLF
         if assigned(FProgress) then FProgress(round((z/zsize)*100));
         s:=AnsiUpperCase(s); //only uppercases are used
         i:=pos(';',s);       //with ';' additional informations start
         if i>0 then
         begin
           tmp:=TItem.Create;
           tmp.FMeaning:=copy(s,i+1,length(s));
           AddObject(copy(s,1,i-1),tmp);
         end else Add(s);
       end; //while not eof(f)
       FDictWordCount:=inttostr(Count);
       Result:=true;
     finally
       closefile(f);
       FChanged:=false;
       if assigned(FProgress) then FProgress(101);//progress>100 clears statusbar
     end;
   except
     Result:=false;
   end else //FileExists()
   begin
     Result:=false;
     FDictFileName:=Language.Translate['not found'];
     FDictFileSize:='---';
     FDictFileDate:='---';
     FDictWordCount:='0';
   end;
end;

procedure TDictionary.SaveAsText(const FileName: string);
var f : textfile;
    i : integer;
    tmp : TItem;
begin
   assignfile(f,FileName);
   rewrite(f);
   for i:=0 to Count-1 do
   begin
      tmp:=TItem(Objects[i]);
      if tmp=nil then writeln(f,Strings[i])
                 else writeln(f,Strings[i]+';'+tmp.FMeaning)
   end;
   closefile(f);
   FChanged:=false;
end;

function TDictionary.GetMeaning(index:integer):string;
var tmp:TItem;
begin
   if (index>0) and (index<Count) then
   begin
      tmp:=TItem(Objects[index]);
      if tmp=nil then Result:='' else Result:=tmp.FMeaning;
   end else Result:='';
end;

procedure TDictionary.SetMeaning(index:integer;Value:string);
var tmp:TItem;
begin
   if (index>0) and (index<Count) then
   begin
      tmp:=TItem(Objects[index]);
      if tmp=nil then tmp:=TItem.Create;
      tmp.FMeaning:=Value;
      Objects[index]:=tmp;
      FChanged:=true;
   end;
end;

function TDictionary.GetIsWordInDictionary(Value:string): boolean;
begin
   Result:=(IndexOf(Value)<>-1);
end;

function TDictionary.IndexOf(const s:string): Integer;
begin
   if not Find(S, Result) then Result:=-1;
end;

function TDictionary.Find(const s:string; var Index: Integer): Boolean;
var l, h, i: Integer;
begin
  Result:=false;
  l:=0;
  h:=Count-1;
  while l<=h do
  begin
    i:=(l+h) shr 1;
    if Strings[i]<s then l:=I+1 else
    begin
      h:=i-1;
      if Strings[i]=s then
      begin
        Result:=true;
        l:=i;
      end;
    end;
  end;
  Index:=l;
end;

function TDictionary.Add(const s:string): Integer;
begin
  Result:=AddObject(s, nil);
  FChanged:=true;
end;

function TDictionary.AddObject(const s:string; aObject: TObject): Integer;
begin
  Find(s, Result);
  InsertItem(Result, s, aObject);
  FChanged:=true;
end;

procedure TDictionary.Delete(Index: Integer);
begin
  inherited;
  FChanged:=true;
end;

end.

