diff --git a/lib/spellchecker.js b/lib/spellchecker.js index 90c72e0..3f754fd 100644 --- a/lib/spellchecker.js +++ b/lib/spellchecker.js @@ -69,7 +69,7 @@ var getDictionaryPath = function() { } catch (error) { } return dict; -} +}; module.exports = { setDictionary: setDictionary, diff --git a/spec/spellchecker-spec.coffee b/spec/spellchecker-spec.coffee index cc1013c..03d2262 100644 --- a/spec/spellchecker-spec.coffee +++ b/spec/spellchecker-spec.coffee @@ -107,7 +107,7 @@ describe "SpellChecker", -> describe ".getCorrectionsForMisspelling(word)", -> beforeEach -> @fixture = new Spellchecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory + @fixture.setDictionary 'en_US', dictionaryDirectory it "returns an array of possible corrections", -> corrections = @fixture.getCorrectionsForMisspelling('worrd') @@ -158,17 +158,24 @@ describe "SpellChecker", -> @fixture.setDictionary defaultLanguage, dictionaryDirectory it "returns an array of string dictionary names", -> - # NB: getAvailableDictionaries is nop'ped in hunspell and it also doesn't - # work inside Appveyor's CI environment - return if process.platform is 'linux' or process.env.CI or process.env.SPELLCHECKER_PREFER_HUNSPELL - - dictionaries = @fixture.getAvailableDictionaries() + dictionaries = @fixture.getAvailableDictionaries dictionaryDirectory expect(Array.isArray(dictionaries)).toBe true expect(dictionaries.length).toBeGreaterThan 0 for dictionary in dictionaries.length expect(typeof dictionary).toBe 'string' - expect(diction.length).toBeGreaterThan 0 + expect(dictionary.length).toBeGreaterThan 0 + + it "returns the right dictionary names when using hunspell on linux", -> + return if not (process.platform is 'linux') and not (process.platform is 'win32' and process.env.SPELLCHECKER_PREFER_HUNSPELL) + + dictionaries = @fixture.getAvailableDictionaries dictionaryDirectory + expect(Array.isArray(dictionaries)).toBe true + + expect(dictionaries.length).toBeGreaterThan 3 + expect(dictionaries).toContain('en_US'); + expect(dictionaries).toContain('de_DE_frami'); + expect(dictionaries).toContain('fr'); describe ".setDictionary(lang, dictDirectory)", -> it "sets the spell checker's language, and dictionary directory", -> diff --git a/src/main.cc b/src/main.cc index 673837e..6c75dea 100644 --- a/src/main.cc +++ b/src/main.cc @@ -120,7 +120,7 @@ class Spellchecker : public Nan::ObjectWrap { std::string path = "."; if (info.Length() > 0) { - std::string path = *String::Utf8Value(info[0]); + path = *String::Utf8Value(info[0]); } std::vector dictionaries = diff --git a/src/spellchecker_hunspell.cc b/src/spellchecker_hunspell.cc index 0ca629b..5e98fdf 100644 --- a/src/spellchecker_hunspell.cc +++ b/src/spellchecker_hunspell.cc @@ -1,9 +1,35 @@ #include #include #include +#include +#include #include "../vendor/hunspell/src/hunspell/hunspell.hxx" #include "spellchecker_hunspell.h" +#ifdef WIN32 + +#include + +#define SEARCH_PATHS "C:\\Hunspell\\" +#define DIR_SEPARATOR "\\" +#define PATH_SEPARATOR ';' + +#else + +// Not windows +#include +#include + +#define SEARCH_PATHS \ + "/usr/share/hunspell:" \ + "/usr/share/myspell:" \ + "/usr/share/myspell/dicts:" \ + "/Library/Spelling" +#define DIR_SEPARATOR "/" +#define PATH_SEPARATOR ':' + +#endif + namespace spellchecker { HunspellSpellchecker::HunspellSpellchecker() : hunspell(NULL), transcoder(NewTranscoder()) { } @@ -29,22 +55,23 @@ bool HunspellSpellchecker::SetDictionary(const std::string& language, const std: std::string lang = language; std::replace(lang.begin(), lang.end(), '-', '_'); - std::string affixpath = dirname + "/" + lang + ".aff"; - std::string dpath = dirname + "/" + lang + ".dic"; + std::string search_path = dirname + PATH_SEPARATOR + SEARCH_PATHS; + + std::string affixpath = FindDictionary(search_path, lang, ".aff"); + std::string dpath = FindDictionary(search_path, lang, ".dic"); - // TODO: This code is almost certainly jacked on Win32 for non-ASCII paths - FILE* handle = fopen(dpath.c_str(), "r"); - if (!handle) { + if (dpath.compare("") == 0) { return false; } - fclose(handle); hunspell = new Hunspell(affixpath.c_str(), dpath.c_str()); return true; } std::vector HunspellSpellchecker::GetAvailableDictionaries(const std::string& path) { - return std::vector(); + std::string search_path = path + PATH_SEPARATOR + SEARCH_PATHS; + + return SearchAvailableDictionaries(search_path); } bool HunspellSpellchecker::IsMisspelled(const std::string& word) { @@ -141,4 +168,84 @@ std::vector HunspellSpellchecker::GetCorrectionsForMisspelling(cons return corrections; } +std::vector HunspellSpellchecker::SearchAvailableDictionaries(const std::string& path) { + std::vector my_list; + std::istringstream path_stream(path); + + for (std::string search_path; std::getline(path_stream, search_path, PATH_SEPARATOR); ) { + search_path.append(DIR_SEPARATOR); + +#ifdef WIN32 + search_path.append("*"); + + WIN32_FIND_DATA search_data; + memset(&search_data, 0, sizeof(WIN32_FIND_DATA)); + + HANDLE handle = FindFirstFile(search_path.c_str(), &search_data); + + while (handle != INVALID_HANDLE_VALUE) { + std::string filename(search_data.cFileName); + + if (filename.size() > 4 && filename.compare(filename.size() - 4, 4, ".dic") == 0) { + my_list.push_back(filename.substr(0, filename.size() - 4)); + } + else if (filename.size() > 7 && filename.compare(filename.size() - 7, 7, ".dic.hz") == 0) { + my_list.push_back(filename.substr(0, filename.size() - 7)); + } + + if (FindNextFile(handle, &search_data) == FALSE) { + break; + } + } + + FindClose(handle); +#else + DIR* dir = opendir(search_path.c_str()); + + if (dir) { + struct dirent* de; + while ((de = readdir(dir))) { + std::string filename(de->d_name); + + if (filename.size() > 4 && filename.compare(filename.size() - 4, 4, ".dic") == 0) { + my_list.push_back(filename.substr(0, filename.size() - 4)); + } + else if (filename.size() > 7 && filename.compare(filename.size() - 7, 7, ".dic.hz") == 0) { + my_list.push_back(filename.substr(0, filename.size() - 7)); + } + } + + closedir(dir); + } +#endif + } + + return my_list; +} + +std::string HunspellSpellchecker::FindDictionary(const std::string& path, const std::string& language, const std::string& extension) { + std::istringstream path_stream(path); + + for (std::string file_path; std::getline(path_stream, file_path, PATH_SEPARATOR); ) { + file_path.append(DIR_SEPARATOR); + file_path.append(language); + file_path.append(extension); + + std::ifstream f; + f.open(file_path, std::ios_base::in); + if (f.is_open()) { + return file_path; + } + + file_path.append(".hz"); + + f.open(file_path, std::ios_base::in); + if (f.is_open()) { + return file_path; + } + } + + return ""; +} + } // namespace spellchecker diff --git a/src/spellchecker_hunspell.h b/src/spellchecker_hunspell.h index bcc9d65..8114b39 100644 --- a/src/spellchecker_hunspell.h +++ b/src/spellchecker_hunspell.h @@ -24,6 +24,9 @@ class HunspellSpellchecker : public SpellcheckerImplementation { private: Hunspell* hunspell; Transcoder *transcoder; + + std::vector SearchAvailableDictionaries(const std::string& path); + std::string FindDictionary(const std::string& path, const std::string& language, const std::string& extension); }; } // namespace spellchecker