source: trunk/src/util/Hunspell.java @ 1271

Last change on this file since 1271 was 1271, checked in by stefan, 6 years ago

improved latex output parser (recognition of nested errors)

File size: 9.1 KB
Line 
1package util;
2
3import de.endrullis.utils.StringUtils;
4
5import java.io.*;
6import java.util.*;
7import java.util.regex.Matcher;
8import java.util.regex.Pattern;
9
10/**
11 * Java API for the command line tool hunspell.
12 *
13 * @author Stefan Endrullis
14 */
15public final class Hunspell implements SpellChecker {
16  public static String HUNSPELL_EXECUTABLE = "hunspell";
17
18  private static final Matcher masterDictMatcher = Pattern.compile("/([^/\\.]+)\\.multi").matcher("");
19
20  private static boolean instanceFailed = false;
21  private static HashMap<String, Hunspell> instances = new HashMap<String, Hunspell>();
22
23  private PrintStream hunspellIn;
24  private BufferedReader hunspellOut;
25  private BufferedReader hunspellErr;
26  private InputStream out;
27  private String lang;
28  private HashMap<String,Result> cache = new HashMap<String,Result>();
29
30  /** Words in the personal dictionary. */
31  private HashSet<String> personalWords = new HashSet<String>();
32
33
34  public static void main(String[] args) throws IOException {
35    // print all available dictionaries
36    System.out.println("available dictionaries");
37    for (String dict : availableDicts()) {
38      System.out.println(dict);
39    }
40    // start hunspell with language "en" / "en_GB"
41    Hunspell hunspell = new Hunspell("en_GB");
42    // test some words
43    System.out.println("\ntest some words");
44    System.out.println(hunspell.check("the"));
45    System.out.println(hunspell.check("bla"));
46    System.out.println(hunspell.check("teh"));
47    System.out.println(hunspell.check("linebreak"));
48    hunspell.shutdown();
49
50    hunspell = new Hunspell("de_DE");
51    // test some words
52    System.out.println("\ntest some words");
53    System.out.println(hunspell.check("Eingabemaske"));
54    System.out.println(hunspell.check("Eingabemenge"));
55    System.out.println(hunspell.check("Bierbauch"));
56    hunspell.shutdown();
57  }
58
59  /**
60   * Starts hunspell with language "en" and language tag "en_GB".
61   *
62   * @throws java.io.IOException if hunspell could not be started
63   */
64  public Hunspell() throws IOException {
65    this("en_GB");
66  }
67
68  /**
69   * Creates the hunspell wrapper that runs hunspell in background.
70   *
71   * @param lang language, e.g. "en" or "en_GB"
72   * @throws java.io.IOException if hunspell could not be started
73   */
74  public Hunspell(String lang) throws IOException {
75    startAspell(lang);
76  }
77
78  private void startAspell(String lang) throws IOException {
79    this.lang = lang;
80
81    String[] hunspellCommand = new String[]{
82        HUNSPELL_EXECUTABLE,
83        "-i",
84        "Latin1",
85        "-a",
86        "-d",
87        lang,
88    };
89
90    Process hunspellProcess = Runtime.getRuntime().exec(hunspellCommand);
91
92    // see if hunspell died
93    try {
94      int exitValue = hunspellProcess.exitValue();
95      throw new IOException("Aspell failed to start / aborted with error code " + exitValue);
96    } catch (IllegalThreadStateException ignored) {
97    }
98
99    hunspellIn = new PrintStream(new BufferedOutputStream(hunspellProcess.getOutputStream()), true, "Latin1");
100    out = hunspellProcess.getInputStream();
101    hunspellOut = new BufferedReader(new InputStreamReader(out, "Latin1"));
102    hunspellErr = new BufferedReader(new InputStreamReader(hunspellProcess.getErrorStream()));
103
104    // read version line
105    String version = hunspellOut.readLine();
106    if (version == null) throw new IOException("Aspell failed to start: " + hunspellErr.readLine());
107
108    personalWords = getPersonalWordList();
109  }
110
111  /**
112   * Check spelling of the word using hunspell.
113   *
114   * @param word word to check
115   * @return hunspell result
116   * @throws java.io.IOException thrown if execution of hunspell failed
117   */
118  public synchronized Result check(String word) throws IOException {
119    word = StringUtils.truncate(word);
120
121    Result cachedResult = cache.get(word);
122    if (cachedResult != null) {
123      return cachedResult;
124    }
125
126    flushOut();
127    hunspellIn.println(word);
128    hunspellIn.flush();
129
130    String line = hunspellOut.readLine();
131
132    if (line.equals("*") || line.startsWith("+") || line.startsWith("-")) {
133      hunspellOut.readLine();
134      return newResult(word, new Result());
135    } else if (line.startsWith("#")) {
136      hunspellOut.readLine();
137      return newResult(word, new Result(new ArrayList<String>(0)));
138    } else if (line.startsWith("&")) {
139      hunspellOut.readLine();
140      return newResult(word, new Result(Arrays.asList(line.split(": ")[1].split(", "))));
141    } else {
142      throw new RuntimeException("unknown hunspell answer: " + line);
143    }
144  }
145
146  private Result newResult(String word, Result result) {
147    cache.put(word, result);
148    return result;
149  }
150
151  /**
152   * Adds the word to the hunspell user dictionary.
153   *
154   * @param word word to add
155   */
156  public synchronized void addToPersonalDict(String word) {
157    word = StringUtils.truncate(word);
158    hunspellIn.println("*" + word);
159    hunspellIn.println("#");
160    hunspellIn.flush();
161
162    personalWords.add(word);
163
164    cache.remove(word);
165  }
166
167  public synchronized void removeFromPersonalDict(String word) throws IOException {
168    word = StringUtils.truncate(word);
169    // remove word from personal dict
170    File personalDict = new File(System.getProperty("user.home"), ".hunspell_" + lang);
171    File newPersonalDict = new File(System.getProperty("user.home"), ".hunspell_" + lang + "_new");
172    BufferedReader r = new BufferedReader(new FileReader(personalDict));
173    PrintStream w = new PrintStream(new FileOutputStream(newPersonalDict));
174
175    String line;
176    while ((line = r.readLine()) != null) {
177      if (!line.equals(word)) {
178        w.println(line);
179      }
180    }
181
182    newPersonalDict.renameTo(personalDict);
183
184    shutdown();
185    startAspell(lang);
186
187    personalWords.remove(word);
188
189    cache.remove(word);
190  }
191
192  /**
193   * Returns the value of the given option.
194   *
195   * @param option option name
196   * @return value
197   * @throws java.io.IOException if an I/O error occurs
198   */
199  public synchronized String getOption(String option) throws IOException {
200    return call("$$cr " + option);
201  }
202
203  /**
204   * Sets the value of the given option.
205   *
206   * @param option option name
207   * @param value  value
208   */
209  public synchronized void setOption(String option, String value) {
210    hunspellIn.println("$$cs " + option + "," + value);
211    hunspellIn.flush();
212  }
213
214  public void setLang(String lang) {
215    setOption("lang", lang);
216  }
217
218  public String getLang() throws IOException {
219    return getOption("lang");
220  }
221
222  public String getMasterLang() throws IOException {
223    masterDictMatcher.reset(getOption("master"));
224    masterDictMatcher.find();
225    return masterDictMatcher.group(1);
226  }
227
228  public HashSet<String> getPersonalWordList() throws IOException {
229    File wordListFile = new File(System.getProperty("user.home"), ".hunspell_" + lang);
230
231    if (wordListFile.exists()) {
232      HashSet<String> wordList = new HashSet<String>();
233
234      BufferedReader r = new BufferedReader(new FileReader(wordListFile));
235      String word;
236      while ((word = r.readLine()) != null) {
237        wordList.add(word);
238      }
239     
240      return wordList;
241    }
242    return new HashSet<String>();
243  }
244
245  private String call(String input) throws IOException {
246    flushOut();
247    hunspellIn.println(input);
248    hunspellIn.flush();
249    return hunspellOut.readLine();
250  }
251
252  private void flushOut() throws IOException {
253    while (out.available() > 0) hunspellOut.readLine();
254  }
255
256  public HashSet<String> getPersonalWords() {
257    return personalWords;
258  }
259
260  /**
261   * Shutdown hunspell.
262   */
263  public void shutdown() {
264    try {
265      hunspellIn.close();
266      hunspellOut.close();
267      hunspellErr.close();
268    } catch (Exception ignored) {
269    }
270  }
271
272  public static Hunspell getInstance(String lang) {
273    if (instanceFailed) return null;
274
275    Hunspell instance = instances.get(lang);
276    if (instance == null) {
277      try {
278        instance = new Hunspell(lang);
279        instances.put(lang, instance);
280      } catch (IOException e) {
281        instanceFailed = true;
282        System.err.println("Warning: Failed to initialize spell checker 'hunspell':");
283        System.err.println("  " + e.getMessage());
284      }
285    }
286
287    return instance;
288  }
289
290  /**
291   * Returns the available dictionaries provided by hunspell.
292   *
293   * @return list of dictionaries provided by hunspell
294   * @throws java.io.IOException thrown if execution of hunspell failed
295   */
296  public static List<String> availableDicts() throws IOException {
297    Process process = Runtime.getRuntime().exec(new String[]{
298        HUNSPELL_EXECUTABLE,
299        "-D"
300    });
301    process.getOutputStream().close();
302    BufferedReader r = new BufferedReader(new InputStreamReader(process.getErrorStream()));
303
304    // full list
305    List<String> fullDicts = new ArrayList<String>();
306    String line;
307    while ((line = r.readLine()) != null && line.startsWith("AVAILABLE DICTIONARIES"));
308
309    while ((line = r.readLine()) != null) {
310      if (line.equals("LOADED DICTIONARY:")) break;
311      fullDicts.add(line);
312    }
313
314    // abbreviate to language names
315    Set<String> dicts = new HashSet<String>();
316    for (String fullDict : fullDicts) {
317      String abbrName = StringUtils.stringAfter(fullDict, "/", 'l').getOrElse(fullDict);
318      dicts.add(abbrName);
319    }
320
321    List<String> sortedDicts = new ArrayList<String>(dicts);
322    Collections.sort(sortedDicts);
323
324    return sortedDicts;
325  }
326}
Note: See TracBrowser for help on using the repository browser.