123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- unit SpellCheck;
- { Simple unit to simplify/OOP-ize pascal-style the aspell interface. Currently
- very limited, will be expanded eventually. Use like you wish. }
- {$mode objfpc}{$H+}
- interface
- uses
- SysUtils, Classes, Aspell;
- type
- TSuggestionArray = array of string;
-
- TWordError = record
- Word: string; // the word itself
- Pos: LongWord; // word position in line
- Length: LongWord; // word length
- Suggestions: TSuggestionArray; // suggestions for the given word
- end;
-
- TLineErrors = array of TWordError;
- TLineErrorsArray = array of TLineErrors;
- { TSpellCheck }
- TSpeller = class // abstract class, basis for all checkers
- protected
- FMode: string;
- FEncoding: string;
- FLanguage: string;
- procedure SetEncoding(const AValue: string);
- procedure SetLanguage(const AValue: string);
- procedure SetMode(const AValue: string);
- procedure CreateSpeller; virtual; abstract;
- procedure FreeSpeller; virtual; abstract;
- public
- constructor Create;
- destructor Destroy; override;
- public
- property Mode: string read FMode write SetMode;
- property Encoding: string read FEncoding write SetEncoding;
- property Language: string read FLanguage write SetLanguage;
- end;
-
- { TWordSpeller }
- TWordSpeller = class(TSpeller) // class for simple per-word checking
- private
- FSpeller: PAspellSpeller;
- FLastError: string;
- function DoCreateSpeller(Lang, Enc, aMode: pChar): PAspellSpeller;
- protected
- procedure CreateSpeller; override;
- procedure FreeSpeller; override;
- public
- function SpellCheck(const Word: string): TSuggestionArray; // use to check single words, parsed out by you
- end;
-
- { TDocumentSpeller }
- TDocumentSpeller = class(TWordSpeller)
- private
- FChecker: PAspellDocumentChecker;
- FLineErrors: TLineErrorsArray;
- FNameSuggestions: Boolean;
- function GetLineErrors(i: Integer): TLineErrors;
- function GetLineErrorsCount: Integer;
- protected
- procedure CreateSpeller; override;
- procedure FreeSpeller; override;
- procedure DoNameSuggestions(const Word: string; var aWordError: TWordError);
- public
- constructor Create;
- function CheckLine(const aLine: string): TLineErrors;
- function CheckDocument(const FileName: string): Integer; // returns number of spelling errors found or -1 for error
- function CheckDocument(aStringList: TStringList): Integer; // returns number of spelling errors found or -1 for error
- procedure Reset;
- public
- property LineErrors[i: Integer]: TLineErrors read GetLineErrors;
- property LineErrorsCount: Integer read GetLineErrorsCount;
- property NameSuggestions: Boolean read FNameSuggestions write FNameSuggestions;
- end;
- implementation
- const
- DEFAULT_ENCODING = 'utf-8';
- DEFAULT_LANGUAGE = 'en';
- DEFAULT_MODE = '';
- function GetDefaultLanguage: string;
- begin
- Result := GetEnvironmentVariable('LANG');
- if Length(Result) = 0 then
- Result := DEFAULT_LANGUAGE;
- end;
- { TSpeller }
- procedure TSpeller.SetEncoding(const AValue: string);
- begin
- FEncoding := aValue;
- CreateSpeller;
- end;
- procedure TSpeller.SetLanguage(const AValue: string);
- begin
- FLanguage := aValue;
- CreateSpeller;
- end;
- procedure TSpeller.SetMode(const AValue: string);
- begin
- FMode := aValue;
- CreateSpeller;
- end;
- constructor TSpeller.Create;
- begin
- FEncoding := DEFAULT_ENCODING;
- FLanguage := GetDefaultLanguage;
- FMode := DEFAULT_MODE;
- CreateSpeller;
- end;
- destructor TSpeller.Destroy;
- begin
- FreeSpeller;
- end;
- { TWordSpeller }
- function TWordSpeller.DoCreateSpeller(Lang, Enc, aMode: pChar): PAspellSpeller;
- var
- Error: Paspellcanhaveerror;
- begin
- Result := new_aspell_config();
- if Length(FLanguage) > 0 then
- aspell_config_replace(Result, 'lang', Lang);
- if Length(FEncoding) > 0 then
- aspell_config_replace(Result, 'encoding', Enc);
- if Length(FMode) > 0 then
- aspell_config_replace(Result, 'mode', aMode);
- Error := new_aspell_speller(Result);
- delete_aspell_config(Result);
- if aspell_error_number(Error) <> 0 then begin
- FLastError := aspell_error_message(Error);
- delete_aspell_can_have_error(Error);
- Result := nil;
- end else
- Result := to_aspell_speller(Error);
- end;
- procedure TWordSpeller.CreateSpeller;
- begin
- FLastError := '';
- FreeSpeller;
- FSpeller := DoCreateSpeller(pChar(FLanguage), pChar(FEncoding), pChar(FMode));
- if not Assigned(FSpeller) then
- FSpeller := DoCreateSpeller(nil, pChar(FEncoding), pChar(FMode));
- if not Assigned(FSpeller) then
- FSpeller := DoCreateSpeller(nil, pChar(FEncoding), nil);
- if not Assigned(FSpeller) then
- FSpeller := DoCreateSpeller(nil, nil, pChar(FMode));
- if not Assigned(FSpeller) then
- FSpeller := DoCreateSpeller(nil, nil, nil);
- if not Assigned(FSpeller) then
- raise Exception.Create('Error on speller creation: ' + FLastError);
- end;
- procedure TWordSpeller.FreeSpeller;
- begin
- if Assigned(FSpeller) then begin
- delete_aspell_speller(FSpeller);
- FSpeller := nil;
- end;
- end;
- function TWordSpeller.SpellCheck(const Word: string): TSuggestionArray;
- var
- sgs: Paspellwordlist;
- elm: Paspellstringenumeration;
- tmp: pChar;
- i: Integer = 0;
- begin
- SetLength(Result, 0);
- if aspell_speller_check(FSpeller, pChar(Word), Length(Word)) = 0 then begin
- sgs := aspell_speller_suggest(FSpeller, pChar(Word), Length(Word));
- elm := aspell_word_list_elements(sgs);
- repeat
- if i >= Length(Result) then
- SetLength(Result, Length(Result) + 10);
- tmp := aspell_string_enumeration_next(elm);
- if tmp <> nil then begin
- Result[i] := tmp;
- Inc(i);
- end;
- until tmp = nil;
- SetLength(Result, i);
- delete_aspell_string_enumeration(elm);
- end;
- end;
- { TDocumentSpeller }
- function TDocumentSpeller.GetLineErrors(i: Integer): TLineErrors;
- begin
- Result := FLineErrors[i];
- end;
- function TDocumentSpeller.GetLineErrorsCount: Integer;
- begin
- Result := Length(FLineErrors);
- end;
- procedure TDocumentSpeller.CreateSpeller;
- var
- Error: PAspellCanHaveError;
- begin
- inherited CreateSpeller;
-
- Error := new_aspell_document_checker(FSpeller);
- if aspell_error_number(Error) <> 0 then
- raise Exception.Create('Error on checker creation: ' + aspell_error_message(Error))
- else
- FChecker := to_aspell_document_checker(Error);
- end;
- procedure TDocumentSpeller.FreeSpeller;
- begin
- if Assigned(FChecker) then begin
- delete_aspell_document_checker(FChecker);
- FChecker := nil;
- end;
- inherited FreeSpeller;
- end;
- procedure TDocumentSpeller.DoNameSuggestions(const Word: string;
- var aWordError: TWordError);
- begin
- aWordError.Suggestions := SpellCheck(Word);
- end;
- constructor TDocumentSpeller.Create;
- begin
- inherited Create;
-
- FNameSuggestions := True;
- end;
- function TDocumentSpeller.CheckLine(const aLine: string): TLineErrors;
- const
- CHUNK_SIZE = 10;
- var
- i, Count: Integer;
- Token: AspellToken;
- begin
- aspell_document_checker_process(FChecker, pChar(aLine), Length(aLine));
- SetLength(Result, CHUNK_SIZE);
- i := 0;
- Count := 0;
- repeat
- Token := aspell_document_checker_next_misspelling(FChecker);
- if Token.len > 0 then begin
- if Length(Result) <= i then
- SetLength(Result, Length(Result) + CHUNK_SIZE);
- Result[i].Word := Copy(aLine, Token.offset + 1, Token.len);
- Result[i].Pos := Token.offset + 1; // C goes from 0, we go from 1
- Result[i].Length := Token.len;
- if FNameSuggestions then
- DoNameSuggestions(Copy(aLine, Token.offset + 1, Token.len), Result[i]);
-
- Inc(Count);
- end;
- Inc(i);
- until Token.len = 0;
-
- SetLength(Result, Count);
- end;
- function TDocumentSpeller.CheckDocument(const FileName: string): Integer;
- var
- s: TStringList;
- begin
- Result := 0;
- if FileExists(FileName) then try
- s := TStringList.Create;
- s.LoadFromFile(FileName);
- Result := CheckDocument(s);
- finally
- s.Free;
- end;
- end;
- function TDocumentSpeller.CheckDocument(aStringList: TStringList): Integer;
- var
- i: Integer;
- begin
- Result := 0;
- SetLength(FLineErrors, aStringList.Count);
- for i := 0 to aStringList.Count - 1 do begin
- FLineErrors[i] := CheckLine(aStringList[i]);
- Inc(Result, Length(FLineErrors[i]));
- end;
- end;
- procedure TDocumentSpeller.Reset;
- begin
- aspell_document_checker_reset(FChecker);
- end;
- end.
|