spellcheck.pp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. unit SpellCheck;
  2. { Simple unit to simplify/OOP-ize pascal-style the aspell interface. Currently
  3. very limited, will be expanded eventually. Use like you wish. }
  4. {$mode objfpc}{$H+}
  5. interface
  6. uses
  7. SysUtils, Classes, Aspell;
  8. type
  9. TSuggestionArray = array of string;
  10. TWordError = record
  11. Word: string; // the word itself
  12. Pos: LongWord; // word position in line
  13. Length: LongWord; // word length
  14. Suggestions: TSuggestionArray; // suggestions for the given word
  15. end;
  16. TLineErrors = array of TWordError;
  17. TLineErrorsArray = array of TLineErrors;
  18. { TSpellCheck }
  19. TSpeller = class // abstract class, basis for all checkers
  20. protected
  21. FMode: string;
  22. FEncoding: string;
  23. FLanguage: string;
  24. procedure SetEncoding(const AValue: string);
  25. procedure SetLanguage(const AValue: string);
  26. procedure SetMode(const AValue: string);
  27. procedure CreateSpeller; virtual; abstract;
  28. procedure FreeSpeller; virtual; abstract;
  29. public
  30. constructor Create;
  31. destructor Destroy; override;
  32. public
  33. property Mode: string read FMode write SetMode;
  34. property Encoding: string read FEncoding write SetEncoding;
  35. property Language: string read FLanguage write SetLanguage;
  36. end;
  37. { TWordSpeller }
  38. TWordSpeller = class(TSpeller) // class for simple per-word checking
  39. private
  40. FSpeller: PAspellSpeller;
  41. FLastError: string;
  42. function DoCreateSpeller(Lang, Enc, aMode: pChar): PAspellSpeller;
  43. protected
  44. procedure CreateSpeller; override;
  45. procedure FreeSpeller; override;
  46. public
  47. function SpellCheck(const Word: string): TSuggestionArray; // use to check single words, parsed out by you
  48. end;
  49. { TDocumentSpeller }
  50. TDocumentSpeller = class(TWordSpeller)
  51. private
  52. FChecker: PAspellDocumentChecker;
  53. FLineErrors: TLineErrorsArray;
  54. FNameSuggestions: Boolean;
  55. function GetLineErrors(i: Integer): TLineErrors;
  56. function GetLineErrorsCount: Integer;
  57. protected
  58. procedure CreateSpeller; override;
  59. procedure FreeSpeller; override;
  60. procedure DoNameSuggestions(const Word: string; var aWordError: TWordError);
  61. public
  62. constructor Create;
  63. function CheckLine(const aLine: string): TLineErrors;
  64. function CheckDocument(const FileName: string): Integer; // returns number of spelling errors found or -1 for error
  65. function CheckDocument(aStringList: TStringList): Integer; // returns number of spelling errors found or -1 for error
  66. procedure Reset;
  67. public
  68. property LineErrors[i: Integer]: TLineErrors read GetLineErrors;
  69. property LineErrorsCount: Integer read GetLineErrorsCount;
  70. property NameSuggestions: Boolean read FNameSuggestions write FNameSuggestions;
  71. end;
  72. implementation
  73. const
  74. DEFAULT_ENCODING = 'utf-8';
  75. DEFAULT_LANGUAGE = 'en';
  76. DEFAULT_MODE = '';
  77. function GetDefaultLanguage: string;
  78. begin
  79. Result := GetEnvironmentVariable('LANG');
  80. if Length(Result) = 0 then
  81. Result := DEFAULT_LANGUAGE;
  82. end;
  83. { TSpeller }
  84. procedure TSpeller.SetEncoding(const AValue: string);
  85. begin
  86. FEncoding := aValue;
  87. CreateSpeller;
  88. end;
  89. procedure TSpeller.SetLanguage(const AValue: string);
  90. begin
  91. FLanguage := aValue;
  92. CreateSpeller;
  93. end;
  94. procedure TSpeller.SetMode(const AValue: string);
  95. begin
  96. FMode := aValue;
  97. CreateSpeller;
  98. end;
  99. constructor TSpeller.Create;
  100. begin
  101. FEncoding := DEFAULT_ENCODING;
  102. FLanguage := GetDefaultLanguage;
  103. FMode := DEFAULT_MODE;
  104. CreateSpeller;
  105. end;
  106. destructor TSpeller.Destroy;
  107. begin
  108. FreeSpeller;
  109. end;
  110. { TWordSpeller }
  111. function TWordSpeller.DoCreateSpeller(Lang, Enc, aMode: pChar): PAspellSpeller;
  112. var
  113. Error: Paspellcanhaveerror;
  114. begin
  115. Result := new_aspell_config();
  116. if Length(FLanguage) > 0 then
  117. aspell_config_replace(Result, 'lang', Lang);
  118. if Length(FEncoding) > 0 then
  119. aspell_config_replace(Result, 'encoding', Enc);
  120. if Length(FMode) > 0 then
  121. aspell_config_replace(Result, 'mode', aMode);
  122. Error := new_aspell_speller(Result);
  123. delete_aspell_config(Result);
  124. if aspell_error_number(Error) <> 0 then begin
  125. FLastError := aspell_error_message(Error);
  126. delete_aspell_can_have_error(Error);
  127. Result := nil;
  128. end else
  129. Result := to_aspell_speller(Error);
  130. end;
  131. procedure TWordSpeller.CreateSpeller;
  132. begin
  133. FLastError := '';
  134. FreeSpeller;
  135. FSpeller := DoCreateSpeller(pChar(FLanguage), pChar(FEncoding), pChar(FMode));
  136. if not Assigned(FSpeller) then
  137. FSpeller := DoCreateSpeller(nil, pChar(FEncoding), pChar(FMode));
  138. if not Assigned(FSpeller) then
  139. FSpeller := DoCreateSpeller(nil, pChar(FEncoding), nil);
  140. if not Assigned(FSpeller) then
  141. FSpeller := DoCreateSpeller(nil, nil, pChar(FMode));
  142. if not Assigned(FSpeller) then
  143. FSpeller := DoCreateSpeller(nil, nil, nil);
  144. if not Assigned(FSpeller) then
  145. raise Exception.Create('Error on speller creation: ' + FLastError);
  146. end;
  147. procedure TWordSpeller.FreeSpeller;
  148. begin
  149. if Assigned(FSpeller) then begin
  150. delete_aspell_speller(FSpeller);
  151. FSpeller := nil;
  152. end;
  153. end;
  154. function TWordSpeller.SpellCheck(const Word: string): TSuggestionArray;
  155. var
  156. sgs: Paspellwordlist;
  157. elm: Paspellstringenumeration;
  158. tmp: pChar;
  159. i: Integer = 0;
  160. begin
  161. SetLength(Result, 0);
  162. if aspell_speller_check(FSpeller, pChar(Word), Length(Word)) = 0 then begin
  163. sgs := aspell_speller_suggest(FSpeller, pChar(Word), Length(Word));
  164. elm := aspell_word_list_elements(sgs);
  165. repeat
  166. if i >= Length(Result) then
  167. SetLength(Result, Length(Result) + 10);
  168. tmp := aspell_string_enumeration_next(elm);
  169. if tmp <> nil then begin
  170. Result[i] := tmp;
  171. Inc(i);
  172. end;
  173. until tmp = nil;
  174. SetLength(Result, i);
  175. delete_aspell_string_enumeration(elm);
  176. end;
  177. end;
  178. { TDocumentSpeller }
  179. function TDocumentSpeller.GetLineErrors(i: Integer): TLineErrors;
  180. begin
  181. Result := FLineErrors[i];
  182. end;
  183. function TDocumentSpeller.GetLineErrorsCount: Integer;
  184. begin
  185. Result := Length(FLineErrors);
  186. end;
  187. procedure TDocumentSpeller.CreateSpeller;
  188. var
  189. Error: PAspellCanHaveError;
  190. begin
  191. inherited CreateSpeller;
  192. Error := new_aspell_document_checker(FSpeller);
  193. if aspell_error_number(Error) <> 0 then
  194. raise Exception.Create('Error on checker creation: ' + aspell_error_message(Error))
  195. else
  196. FChecker := to_aspell_document_checker(Error);
  197. end;
  198. procedure TDocumentSpeller.FreeSpeller;
  199. begin
  200. if Assigned(FChecker) then begin
  201. delete_aspell_document_checker(FChecker);
  202. FChecker := nil;
  203. end;
  204. inherited FreeSpeller;
  205. end;
  206. procedure TDocumentSpeller.DoNameSuggestions(const Word: string;
  207. var aWordError: TWordError);
  208. begin
  209. aWordError.Suggestions := SpellCheck(Word);
  210. end;
  211. constructor TDocumentSpeller.Create;
  212. begin
  213. inherited Create;
  214. FNameSuggestions := True;
  215. end;
  216. function TDocumentSpeller.CheckLine(const aLine: string): TLineErrors;
  217. const
  218. CHUNK_SIZE = 10;
  219. var
  220. i, Count: Integer;
  221. Token: AspellToken;
  222. begin
  223. aspell_document_checker_process(FChecker, pChar(aLine), Length(aLine));
  224. SetLength(Result, CHUNK_SIZE);
  225. i := 0;
  226. Count := 0;
  227. repeat
  228. Token := aspell_document_checker_next_misspelling(FChecker);
  229. if Token.len > 0 then begin
  230. if Length(Result) <= i then
  231. SetLength(Result, Length(Result) + CHUNK_SIZE);
  232. Result[i].Word := Copy(aLine, Token.offset + 1, Token.len);
  233. Result[i].Pos := Token.offset + 1; // C goes from 0, we go from 1
  234. Result[i].Length := Token.len;
  235. if FNameSuggestions then
  236. DoNameSuggestions(Copy(aLine, Token.offset + 1, Token.len), Result[i]);
  237. Inc(Count);
  238. end;
  239. Inc(i);
  240. until Token.len = 0;
  241. SetLength(Result, Count);
  242. end;
  243. function TDocumentSpeller.CheckDocument(const FileName: string): Integer;
  244. var
  245. s: TStringList;
  246. begin
  247. Result := 0;
  248. if FileExists(FileName) then try
  249. s := TStringList.Create;
  250. s.LoadFromFile(FileName);
  251. Result := CheckDocument(s);
  252. finally
  253. s.Free;
  254. end;
  255. end;
  256. function TDocumentSpeller.CheckDocument(aStringList: TStringList): Integer;
  257. var
  258. i: Integer;
  259. begin
  260. Result := 0;
  261. SetLength(FLineErrors, aStringList.Count);
  262. for i := 0 to aStringList.Count - 1 do begin
  263. FLineErrors[i] := CheckLine(aStringList[i]);
  264. Inc(Result, Length(FLineErrors[i]));
  265. end;
  266. end;
  267. procedure TDocumentSpeller.Reset;
  268. begin
  269. aspell_document_checker_reset(FChecker);
  270. end;
  271. end.