blob: 8cb9dd78c69cb16050efa62a5d9040f6bd5b0015 [file] [log] [blame]
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "app/l10n_util.h"
#include "chrome/browser/spellchecker.h"
#include "chrome/browser/spellchecker_common.h"
#include "chrome/browser/spellchecker_platform_engine.h"
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/histogram.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/stats_counters.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/url_fetcher.h"
#include "chrome/browser/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_counters.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/third_party/hunspell/src/hunspell/hunspell.hxx"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "net/url_request/url_request.h"
using base::TimeTicks;
namespace {
static const struct {
// The language.
const char* language;
// The corresponding language and region, used by the dictionaries.
const char* language_region;
} g_supported_spellchecker_languages[] = {
{"en-US", "en-US"},
{"en-GB", "en-GB"},
{"en-AU", "en-AU"},
{"fr", "fr-FR"},
{"it", "it-IT"},
{"de", "de-DE"},
{"es", "es-ES"},
{"nl", "nl-NL"},
{"pt-BR", "pt-BR"},
{"ru", "ru-RU"},
{"pl", "pl-PL"},
// {"th", "th-TH"}, // Not to be included in Spellchecker as per B=1277824
{"sv", "sv-SE"},
{"da", "da-DK"},
{"pt-PT", "pt-PT"},
{"ro", "ro-RO"},
// {"hu", "hu-HU"}, // Not to be included in Spellchecker as per B=1277824
{"he", "he-IL"},
{"id", "id-ID"},
{"cs", "cs-CZ"},
{"el", "el-GR"},
{"nb", "nb-NO"},
{"vi", "vi-VN"},
// {"bg", "bg-BG"}, // Not to be included in Spellchecker as per B=1277824
{"hr", "hr-HR"},
{"lt", "lt-LT"},
{"sk", "sk-SK"},
{"sl", "sl-SI"},
{"ca", "ca-ES"},
{"lv", "lv-LV"},
// {"uk", "uk-UA"}, // Not to be included in Spellchecker as per B=1277824
{"hi", "hi-IN"},
{"et", "et-EE"},
{"tr", "tr-TR"},
};
// Get the fallback folder (currently chrome::DIR_USER_DATA) where the
// dictionary is downloaded in case of system-wide installations.
FilePath GetFallbackDictionaryDownloadDirectory() {
FilePath dict_dir_userdata;
PathService::Get(chrome::DIR_USER_DATA, &dict_dir_userdata);
dict_dir_userdata = dict_dir_userdata.AppendASCII("Dictionaries");
return dict_dir_userdata;
}
bool SaveBufferToFile(const std::string& data,
FilePath file_to_write) {
int num_bytes = data.length();
return file_util::WriteFile(file_to_write, data.data(), num_bytes) ==
num_bytes;
}
}
// This is a helper class which acts as a proxy for invoking a task from the
// file loop back to the IO loop. Invoking a task from file loop to the IO
// loop directly is not safe as during browser shutdown, the IO loop tears
// down before the file loop. To avoid a crash, this object is invoked in the
// UI loop from the file loop, from where it gets the IO thread directly from
// g_browser_process and invokes the given task in the IO loop if it is not
// NULL. This object also takes ownership of the given task.
class UIProxyForIOTask : public Task {
public:
explicit UIProxyForIOTask(Task* on_dictionary_save_complete_callback_task)
: on_dictionary_save_complete_callback_task_(
on_dictionary_save_complete_callback_task) {
}
private:
void Run();
Task* on_dictionary_save_complete_callback_task_;
DISALLOW_COPY_AND_ASSIGN(UIProxyForIOTask);
};
void UIProxyForIOTask::Run() {
// This has been invoked in the UI thread.
base::Thread* io_thread = g_browser_process->io_thread();
if (io_thread) { // io_thread has not been torn down yet.
MessageLoop* io_loop = io_thread->message_loop();
io_loop->PostTask(FROM_HERE,
on_dictionary_save_complete_callback_task_);
on_dictionary_save_complete_callback_task_ = NULL;
}
}
// Design: The spellchecker initializes hunspell_ in the Initialize() method.
// This is done using the dictionary file on disk, e.g. "en-US_1_1.bdic".
// Initialization of hunspell_ is held off during this process. If the
// dictionaryis not available, we first attempt to download and save it. After
// the dictionary is downloaded and saved to disk (or the attempt to do so
// fails)), corresponding flags are set
// in spellchecker - in the IO thread. Since IO thread goes first during closing
// of browser, a proxy task |UIProxyForIOTask| is created in the UI thread,
// which obtains the IO thread independently and invokes the task in the IO
// thread if it's not NULL. After the flags are cleared, a (final) attempt is
// made to initialize hunspell_. If it fails even then (dictionary could not
// download), no more attempts are made to initialize it.
class SaveDictionaryTask : public Task {
public:
SaveDictionaryTask(Task* on_dictionary_save_complete_callback_task,
const FilePath& first_attempt_file_name,
const FilePath& fallback_file_name,
const std::string& data,
MessageLoop* ui_loop)
: on_dictionary_save_complete_callback_task_(
on_dictionary_save_complete_callback_task),
first_attempt_file_name_(first_attempt_file_name),
fallback_file_name_(fallback_file_name),
data_(data),
ui_loop_(ui_loop) {
}
private:
void Run();
bool SaveBufferToFile(const std::string& data,
FilePath file_to_write) {
int num_bytes = data.length();
return file_util::WriteFile(file_to_write, data.data(), num_bytes) ==
num_bytes;
}
// factory object to invokelater back to spellchecker in io thread on
// download completion to change appropriate flags.
Task* on_dictionary_save_complete_callback_task_;
// The file which will be stored in the first attempt.
FilePath first_attempt_file_name_;
// The file which will be stored as a fallback.
FilePath fallback_file_name_;
// The buffer which has to be stored to disk.
std::string data_;
// This invokes back to io loop when downloading is over.
MessageLoop* ui_loop_;
DISALLOW_COPY_AND_ASSIGN(SaveDictionaryTask);
};
void SaveDictionaryTask::Run() {
if (!SaveBufferToFile(data_, first_attempt_file_name_)) {
// Try saving it to |fallback_file_name_|, which almost surely has
// write permission. If even this fails, there is nothing to be done.
FilePath fallback_dir = fallback_file_name_.DirName();
// Create the directory if it does not exist.
if (!file_util::PathExists(fallback_dir))
file_util::CreateDirectory(fallback_dir);
SaveBufferToFile(data_, fallback_file_name_);
} // Unsuccessful save is taken care of in SpellChecker::Initialize().
// Set Flag that dictionary is not downloading anymore.
ui_loop_->PostTask(FROM_HERE,
new UIProxyForIOTask(on_dictionary_save_complete_callback_task_));
}
void SpellChecker::SpellCheckLanguages(std::vector<std::string>* languages) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
++i)
languages->push_back(g_supported_spellchecker_languages[i].language);
}
// This function returns the language-region version of language name.
// e.g. returns hi-IN for hi.
std::string SpellChecker::GetSpellCheckLanguageRegion(
std::string input_language) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
++i) {
std::string language(
g_supported_spellchecker_languages[i].language);
if (language == input_language)
return std::string(
g_supported_spellchecker_languages[i].language_region);
}
return input_language;
}
std::string SpellChecker::GetLanguageFromLanguageRegion(
std::string input_language) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
++i) {
std::string language(
g_supported_spellchecker_languages[i].language_region);
if (language == input_language)
return std::string(g_supported_spellchecker_languages[i].language);
}
return input_language;
}
std::string SpellChecker::GetCorrespondingSpellCheckLanguage(
const std::string& language) {
// Look for exact match in the Spell Check language list.
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
++i) {
// First look for exact match in the language region of the list.
std::string spellcheck_language(
g_supported_spellchecker_languages[i].language);
if (spellcheck_language == language)
return language;
// Next, look for exact match in the language_region part of the list.
std::string spellcheck_language_region(
g_supported_spellchecker_languages[i].language_region);
if (spellcheck_language_region == language)
return g_supported_spellchecker_languages[i].language;
}
// Look for a match by comparing only language parts. All the 'en-RR'
// except for 'en-GB' exactly matched in the above loop, will match
// 'en-US'. This is not ideal because 'en-ZA', 'en-NZ' had
// better be matched with 'en-GB'. This does not handle cases like
// 'az-Latn-AZ' vs 'az-Arab-AZ', either, but we don't use 3-part
// locale ids with a script code in the middle, yet.
// TODO(jungshik): Add a better fallback.
std::string language_part(language, 0, language.find('-'));
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
++i) {
std::string spellcheck_language(
g_supported_spellchecker_languages[i].language_region);
if (spellcheck_language.substr(0, spellcheck_language.find('-')) ==
language_part)
return spellcheck_language;
}
// No match found - return blank.
return std::string();
}
int SpellChecker::GetSpellCheckLanguages(
Profile* profile,
std::vector<std::string>* languages) {
StringPrefMember accept_languages_pref;
StringPrefMember dictionary_language_pref;
accept_languages_pref.Init(prefs::kAcceptLanguages, profile->GetPrefs(),
NULL);
dictionary_language_pref.Init(prefs::kSpellCheckDictionary,
profile->GetPrefs(), NULL);
std::string dictionary_language =
WideToASCII(dictionary_language_pref.GetValue());
// The current dictionary language should be there.
languages->push_back(dictionary_language);
// Now scan through the list of accept languages, and find possible mappings
// from this list to the existing list of spell check languages.
std::vector<std::string> accept_languages;
if (SpellCheckerPlatform::SpellCheckerAvailable()) {
SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
} else {
SplitString(WideToASCII(accept_languages_pref.GetValue()), ',',
&accept_languages);
}
for (std::vector<std::string>::const_iterator i = accept_languages.begin();
i != accept_languages.end(); ++i) {
std::string language = GetCorrespondingSpellCheckLanguage(*i);
if (!language.empty() &&
std::find(languages->begin(), languages->end(), language) ==
languages->end())
languages->push_back(language);
}
for (size_t i = 0; i < languages->size(); ++i) {
if ((*languages)[i] == dictionary_language)
return i;
}
return -1;
}
FilePath SpellChecker::GetVersionedFileName(const std::string& input_language,
const FilePath& dict_dir) {
// The default dictionary version is 1-2. These versions have been augmented
// with additional words found by the translation team.
static const char kDefaultVersionString[] = "-1-2";
// The following dictionaries have either not been augmented with additional
// words (version 1-1) or have new words, as well as an upgraded dictionary
// as of Feb 2009 (version 1-3).
static const struct {
// The language input.
const char* language;
// The corresponding version.
const char* version;
} special_version_string[] = {
{"en-AU", "-1-1"},
{"en-GB", "-1-1"},
{"es-ES", "-1-1"},
{"nl-NL", "-1-1"},
{"ru-RU", "-1-1"},
{"sv-SE", "-1-1"},
{"he-IL", "-1-1"},
{"el-GR", "-1-1"},
{"hi-IN", "-1-1"},
{"tr-TR", "-1-1"},
{"et-EE", "-1-1"},
{"fr-FR", "-1-4"}, // to fix crash, fr dictionary was updated to 1.4
{"lt-LT", "-1-3"},
{"pl-PL", "-1-3"}
};
// Generate the bdict file name using default version string or special
// version string, depending on the language.
std::string language = GetSpellCheckLanguageRegion(input_language);
std::string versioned_bdict_file_name(language + kDefaultVersionString +
".bdic");
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(special_version_string); ++i) {
if (language == special_version_string[i].language) {
versioned_bdict_file_name =
language + special_version_string[i].version + ".bdic";
break;
}
}
return dict_dir.AppendASCII(versioned_bdict_file_name);
}
SpellChecker::SpellChecker(const FilePath& dict_dir,
const std::string& language,
URLRequestContext* request_context,
const FilePath& custom_dictionary_file_name)
: given_dictionary_directory_(dict_dir),
custom_dictionary_file_name_(custom_dictionary_file_name),
tried_to_init_(false),
language_(language),
#ifndef NDEBUG
worker_loop_(NULL),
#endif
tried_to_download_dictionary_file_(false),
file_loop_(NULL),
ui_loop_(MessageLoop::current()),
url_request_context_(request_context),
obtaining_dictionary_(false),
auto_spell_correct_turned_on_(false),
is_using_platform_spelling_engine_(false),
fetcher_(NULL),
ALLOW_THIS_IN_INITIALIZER_LIST(
on_dictionary_save_complete_callback_factory_(this)) {
if (SpellCheckerPlatform::SpellCheckerAvailable()) {
SpellCheckerPlatform::Init();
if (SpellCheckerPlatform::PlatformSupportsLanguage(language)) {
// If we have reached here, then we know that the current platform
// supports the given language and we will use it instead of hunspell.
SpellCheckerPlatform::SetLanguage(language);
is_using_platform_spelling_engine_ = true;
}
}
// Get the corresponding BDIC file name.
bdic_file_name_ = GetVersionedFileName(language, dict_dir).BaseName();
// Get File Loop - hunspell gets initialized here.
base::Thread* file_thread = g_browser_process->file_thread();
if (file_thread)
file_loop_ = file_thread->message_loop();
// Get the path to the custom dictionary file.
if (custom_dictionary_file_name_.empty()) {
FilePath personal_file_directory;
PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
custom_dictionary_file_name_ =
personal_file_directory.Append(chrome::kCustomDictionaryFileName);
}
// Use this dictionary language as the default one of the
// SpellcheckCharAttribute object.
character_attributes_.SetDefaultLanguage(language);
}
SpellChecker::~SpellChecker() {
#ifndef NDEBUG
// This must be deleted on the I/O thread (see the header). This is the same
// thread thatSpellCheckWord is called on, so we verify that they were all the
// same thread.
if (worker_loop_)
DCHECK(MessageLoop::current() == worker_loop_);
#endif
}
void SpellChecker::StartDictionaryDownload(const FilePath& file_name) {
// Determine URL of file to download.
static const char kDownloadServerUrl[] =
"http://cache.pack.google.com/edgedl/chrome/dict/";
GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8(
l10n_util::ToLower(bdic_file_name_.ToWStringHack())));
fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
fetcher_->set_request_context(url_request_context_);
obtaining_dictionary_ = true;
fetcher_->Start();
}
void SpellChecker::OnURLFetchComplete(const URLFetcher* source,
const GURL& url,
const URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data) {
DCHECK(source);
if (!((response_code / 100) == 2 ||
response_code == 401 ||
response_code == 407)) {
obtaining_dictionary_ = false;
return;
}
// Save the file in the file thread, and not here, the IO thread.
FilePath first_attempt_file_name = given_dictionary_directory_.Append(
bdic_file_name_);
FilePath user_data_dir = GetFallbackDictionaryDownloadDirectory();
FilePath fallback_file_name = user_data_dir.Append(bdic_file_name_);
Task* dic_task = on_dictionary_save_complete_callback_factory_.
NewRunnableMethod(&SpellChecker::OnDictionarySaveComplete);
file_loop_->PostTask(FROM_HERE, new SaveDictionaryTask(dic_task,
first_attempt_file_name, fallback_file_name, data, ui_loop_));
}
// Initialize SpellChecker. In this method, if the dictionary is not present
// in the local disk, it is fetched asynchronously.
// TODO(sidchat): After dictionary is downloaded, initialize hunspell in
// file loop - this is currently being done in the io loop.
// Bug: http://b/issue?id=1123096
bool SpellChecker::Initialize() {
// Return false if the dictionary files are downloading.
if (obtaining_dictionary_)
return false;
// Return false if tried to init and failed - don't try multiple times in
// this session.
if (tried_to_init_)
return hunspell_.get() != NULL;
StatsScope<StatsCounterTimer> timer(chrome::Counters::spellcheck_init());
// The default place whether the spellcheck dictionary can reside is
// chrome::DIR_APP_DICTIONARIES. However, for systemwide installations,
// this directory may not have permissions for download. In that case, the
// alternate directory for download is chrome::DIR_USER_DATA. We have to check
// for the spellcheck dictionaries in both the directories. If not found in
// either one, it has to be downloaded in either of the two.
// TODO(sidchat): Some sort of UI to warn users that spellchecker is not
// working at all (due to failed dictionary download)?
// File name for downloading in DIR_APP_DICTIONARIES.
FilePath dictionary_file_name_app = GetVersionedFileName(language_,
given_dictionary_directory_);
// Filename for downloading in the fallback dictionary download directory,
// DIR_USER_DATA.
FilePath dict_dir_userdata = GetFallbackDictionaryDownloadDirectory();
FilePath dictionary_file_name_usr = GetVersionedFileName(language_,
dict_dir_userdata);
// Check in both the directories to see whether the spellcheck dictionary
// already resides in one of these.
FilePath bdic_file_name;
if (file_util::PathExists(dictionary_file_name_app)) {
bdic_file_name = dictionary_file_name_app;
} else if (file_util::PathExists(dictionary_file_name_usr)) {
bdic_file_name = dictionary_file_name_usr;
} else {
// Download the dictionary file.
if (file_loop_ && url_request_context_) {
if (!tried_to_download_dictionary_file_) {
StartDictionaryDownload(dictionary_file_name_app);
tried_to_download_dictionary_file_ = true;
return false;
} else { // There is no dictionary even after trying to download it.
// Stop trying to download the dictionary in this session.
tried_to_init_ = true;
return false;
}
}
}
// Control has come so far - the BDIC dictionary file probably exists. Now try
// to initialize hunspell using the available bdic dictionary file.
TimeTicks begin_time = TimeTicks::Now();
bdict_file_.reset(new file_util::MemoryMappedFile());
if (bdict_file_->Initialize(bdic_file_name)) {
hunspell_.reset(new Hunspell(bdict_file_->data(), bdict_file_->length()));
AddCustomWordsToHunspell();
}
DHISTOGRAM_TIMES("Spellcheck.InitTime", TimeTicks::Now() - begin_time);
tried_to_init_ = true;
return false;
}
void SpellChecker::GetAutoCorrectionWord(const std::wstring& word,
std::wstring* autocorrect_word) {
autocorrect_word->clear();
if (!auto_spell_correct_turned_on_)
return;
int word_length = static_cast<int>(word.size());
if (word_length < 2 || word_length > kMaxAutoCorrectWordSize)
return;
wchar_t misspelled_word[kMaxAutoCorrectWordSize + 1];
const wchar_t* word_char = word.c_str();
for (int i = 0; i <= kMaxAutoCorrectWordSize; i++) {
if (i >= word_length)
misspelled_word[i] = NULL;
else
misspelled_word[i] = word_char[i];
}
// Swap adjacent characters and spellcheck.
int misspelling_start, misspelling_len;
for (int i = 0; i < word_length - 1; i++) {
// Swap.
std::swap(misspelled_word[i], misspelled_word[i + 1]);
// Check spelling.
misspelling_start = misspelling_len = 0;
SpellCheckWord(misspelled_word, word_length, &misspelling_start,
&misspelling_len, NULL);
// Make decision: if only one swap produced a valid word, then we want to
// return it. If we found two or more, we don't do autocorrection.
if (misspelling_len == 0) {
if (autocorrect_word->empty()) {
autocorrect_word->assign(misspelled_word);
} else {
autocorrect_word->clear();
return;
}
}
// Restore the swapped characters.
std::swap(misspelled_word[i], misspelled_word[i + 1]);
}
}
void SpellChecker::EnableAutoSpellCorrect(bool turn_on) {
auto_spell_correct_turned_on_ = turn_on;
}
void SpellChecker::AddCustomWordsToHunspell() {
// Add custom words to Hunspell.
// This should be done in File Loop, but since Hunspell is in this IO Loop,
// this too has to be initialized here.
// TODO(sidchat): Work out a way to initialize Hunspell in the File Loop.
std::string contents;
file_util::ReadFileToString(custom_dictionary_file_name_, &contents);
std::vector<std::string> list_of_words;
SplitString(contents, '\n', &list_of_words);
if (hunspell_.get()) {
for (std::vector<std::string>::iterator it = list_of_words.begin();
it != list_of_words.end(); ++it) {
hunspell_->put_word(it->c_str());
}
}
}
// Returns whether or not the given string is a valid contraction.
// This function is a fall-back when the SpellcheckWordIterator class
// returns a concatenated word which is not in the selected dictionary
// (e.g. "in'n'out") but each word is valid.
bool SpellChecker::IsValidContraction(const string16& contraction) {
SpellcheckWordIterator word_iterator;
word_iterator.Initialize(&character_attributes_, contraction.c_str(),
contraction.length(), false);
string16 word;
int word_start;
int word_length;
while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
if (!CheckSpelling(UTF16ToUTF8(word)))
return false;
}
return true;
}
bool SpellChecker::SpellCheckWord(
const wchar_t* in_word,
int in_word_len,
int* misspelling_start,
int* misspelling_len,
std::vector<std::wstring>* optional_suggestions) {
DCHECK(in_word_len >= 0);
DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";
#ifndef NDEBUG
// This must always be called on the same thread (normally the I/O thread).
if (worker_loop_)
DCHECK(MessageLoop::current() == worker_loop_);
else
worker_loop_ = MessageLoop::current();
#endif
// Check if the platform spellchecker is being used.
if (!is_using_platform_spelling_engine_) {
// If it isn't, try and init hunspell.
Initialize();
// Check to see if hunspell was successful.
if (!hunspell_.get())
return true; // Unable to spellcheck, return word is OK.
}
StatsScope<StatsRate> timer(chrome::Counters::spellcheck_lookup());
*misspelling_start = 0;
*misspelling_len = 0;
if (in_word_len == 0)
return true; // No input means always spelled correctly.
SpellcheckWordIterator word_iterator;
string16 word;
string16 in_word_utf16;
WideToUTF16(in_word, in_word_len, &in_word_utf16);
int word_start;
int word_length;
word_iterator.Initialize(&character_attributes_, in_word_utf16.c_str(),
in_word_len, true);
while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
// Found a word (or a contraction) that the spellchecker can check the
// spelling of.
std::string encoded_word = UTF16ToUTF8(word);
bool word_ok = CheckSpelling(encoded_word);
if (word_ok)
continue;
// If the given word is a concatenated word of two or more valid words
// (e.g. "hello:hello"), we should treat it as a valid word.
if (IsValidContraction(word))
continue;
*misspelling_start = word_start;
*misspelling_len = word_length;
// Get the list of suggested words.
if (optional_suggestions) {
FillSuggestionList(encoded_word, optional_suggestions);
}
return false;
}
return true;
}
// This task is called in the file loop to write the new word to the custom
// dictionary in disc.
class AddWordToCustomDictionaryTask : public Task {
public:
AddWordToCustomDictionaryTask(const FilePath& file_name,
const std::wstring& word)
: file_name_(file_name),
word_(WideToUTF8(word)) {
}
private:
void Run();
FilePath file_name_;
std::string word_;
};
void AddWordToCustomDictionaryTask::Run() {
// Add the word with a new line. Note that, although this would mean an
// extra line after the list of words, this is potentially harmless and
// faster, compared to verifying everytime whether to append a new line
// or not.
word_ += "\n";
FILE* f = file_util::OpenFile(file_name_, "a+");
if (f != NULL)
fputs(word_.c_str(), f);
file_util::CloseFile(f);
}
void SpellChecker::AddWord(const std::wstring& word) {
if (is_using_platform_spelling_engine_) {
SpellCheckerPlatform::AddWord(word);
return;
}
// Check if the |hunspell_| has been initialized at all.
Initialize();
// Add the word to hunspell.
std::string word_to_add = WideToUTF8(word);
if (!word_to_add.empty())
hunspell_->put_word(word_to_add.c_str());
// Now add the word to the custom dictionary file.
Task* write_word_task =
new AddWordToCustomDictionaryTask(custom_dictionary_file_name_, word);
if (file_loop_)
file_loop_->PostTask(FROM_HERE, write_word_task);
else
write_word_task->Run();
}
bool SpellChecker::CheckSpelling(const std::string& word_to_check) {
bool word_correct = false;
TimeTicks begin_time = TimeTicks::Now();
if (is_using_platform_spelling_engine_) {
word_correct = SpellCheckerPlatform::CheckSpelling(word_to_check);
} else {
// |hunspell_->spell| returns 0 if the word is spelled correctly and
// non-zero otherwsie.
word_correct = (hunspell_->spell(word_to_check.c_str()) != 0);
}
DHISTOGRAM_TIMES("Spellcheck.CheckTime", TimeTicks::Now() - begin_time);
return word_correct;
}
void SpellChecker::FillSuggestionList(const std::string& wrong_word,
std::vector<std::wstring>* optional_suggestions) {
if (is_using_platform_spelling_engine_) {
SpellCheckerPlatform::FillSuggestionList(wrong_word, optional_suggestions);
return;
}
char** suggestions;
TimeTicks begin_time = TimeTicks::Now();
int number_of_suggestions = hunspell_->suggest(&suggestions,
wrong_word.c_str());
DHISTOGRAM_TIMES("Spellcheck.SuggestTime",
TimeTicks::Now() - begin_time);
// Populate the vector of WideStrings.
for (int i = 0; i < number_of_suggestions; i++) {
if (i < kMaxSuggestions)
optional_suggestions->push_back(UTF8ToWide(suggestions[i]));
free(suggestions[i]);
}
if (suggestions != NULL)
free(suggestions);
}