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 <unicode/ubrk.h> |
26 | #include <wtf/Language.h> |
27 | #include <wtf/NeverDestroyed.h> |
28 | #include <wtf/text/CString.h> |
29 | #include <wtf/text/TextBreakIterator.h> |
30 | #include <wtf/text/WTFString.h> |
31 | |
32 | namespace WebCore { |
33 | |
34 | TextCheckerEnchant& TextCheckerEnchant::singleton() |
35 | { |
36 | static NeverDestroyed<TextCheckerEnchant> textChecker; |
37 | return textChecker; |
38 | } |
39 | |
40 | void TextCheckerEnchant::EnchantDictDeleter::operator()(EnchantDict* dictionary) const |
41 | { |
42 | enchant_broker_free_dict(TextCheckerEnchant::singleton().m_broker, dictionary); |
43 | } |
44 | |
45 | TextCheckerEnchant::TextCheckerEnchant() |
46 | : m_broker(enchant_broker_init()) |
47 | { |
48 | } |
49 | |
50 | void TextCheckerEnchant::ignoreWord(const String& word) |
51 | { |
52 | auto utf8Word = word.utf8(); |
53 | for (auto& dictionary : m_enchantDictionaries) |
54 | enchant_dict_add_to_session(dictionary.get(), utf8Word.data(), utf8Word.length()); |
55 | } |
56 | |
57 | void TextCheckerEnchant::learnWord(const String& word) |
58 | { |
59 | auto utf8Word = word.utf8(); |
60 | for (auto& dictionary : m_enchantDictionaries) |
61 | enchant_dict_add(dictionary.get(), utf8Word.data(), utf8Word.length()); |
62 | } |
63 | |
64 | void TextCheckerEnchant::checkSpellingOfWord(const String& word, int start, int end, int& misspellingLocation, int& misspellingLength) |
65 | { |
66 | CString string = word.substring(start, end - start).utf8(); |
67 | |
68 | for (auto& dictionary : m_enchantDictionaries) { |
69 | if (!enchant_dict_check(dictionary.get(), string.data(), string.length())) { |
70 | // Stop checking, this word is ok in at least one dict. |
71 | misspellingLocation = -1; |
72 | misspellingLength = 0; |
73 | return; |
74 | } |
75 | } |
76 | |
77 | misspellingLocation = start; |
78 | misspellingLength = end - start; |
79 | } |
80 | |
81 | void TextCheckerEnchant::checkSpellingOfString(const String& string, int& misspellingLocation, int& misspellingLength) |
82 | { |
83 | // Assume that the words in the string are spelled correctly. |
84 | misspellingLocation = -1; |
85 | misspellingLength = 0; |
86 | |
87 | if (!hasDictionary()) |
88 | return; |
89 | |
90 | UBreakIterator* iter = wordBreakIterator(string); |
91 | if (!iter) |
92 | return; |
93 | |
94 | int start = ubrk_first(iter); |
95 | for (int end = ubrk_next(iter); end != UBRK_DONE; end = ubrk_next(iter)) { |
96 | if (isWordTextBreak(iter)) { |
97 | checkSpellingOfWord(string, start, end, misspellingLocation, misspellingLength); |
98 | // Stop checking the next words If the current word is misspelled, to do not overwrite its misspelled location and length. |
99 | if (misspellingLength) |
100 | return; |
101 | } |
102 | start = end; |
103 | } |
104 | } |
105 | |
106 | Vector<String> TextCheckerEnchant::getGuessesForWord(const String& word) |
107 | { |
108 | if (!hasDictionary()) |
109 | return { }; |
110 | |
111 | static const size_t maximumNumberOfSuggestions = 10; |
112 | |
113 | Vector<String> guesses; |
114 | auto utf8Word = word.utf8(); |
115 | for (auto& dictionary : m_enchantDictionaries) { |
116 | size_t numberOfSuggestions; |
117 | size_t i; |
118 | |
119 | char** suggestions = enchant_dict_suggest(dictionary.get(), utf8Word.data(), utf8Word.length(), &numberOfSuggestions); |
120 | if (numberOfSuggestions <= 0) |
121 | continue; |
122 | |
123 | if (numberOfSuggestions > maximumNumberOfSuggestions) |
124 | numberOfSuggestions = maximumNumberOfSuggestions; |
125 | |
126 | for (i = 0; i < numberOfSuggestions; i++) |
127 | guesses.append(String::fromUTF8(suggestions[i])); |
128 | |
129 | enchant_dict_free_string_list(dictionary.get(), suggestions); |
130 | } |
131 | |
132 | return guesses; |
133 | } |
134 | |
135 | void TextCheckerEnchant::updateSpellCheckingLanguages(const Vector<String>& languages) |
136 | { |
137 | Vector<UniqueEnchantDict> spellDictionaries; |
138 | if (!languages.isEmpty()) { |
139 | for (auto& language : languages) { |
140 | CString currentLanguage = language.utf8(); |
141 | if (enchant_broker_dict_exists(m_broker, currentLanguage.data())) { |
142 | if (auto* dict = enchant_broker_request_dict(m_broker, currentLanguage.data())) |
143 | spellDictionaries.append(dict); |
144 | } |
145 | } |
146 | } else { |
147 | // Languages are not specified by user, try to get default language. |
148 | CString language = defaultLanguage().utf8(); |
149 | if (enchant_broker_dict_exists(m_broker, language.data())) { |
150 | if (auto* dict = enchant_broker_request_dict(m_broker, language.data())) |
151 | spellDictionaries.append(dict); |
152 | } else { |
153 | // No dictionaries selected, we get the first one from the list. |
154 | CString dictLanguage; |
155 | enchant_broker_list_dicts(m_broker, [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) { |
156 | auto* dictLanguage = static_cast<CString*>(data); |
157 | if (dictLanguage->isNull()) |
158 | *dictLanguage = languageTag; |
159 | }, &dictLanguage); |
160 | if (!dictLanguage.isNull()) { |
161 | if (auto* dict = enchant_broker_request_dict(m_broker, dictLanguage.data())) |
162 | spellDictionaries.append(dict); |
163 | } |
164 | } |
165 | } |
166 | m_enchantDictionaries = WTFMove(spellDictionaries); |
167 | } |
168 | |
169 | Vector<String> TextCheckerEnchant::loadedSpellCheckingLanguages() const |
170 | { |
171 | if (!hasDictionary()) |
172 | return { }; |
173 | |
174 | Vector<String> languages; |
175 | for (auto& dictionary : m_enchantDictionaries) { |
176 | enchant_dict_describe(dictionary.get(), [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) { |
177 | auto* languages = static_cast<Vector<String>*>(data); |
178 | languages->append(String::fromUTF8(languageTag)); |
179 | }, &languages); |
180 | } |
181 | return languages; |
182 | } |
183 | |
184 | Vector<String> TextCheckerEnchant::availableSpellCheckingLanguages() const |
185 | { |
186 | Vector<String> languages; |
187 | enchant_broker_list_dicts(m_broker, [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) { |
188 | auto* languages = static_cast<Vector<String>*>(data); |
189 | languages->append(String::fromUTF8(languageTag)); |
190 | }, &languages); |
191 | return languages; |
192 | } |
193 | |
194 | } // namespace WebCore |
195 | |
196 | #endif // ENABLE(SPELLCHECK) |
197 | |
198 | |