Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@
'src/spellchecker_linux.cc',
'src/transcoder_posix.cc',
],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
}],
['OS=="mac"', {
'sources': [
'src/spellchecker_mac.mm',
'src/transcoder_posix.cc',
],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'link_settings': {
'libraries': [
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
Expand Down
6 changes: 6 additions & 0 deletions lib/spellchecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ var ensureDefaultSpellCheck = function() {
setDictionary(lang, getDictionaryPath());
};

var addDictionary = function(lang, dictPath) {
ensureDefaultSpellCheck();
return defaultSpellcheck.addDictionary(lang, dictPath);
};

var setDictionary = function(lang, dictPath) {
ensureDefaultSpellCheck();
return defaultSpellcheck.setDictionary(lang, dictPath);
Expand Down Expand Up @@ -72,6 +77,7 @@ var getDictionaryPath = function() {
}

module.exports = {
addDictionary: addDictionary,
setDictionary: setDictionary,
add: add,
remove: remove,
Expand Down
22 changes: 21 additions & 1 deletion src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ class Spellchecker : public Nan::ObjectWrap {
info.GetReturnValue().Set(info.This());
}

static NAN_METHOD(AddDictionary) {
Nan::HandleScope scope;

if (info.Length() < 1) {
return Nan::ThrowError("Bad argument");
}

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

std::string language = *String::Utf8Value(info[0]);
std::string directory = ".";
if (info.Length() > 1) {
directory = *String::Utf8Value(info[1]);
}

bool result = that->impl->AddDictionary(language, directory);
info.GetReturnValue().Set(Nan::New(result));
}

static NAN_METHOD(SetDictionary) {
Nan::HandleScope scope;

Expand Down Expand Up @@ -98,7 +117,7 @@ class Spellchecker : public Nan::ObjectWrap {
that->impl->Add(word);
return;
}

static NAN_METHOD(Remove) {
Nan::HandleScope scope;
if (info.Length() < 1) {
Expand Down Expand Up @@ -174,6 +193,7 @@ class Spellchecker : public Nan::ObjectWrap {
tpl->SetClassName(Nan::New<String>("Spellchecker").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);

Nan::SetMethod(tpl->InstanceTemplate(), "addDictionary", Spellchecker::AddDictionary);
Nan::SetMethod(tpl->InstanceTemplate(), "setDictionary", Spellchecker::SetDictionary);
Nan::SetMethod(tpl->InstanceTemplate(), "getAvailableDictionaries", Spellchecker::GetAvailableDictionaries);
Nan::SetMethod(tpl->InstanceTemplate(), "getCorrectionsForMisspelling", Spellchecker::GetCorrectionsForMisspelling);
Expand Down
4 changes: 3 additions & 1 deletion src/spellchecker.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SRC_SPELLCHECKER_H_
#define SRC_SPELLCHECKER_H_

#include <locale>
#include <string>
#include <vector>
#include <stdint.h>
Expand All @@ -14,6 +15,7 @@ struct MisspelledRange {

class SpellcheckerImplementation {
public:
virtual bool AddDictionary(const std::string& language, const std::string& path) = 0;
virtual bool SetDictionary(const std::string& language, const std::string& path) = 0;
virtual std::vector<std::string> GetAvailableDictionaries(const std::string& path) = 0;

Expand All @@ -29,7 +31,7 @@ class SpellcheckerImplementation {
// NB: When using Hunspell, this will not modify the .dic file; custom words must be added each
// time the spellchecker is created. Use a custom dictionary file.
virtual void Add(const std::string& word) = 0;

// Removes a word from the custom dictionary added by Add.
// NB: When using Hunspell, this will not modify the .dic file; custom words must be added each
// time the spellchecker is created. Use a custom dictionary file.
Expand Down
90 changes: 69 additions & 21 deletions src/spellchecker_hunspell.cc
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
#include <cstdio>
#include <cwctype>
#include <algorithm>
#include <stdexcept>
#include "../vendor/hunspell/src/hunspell/hunspell.hxx"
#include "spellchecker_hunspell.h"

namespace spellchecker {

HunspellSpellchecker::HunspellSpellchecker() : hunspell(NULL), transcoder(NewTranscoder()) { }
HunspellSpellchecker::HunspellSpellchecker() : transcoder(NewTranscoder()) { }

HunspellSpellchecker::~HunspellSpellchecker() {
if (hunspell) {
delete hunspell;
for (size_t i = 0; i < hunspells.size(); ++i) {
delete hunspells[i].second;
}

if (transcoder) {
FreeTranscoder(transcoder);
}
}

bool HunspellSpellchecker::SetDictionary(const std::string& language, const std::string& dirname) {
if (hunspell) {
delete hunspell;
hunspell = NULL;
}

bool HunspellSpellchecker::AddDictionary(const std::string& language, const std::string& dirname) {
// NB: Hunspell uses underscore to separate language and locale, and Win8 uses
// dash - if they use the wrong one, just silently replace it for them
std::string lang = language;
Expand All @@ -39,25 +35,51 @@ bool HunspellSpellchecker::SetDictionary(const std::string& language, const std:
}
fclose(handle);

hunspell = new Hunspell(affixpath.c_str(), dpath.c_str());
std::locale loc;
try {
// On Linux locale requires "UTF-8" suffix; e.g., "en_US.UTF-8"
loc = std::locale((lang + ".UTF-8").c_str());
} catch (std::runtime_error & e) {
// On Windows locale names are different; e.g. en-US
std::string langDashed = language;
std::replace(langDashed.begin(), langDashed.end(), '_', '-');
std::locale loc(langDashed.c_str());
}

Hunspell* hunspell = new Hunspell(affixpath.c_str(), dpath.c_str());
hunspells.push_back(std::make_pair(loc, hunspell));
return true;
}

bool HunspellSpellchecker::SetDictionary(const std::string& language, const std::string& dirname) {
for (size_t i = 0; i < hunspells.size(); ++i) {
delete hunspells[i].second;
}
hunspells.clear();

return AddDictionary(language, dirname);
}

std::vector<std::string> HunspellSpellchecker::GetAvailableDictionaries(const std::string& path) {
return std::vector<std::string>();
}

bool HunspellSpellchecker::IsMisspelled(const std::string& word) {
if (!hunspell) {
return false;
for (size_t i = 0; i < hunspells.size(); ++i) {
Hunspell* hunspell = hunspells[i].second;
bool misspelled = hunspell->spell(word.c_str()) == 0;
if (!misspelled) {
return false;
}
}
return hunspell->spell(word.c_str()) == 0;

return true;
}

std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t *utf16_text, size_t utf16_length) {
std::vector<MisspelledRange> result;

if (!hunspell || !transcoder) {
if (hunspells.empty() || !transcoder) {
return result;
}

Expand All @@ -80,7 +102,7 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
break;

case in_separator:
if (iswalpha(c)) {
if (isAlpha(c)) {
word_start = i;
state = in_word;
} else if (!iswpunct(c) && !iswspace(c)) {
Expand All @@ -89,20 +111,29 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
break;

case in_word:
if (c == '\'' && iswalpha(utf16_text[i + 1])) {
if (c == '\'' && isAlpha(utf16_text[i + 1])) {
i++;
} else if (c == 0 || iswpunct(c) || iswspace(c)) {
state = in_separator;
bool converted = TranscodeUTF16ToUTF8(transcoder, (char *)utf8_buffer.data(), utf8_buffer.size(), utf16_text + word_start, i - word_start);
if (converted) {
if (hunspell->spell(utf8_buffer.data()) == 0) {
bool all_misspelled = true;
for (size_t i = 0; i < hunspells.size(); ++i) {
Hunspell* hunspell = hunspells[i].second;
bool misspelled = hunspell->spell(utf8_buffer.data()) == 0;
if (!misspelled) {
all_misspelled = false;
break;
}
}
if (all_misspelled) {
MisspelledRange range;
range.start = word_start;
range.end = i;
result.push_back(range);
}
}
} else if (!iswalpha(c)) {
} else if (!isAlpha(c)) {
state = unknown;
}
break;
Expand All @@ -113,21 +144,25 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
}

void HunspellSpellchecker::Add(const std::string& word) {
if (hunspell) {
if (!hunspells.empty()) {
Hunspell* hunspell = hunspells[0].second;
hunspell->add(word.c_str());
}
}

void HunspellSpellchecker::Remove(const std::string& word) {
if (hunspell) {
if (!hunspells.empty()) {
Hunspell* hunspell = hunspells[0].second;
hunspell->remove(word.c_str());
}
}

std::vector<std::string> HunspellSpellchecker::GetCorrectionsForMisspelling(const std::string& word) {
std::vector<std::string> corrections;

if (hunspell) {
for (size_t i = 0; i < hunspells.size(); ++i) {
Hunspell* hunspell = hunspells[i].second;

char** slist;
int size = hunspell->suggest(&slist, word.c_str());

Expand All @@ -141,4 +176,17 @@ std::vector<std::string> HunspellSpellchecker::GetCorrectionsForMisspelling(cons
return corrections;
}

bool HunspellSpellchecker::isAlpha(std::wint_t c) const {
if (iswalpha(c)) {
return true;
}
for (size_t i = 0; i < hunspells.size(); ++i) {
std::locale loc = hunspells[i].first;
if (std::isalpha((wchar_t)c, loc)) {
return true;
}
}
return false;
}

} // namespace spellchecker
5 changes: 4 additions & 1 deletion src/spellchecker_hunspell.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class HunspellSpellchecker : public SpellcheckerImplementation {
HunspellSpellchecker();
~HunspellSpellchecker();

bool AddDictionary(const std::string& language, const std::string& path);
bool SetDictionary(const std::string& language, const std::string& path);
std::vector<std::string> GetAvailableDictionaries(const std::string& path);
std::vector<std::string> GetCorrectionsForMisspelling(const std::string& word);
Expand All @@ -22,7 +23,9 @@ class HunspellSpellchecker : public SpellcheckerImplementation {
void Remove(const std::string& word);

private:
Hunspell* hunspell;
bool isAlpha(std::wint_t c) const;

std::vector<std::pair<std::locale, Hunspell*>> hunspells;
Transcoder *transcoder;
};

Expand Down
3 changes: 2 additions & 1 deletion src/spellchecker_mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ class MacSpellchecker : public SpellcheckerImplementation {
MacSpellchecker();
~MacSpellchecker();

bool AddDictionary(const std::string& language, const std::string& path);
bool SetDictionary(const std::string& language, const std::string& path);
std::vector<std::string> GetAvailableDictionaries(const std::string& path);
std::vector<std::string> GetCorrectionsForMisspelling(const std::string& word);
bool IsMisspelled(const std::string& word);
std::vector<MisspelledRange> CheckSpelling(const uint16_t *text, size_t length);
void Add(const std::string& word);
void Remove(const std::string& word);

private:
NSSpellChecker* spellChecker;
NSString* spellCheckerLanguage;
Expand Down
5 changes: 5 additions & 0 deletions src/spellchecker_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
this->spellCheckerLanguage = nil;
}

bool MacSpellchecker::AddDictionary(const std::string& language, const std::string& path) {
// TODO: write appropriate method
return this->SetDictionary(language, path);
}

bool MacSpellchecker::SetDictionary(const std::string& language, const std::string& path) {
@autoreleasepool {
[this->spellCheckerLanguage release];
Expand Down
Loading