1/*
2 *  Copyright (C) 2012 Igalia S.L.
3 *  Copyright (C) 2012 Samsung Electronics
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Lesser General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Lesser General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Lesser General Public
16 *  License along with this library; if not, write to the Free Software
17 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#include "config.h"
21#include "TextCheckerEnchant.h"
22
23#if ENABLE(SPELLCHECK)
24
25#include <Language.h>
26#include <glib.h>
27#include <text/TextBreakIterator.h>
28
29namespace WebCore {
30
31static const size_t maximumNumberOfSuggestions = 10;
32
33static void enchantDictDescribeCallback(const char* const languageTag, const char* const, const char* const, const char* const, void* data)
34{
35    Vector<CString>* dictionaries = static_cast<Vector<CString>*>(data);
36    dictionaries->append(languageTag);
37}
38
39TextCheckerEnchant::TextCheckerEnchant()
40    : m_broker(enchant_broker_init())
41    , m_enchantDictionaries(0)
42{
43}
44
45TextCheckerEnchant::~TextCheckerEnchant()
46{
47    if (!m_broker)
48        return;
49
50    freeEnchantBrokerDictionaries();
51    enchant_broker_free(m_broker);
52}
53
54void TextCheckerEnchant::ignoreWord(const String& word)
55{
56    for (Vector<EnchantDict*>::const_iterator iter = m_enchantDictionaries.begin(); iter != m_enchantDictionaries.end(); ++iter)
57        enchant_dict_add_to_session(*iter, word.utf8().data(), -1);
58}
59
60void TextCheckerEnchant::learnWord(const String& word)
61{
62    for (Vector<EnchantDict*>::const_iterator iter = m_enchantDictionaries.begin(); iter != m_enchantDictionaries.end(); ++iter)
63        enchant_dict_add(*iter, word.utf8().data(), -1);
64}
65
66void TextCheckerEnchant::checkSpellingOfWord(const CString& word, int start, int end, int& misspellingLocation, int& misspellingLength)
67{
68    const char* string = word.data();
69    char* startPtr = g_utf8_offset_to_pointer(string, start);
70    int numberOfBytes = static_cast<int>(g_utf8_offset_to_pointer(string, end) - startPtr);
71
72    for (Vector<EnchantDict*>::const_iterator dictIter = m_enchantDictionaries.begin(); dictIter != m_enchantDictionaries.end(); ++dictIter) {
73        if (!enchant_dict_check(*dictIter, startPtr, numberOfBytes)) {
74            // Stop checking, this word is ok in at least one dict.
75            misspellingLocation = -1;
76            misspellingLength = 0;
77            return;
78        }
79    }
80
81    misspellingLocation = start;
82    misspellingLength = end - start;
83}
84
85void TextCheckerEnchant::checkSpellingOfString(const String& string, int& misspellingLocation, int& misspellingLength)
86{
87    // Assume that the words in the string are spelled correctly.
88    misspellingLocation = -1;
89    misspellingLength = 0;
90
91    if (!hasDictionary())
92        return;
93
94    TextBreakIterator* iter = wordBreakIterator(string.characters(), string.length());
95    if (!iter)
96        return;
97
98    CString utf8String = string.utf8();
99    int start = textBreakFirst(iter);
100    for (int end = textBreakNext(iter); end != TextBreakDone; end = textBreakNext(iter)) {
101        if (isWordTextBreak(iter)) {
102            checkSpellingOfWord(utf8String, start, end, misspellingLocation, misspellingLength);
103            // Stop checking the next words If the current word is misspelled, to do not overwrite its misspelled location and length.
104            if (misspellingLength)
105                return;
106        }
107        start = end;
108    }
109}
110
111Vector<String> TextCheckerEnchant::getGuessesForWord(const String& word)
112{
113    Vector<String> guesses;
114    if (!hasDictionary())
115        return guesses;
116
117    for (Vector<EnchantDict*>::const_iterator iter = m_enchantDictionaries.begin(); iter != m_enchantDictionaries.end(); ++iter) {
118        size_t numberOfSuggestions;
119        size_t i;
120
121        char** suggestions = enchant_dict_suggest(*iter, word.utf8().data(), -1, &numberOfSuggestions);
122        if (numberOfSuggestions <= 0)
123            continue;
124
125        if (numberOfSuggestions > maximumNumberOfSuggestions)
126            numberOfSuggestions = maximumNumberOfSuggestions;
127
128        for (i = 0; i < numberOfSuggestions; i++)
129            guesses.append(String::fromUTF8(suggestions[i]));
130
131        enchant_dict_free_suggestions(*iter, suggestions);
132    }
133
134    return guesses;
135}
136
137void TextCheckerEnchant::updateSpellCheckingLanguages(const Vector<String>& languages)
138{
139    Vector<EnchantDict*> spellDictionaries;
140
141    if (!languages.isEmpty()) {
142        for (Vector<String>::const_iterator iter = languages.begin(); iter != languages.end(); ++iter) {
143            CString currentLanguage = iter->utf8();
144            if (enchant_broker_dict_exists(m_broker, currentLanguage.data())) {
145                EnchantDict* dict = enchant_broker_request_dict(m_broker, currentLanguage.data());
146                spellDictionaries.append(dict);
147            }
148        }
149    } else {
150        // Languages are not specified by user, try to get default language.
151        CString utf8Language = defaultLanguage().utf8();
152        const char* language = utf8Language.data();
153        if (enchant_broker_dict_exists(m_broker, language)) {
154            EnchantDict* dict = enchant_broker_request_dict(m_broker, language);
155            spellDictionaries.append(dict);
156        } else {
157            // No dictionaries selected, we get the first one from the list.
158            Vector<CString> allDictionaries;
159            enchant_broker_list_dicts(m_broker, enchantDictDescribeCallback, &allDictionaries);
160            if (!allDictionaries.isEmpty()) {
161                EnchantDict* dict = enchant_broker_request_dict(m_broker, allDictionaries.first().data());
162                spellDictionaries.append(dict);
163            }
164        }
165    }
166    freeEnchantBrokerDictionaries();
167    m_enchantDictionaries = spellDictionaries;
168}
169
170Vector<String> TextCheckerEnchant::loadedSpellCheckingLanguages() const
171{
172    Vector<String> languages;
173    if (!hasDictionary())
174        return languages;
175
176    // Get a Vector<CString> with the list of languages in use.
177    Vector<CString> currentDictionaries;
178    for (Vector<EnchantDict*>::const_iterator iter = m_enchantDictionaries.begin(); iter != m_enchantDictionaries.end(); ++iter)
179        enchant_dict_describe(*iter, enchantDictDescribeCallback, &currentDictionaries);
180
181    for (Vector<CString>::const_iterator iter = currentDictionaries.begin(); iter != currentDictionaries.end(); ++iter)
182        languages.append(String::fromUTF8(iter->data()));
183
184    return languages;
185}
186
187Vector<String> TextCheckerEnchant::availableSpellCheckingLanguages() const
188{
189    Vector<CString> allDictionaries;
190    enchant_broker_list_dicts(m_broker, enchantDictDescribeCallback, &allDictionaries);
191
192    Vector<String> languages;
193    for (Vector<CString>::const_iterator iter = allDictionaries.begin(); iter != allDictionaries.end(); ++iter)
194        languages.append(String::fromUTF8(iter->data()));
195
196    return languages;
197}
198
199void TextCheckerEnchant::freeEnchantBrokerDictionaries()
200{
201    for (Vector<EnchantDict*>::const_iterator iter = m_enchantDictionaries.begin(); iter != m_enchantDictionaries.end(); ++iter)
202        enchant_broker_free_dict(m_broker, *iter);
203}
204
205} // namespace WebCore
206
207#endif // ENABLE(SPELLCHECK)
208
209