/src/libreoffice/lingucomponent/source/spellcheck/spell/sspellimp.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <com/sun/star/uno/Reference.h> |
21 | | |
22 | | #include <com/sun/star/linguistic2/SpellFailure.hpp> |
23 | | #include <com/sun/star/linguistic2/XLinguProperties.hpp> |
24 | | #include <comphelper/lok.hxx> |
25 | | #include <comphelper/processfactory.hxx> |
26 | | #include <cppuhelper/supportsservice.hxx> |
27 | | #include <cppuhelper/weak.hxx> |
28 | | #include <com/sun/star/lang/XMultiServiceFactory.hpp> |
29 | | #include <tools/debug.hxx> |
30 | | #include <osl/mutex.hxx> |
31 | | #include <osl/thread.h> |
32 | | #include <com/sun/star/ucb/XSimpleFileAccess.hpp> |
33 | | |
34 | | #include <lingutil.hxx> |
35 | | #include <hunspell.hxx> |
36 | | #include "sspellimp.hxx" |
37 | | |
38 | | #include <linguistic/misc.hxx> |
39 | | #include <linguistic/spelldta.hxx> |
40 | | #include <i18nlangtag/languagetag.hxx> |
41 | | #include <svtools/strings.hrc> |
42 | | #include <unotools/lingucfg.hxx> |
43 | | #include <unotools/resmgr.hxx> |
44 | | #include <osl/diagnose.h> |
45 | | #include <osl/file.hxx> |
46 | | #include <rtl/ustrbuf.hxx> |
47 | | #include <rtl/textenc.h> |
48 | | #include <sal/log.hxx> |
49 | | |
50 | | #include <numeric> |
51 | | #include <utility> |
52 | | #include <vector> |
53 | | #include <set> |
54 | | #include <string.h> |
55 | | |
56 | | using namespace osl; |
57 | | using namespace com::sun::star; |
58 | | using namespace com::sun::star::beans; |
59 | | using namespace com::sun::star::lang; |
60 | | using namespace com::sun::star::uno; |
61 | | using namespace com::sun::star::linguistic2; |
62 | | using namespace linguistic; |
63 | | |
64 | | // XML-header of SPELLML queries |
65 | | #if !defined SPELL_XML |
66 | | constexpr OUStringLiteral SPELL_XML = u"<?xml?>"; |
67 | | #endif |
68 | | |
69 | | // only available in hunspell >= 1.5 |
70 | | #if !defined MAXWORDLEN |
71 | | #define MAXWORDLEN 176 |
72 | | #endif |
73 | | |
74 | | SpellChecker::SpellChecker() : |
75 | 0 | m_aEvtListeners(GetLinguMutex()), |
76 | 0 | m_bDisposing(false) |
77 | 0 | { |
78 | 0 | } |
79 | | |
80 | | SpellChecker::DictItem::DictItem(OUString i_DName, Locale i_DLoc, rtl_TextEncoding i_DEnc) |
81 | 0 | : m_aDName(std::move(i_DName)) |
82 | 0 | , m_aDLoc(std::move(i_DLoc)) |
83 | 0 | , m_aDEnc(i_DEnc) |
84 | 0 | { |
85 | 0 | } |
86 | | |
87 | | SpellChecker::~SpellChecker() |
88 | 0 | { |
89 | 0 | if (m_pPropHelper) |
90 | 0 | { |
91 | 0 | m_pPropHelper->RemoveAsPropListener(); |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | PropertyHelper_Spelling & SpellChecker::GetPropHelper_Impl() |
96 | 0 | { |
97 | 0 | if (!m_pPropHelper) |
98 | 0 | { |
99 | 0 | Reference< XLinguProperties > xPropSet = GetLinguProperties(); |
100 | |
|
101 | 0 | m_pPropHelper.reset( new PropertyHelper_Spelling( static_cast<XSpellChecker *>(this), xPropSet ) ); |
102 | 0 | m_pPropHelper->AddAsPropListener(); //! after a reference is established |
103 | 0 | } |
104 | 0 | return *m_pPropHelper; |
105 | 0 | } |
106 | | |
107 | | Sequence< Locale > SAL_CALL SpellChecker::getLocales() |
108 | 0 | { |
109 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
110 | | |
111 | | // this routine should return the locales supported by the installed |
112 | | // dictionaries. |
113 | 0 | if (m_DictItems.empty()) |
114 | 0 | { |
115 | 0 | SvtLinguConfig aLinguCfg; |
116 | | |
117 | | // get list of extension dictionaries-to-use |
118 | | // (or better speaking: the list of dictionaries using the |
119 | | // new configuration entries). |
120 | 0 | std::vector< SvtLinguConfigDictionaryEntry > aDics; |
121 | 0 | uno::Sequence< OUString > aFormatList; |
122 | 0 | aLinguCfg.GetSupportedDictionaryFormatsFor( u"SpellCheckers"_ustr, |
123 | 0 | u"org.openoffice.lingu.MySpellSpellChecker"_ustr, aFormatList ); |
124 | 0 | for (auto const& format : aFormatList) |
125 | 0 | { |
126 | 0 | std::vector< SvtLinguConfigDictionaryEntry > aTmpDic( |
127 | 0 | aLinguCfg.GetActiveDictionariesByFormat(format) ); |
128 | 0 | aDics.insert( aDics.end(), aTmpDic.begin(), aTmpDic.end() ); |
129 | 0 | } |
130 | | |
131 | | //!! for compatibility with old dictionaries (the ones not using extensions |
132 | | //!! or new configuration entries, but still using the dictionary.lst file) |
133 | | //!! Get the list of old style spell checking dictionaries to use... |
134 | 0 | std::vector< SvtLinguConfigDictionaryEntry > aOldStyleDics( |
135 | 0 | GetOldStyleDics( "DICT" ) ); |
136 | | |
137 | | // to prefer dictionaries with configuration entries we will only |
138 | | // use those old style dictionaries that add a language that |
139 | | // is not yet supported by the list of new style dictionaries |
140 | 0 | MergeNewStyleDicsAndOldStyleDics( aDics, aOldStyleDics ); |
141 | |
|
142 | 0 | if (!aDics.empty()) |
143 | 0 | { |
144 | 0 | uno::Reference< lang::XMultiServiceFactory > xServiceFactory(comphelper::getProcessServiceFactory()); |
145 | 0 | uno::Reference< ucb::XSimpleFileAccess > xAccess(xServiceFactory->createInstance(u"com.sun.star.ucb.SimpleFileAccess"_ustr), uno::UNO_QUERY); |
146 | | // get supported locales from the dictionaries-to-use... |
147 | 0 | std::set<OUString> aLocaleNamesSet; |
148 | 0 | for (auto const& dict : aDics) |
149 | 0 | { |
150 | 0 | const uno::Sequence< OUString > aLocaleNames( dict.aLocaleNames ); |
151 | 0 | uno::Sequence< OUString > aLocations( dict.aLocations ); |
152 | 0 | SAL_WARN_IF( |
153 | 0 | aLocaleNames.hasElements() && !aLocations.hasElements(), |
154 | 0 | "lingucomponent", "no locations"); |
155 | 0 | if (aLocations.hasElements()) |
156 | 0 | { |
157 | 0 | if (xAccess.is() && xAccess->exists(aLocations[0])) |
158 | 0 | { |
159 | 0 | for (auto const& locale : aLocaleNames) |
160 | 0 | { |
161 | 0 | if (!comphelper::LibreOfficeKit::isAllowlistedLanguage(locale)) |
162 | 0 | continue; |
163 | | |
164 | 0 | aLocaleNamesSet.insert(locale); |
165 | 0 | } |
166 | 0 | } |
167 | 0 | else |
168 | 0 | { |
169 | 0 | SAL_WARN( |
170 | 0 | "lingucomponent", |
171 | 0 | "missing <" << aLocations[0] << ">"); |
172 | 0 | } |
173 | 0 | } |
174 | 0 | } |
175 | | // ... and add them to the resulting sequence |
176 | 0 | m_aSuppLocales.realloc( aLocaleNamesSet.size() ); |
177 | 0 | std::transform( |
178 | 0 | aLocaleNamesSet.begin(), aLocaleNamesSet.end(), m_aSuppLocales.getArray(), |
179 | 0 | [](auto const& localeName) { return LanguageTag::convertToLocale(localeName); }); |
180 | | |
181 | | //! For each dictionary and each locale we need a separate entry. |
182 | | //! If this results in more than one dictionary per locale than (for now) |
183 | | //! it is undefined which dictionary gets used. |
184 | | //! In the future the implementation should support using several dictionaries |
185 | | //! for one locale. |
186 | 0 | sal_uInt32 nDictSize = std::accumulate(aDics.begin(), aDics.end(), sal_uInt32(0), |
187 | 0 | [](const sal_uInt32 nSum, const SvtLinguConfigDictionaryEntry& dict) { |
188 | 0 | return nSum + dict.aLocaleNames.getLength(); }); |
189 | | |
190 | | // add dictionary information |
191 | 0 | m_DictItems.reserve(nDictSize); |
192 | 0 | for (auto const& dict : aDics) |
193 | 0 | { |
194 | 0 | if (dict.aLocaleNames.hasElements() && |
195 | 0 | dict.aLocations.hasElements()) |
196 | 0 | { |
197 | 0 | const uno::Sequence< OUString > aLocaleNames( dict.aLocaleNames ); |
198 | | |
199 | | // currently only one language per dictionary is supported in the actual implementation... |
200 | | // Thus here we work-around this by adding the same dictionary several times. |
201 | | // Once for each of its supported locales. |
202 | 0 | for (auto const& localeName : aLocaleNames) |
203 | 0 | { |
204 | | // also both files have to be in the same directory and the |
205 | | // file names must only differ in the extension (.aff/.dic). |
206 | | // Thus we use the first location only and strip the extension part. |
207 | 0 | OUString aLocation = dict.aLocations[0]; |
208 | 0 | sal_Int32 nPos = aLocation.lastIndexOf( '.' ); |
209 | 0 | aLocation = aLocation.copy( 0, nPos ); |
210 | |
|
211 | 0 | m_DictItems.emplace_back(aLocation, LanguageTag::convertToLocale(localeName), RTL_TEXTENCODING_DONTKNOW); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | 0 | DBG_ASSERT( nDictSize == m_DictItems.size(), "index mismatch?" ); |
216 | 0 | } |
217 | 0 | else |
218 | 0 | { |
219 | | // no dictionary found so register no dictionaries |
220 | 0 | m_aSuppLocales.realloc(0); |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | 0 | return m_aSuppLocales; |
225 | 0 | } |
226 | | |
227 | | sal_Bool SAL_CALL SpellChecker::hasLocale(const Locale& rLocale) |
228 | 0 | { |
229 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
230 | |
|
231 | 0 | bool bRes = false; |
232 | 0 | if (!m_aSuppLocales.hasElements()) |
233 | 0 | getLocales(); |
234 | |
|
235 | 0 | for (auto const& suppLocale : m_aSuppLocales) |
236 | 0 | { |
237 | 0 | if (rLocale == suppLocale) |
238 | 0 | { |
239 | 0 | bRes = true; |
240 | 0 | break; |
241 | 0 | } |
242 | 0 | } |
243 | 0 | return bRes; |
244 | 0 | } |
245 | | |
246 | 0 | #define SPELL_NON_ASCII_APOSTROPHE 1 << 10 |
247 | | sal_Int16 SpellChecker::GetSpellFailure(const OUString &rWord, const Locale &rLocale, int& rInfo) |
248 | 0 | { |
249 | 0 | if (rWord.getLength() > MAXWORDLEN) |
250 | 0 | return -1; |
251 | | |
252 | 0 | Hunspell * pMS = nullptr; |
253 | 0 | rtl_TextEncoding eEnc = RTL_TEXTENCODING_DONTKNOW; |
254 | | |
255 | | // initialize a myspell object for each dictionary once |
256 | | // (note: mutex is held higher up in isValid) |
257 | |
|
258 | 0 | sal_Int16 nRes = -1; |
259 | | |
260 | | // first handle smart quotes both single and double |
261 | 0 | OUStringBuffer rBuf(rWord); |
262 | 0 | sal_Int32 n = rBuf.getLength(); |
263 | 0 | sal_Unicode c; |
264 | 0 | sal_Int32 extrachar = 0; |
265 | 0 | const bool bDoNotConvertApostrophe = bool(rInfo & SPELL_NON_ASCII_APOSTROPHE); |
266 | 0 | bool bHasNonASCIIApostrophe = false; |
267 | 0 | rInfo = 0; |
268 | 0 | for (sal_Int32 ix=0; ix < n; ix++) |
269 | 0 | { |
270 | 0 | c = rBuf[ix]; |
271 | 0 | if ((c == 0x201C) || (c == 0x201D)) |
272 | 0 | rBuf[ix] = u'"'; |
273 | 0 | else if (!bDoNotConvertApostrophe && ((c == 0x2018) || (c == 0x2019))) |
274 | 0 | { |
275 | 0 | rBuf[ix] = u'\''; |
276 | 0 | bHasNonASCIIApostrophe = true; |
277 | 0 | } |
278 | | // recognize words with Unicode ligatures and ZWNJ/ZWJ characters (only |
279 | | // with 8-bit encoded dictionaries. For UTF-8 encoded dictionaries |
280 | | // set ICONV and IGNORE aff file options, if needed.) |
281 | 0 | else if ((c == 0x200C) || (c == 0x200D) || |
282 | 0 | ((c >= 0xFB00) && (c <= 0xFB04))) |
283 | 0 | extrachar = 1; |
284 | 0 | } |
285 | 0 | OUString nWord(rBuf.makeStringAndClear()); |
286 | |
|
287 | 0 | if (n) |
288 | 0 | { |
289 | 0 | for (auto& currDict : m_DictItems) |
290 | 0 | { |
291 | 0 | pMS = nullptr; |
292 | 0 | eEnc = RTL_TEXTENCODING_DONTKNOW; |
293 | |
|
294 | 0 | if (rLocale == currDict.m_aDLoc) |
295 | 0 | { |
296 | 0 | if (!currDict.m_pDict) |
297 | 0 | { |
298 | 0 | OUString dicpath = currDict.m_aDName + ".dic"; |
299 | 0 | OUString affpath = currDict.m_aDName + ".aff"; |
300 | 0 | OUString dict; |
301 | 0 | OUString aff; |
302 | 0 | osl::FileBase::getSystemPathFromFileURL(dicpath,dict); |
303 | 0 | osl::FileBase::getSystemPathFromFileURL(affpath,aff); |
304 | | #if defined(_WIN32) |
305 | | // workaround for Windows specific problem that the |
306 | | // path length in calls to 'fopen' is limited to somewhat |
307 | | // about 120+ characters which will usually be exceed when |
308 | | // using dictionaries as extensions. (Hunspell waits UTF-8 encoded |
309 | | // path with \\?\ long path prefix.) |
310 | | OString aTmpaff = Win_AddLongPathPrefix(OUStringToOString(aff, RTL_TEXTENCODING_UTF8)); |
311 | | OString aTmpdict = Win_AddLongPathPrefix(OUStringToOString(dict, RTL_TEXTENCODING_UTF8)); |
312 | | #else |
313 | 0 | OString aTmpaff(OU2ENC(aff,osl_getThreadTextEncoding())); |
314 | 0 | OString aTmpdict(OU2ENC(dict,osl_getThreadTextEncoding())); |
315 | 0 | #endif |
316 | |
|
317 | 0 | currDict.m_pDict = std::make_unique<Hunspell>(aTmpaff.getStr(),aTmpdict.getStr()); |
318 | 0 | #if defined(H_DEPRECATED) |
319 | 0 | currDict.m_aDEnc = getTextEncodingFromCharset(currDict.m_pDict->get_dict_encoding().c_str()); |
320 | | #else |
321 | | currDict.m_aDEnc = getTextEncodingFromCharset(currDict.m_pDict->get_dic_encoding()); |
322 | | #endif |
323 | 0 | } |
324 | 0 | pMS = currDict.m_pDict.get(); |
325 | 0 | eEnc = currDict.m_aDEnc; |
326 | 0 | } |
327 | |
|
328 | 0 | if (pMS) |
329 | 0 | { |
330 | | // we don't want to work with a default text encoding since following incorrect |
331 | | // results may occur only for specific text and thus may be hard to notice. |
332 | | // Thus better always make a clean exit here if the text encoding is in question. |
333 | | // Hopefully something not working at all will raise proper attention quickly. ;-) |
334 | 0 | DBG_ASSERT( eEnc != RTL_TEXTENCODING_DONTKNOW, "failed to get text encoding! (maybe incorrect encoding string in file)" ); |
335 | 0 | if (eEnc == RTL_TEXTENCODING_DONTKNOW) |
336 | 0 | return -1; |
337 | | |
338 | 0 | OString aWrd(OU2ENC(nWord,eEnc)); |
339 | 0 | #if defined(H_DEPRECATED) |
340 | 0 | bool bVal = pMS->spell(std::string(aWrd), &rInfo); |
341 | | #else |
342 | | bool bVal = pMS->spell(aWrd.getStr(), &rInfo) != 0; |
343 | | #endif |
344 | 0 | if (!bVal) { |
345 | 0 | if (extrachar && (eEnc != RTL_TEXTENCODING_UTF8)) { |
346 | 0 | OUStringBuffer aBuf(nWord); |
347 | 0 | n = aBuf.getLength(); |
348 | 0 | for (sal_Int32 ix=n-1; ix >= 0; ix--) |
349 | 0 | { |
350 | 0 | switch (aBuf[ix]) { |
351 | 0 | case 0xFB00: aBuf.remove(ix, 1); aBuf.insert(ix, "ff"); break; |
352 | 0 | case 0xFB01: aBuf.remove(ix, 1); aBuf.insert(ix, "fi"); break; |
353 | 0 | case 0xFB02: aBuf.remove(ix, 1); aBuf.insert(ix, "fl"); break; |
354 | 0 | case 0xFB03: aBuf.remove(ix, 1); aBuf.insert(ix, "ffi"); break; |
355 | 0 | case 0xFB04: aBuf.remove(ix, 1); aBuf.insert(ix, "ffl"); break; |
356 | 0 | case 0x200C: |
357 | 0 | case 0x200D: aBuf.remove(ix, 1); break; |
358 | 0 | } |
359 | 0 | } |
360 | 0 | OUString aWord(aBuf.makeStringAndClear()); |
361 | 0 | OString bWrd(OU2ENC(aWord, eEnc)); |
362 | 0 | #if defined(H_DEPRECATED) |
363 | 0 | bVal = pMS->spell(std::string(bWrd), &rInfo); |
364 | | #else |
365 | | bVal = pMS->spell(bWrd.getStr(), &rInfo) != 0; |
366 | | #endif |
367 | 0 | if (bVal) return -1; |
368 | 0 | } |
369 | 0 | nRes = SpellFailure::SPELLING_ERROR; |
370 | 0 | } else { |
371 | 0 | return -1; |
372 | 0 | } |
373 | 0 | pMS = nullptr; |
374 | 0 | } |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | | // checked with apostrophe conversion |
379 | 0 | if ( bHasNonASCIIApostrophe ) |
380 | 0 | rInfo |= SPELL_NON_ASCII_APOSTROPHE; |
381 | |
|
382 | 0 | return nRes; |
383 | 0 | } |
384 | | |
385 | | sal_Bool SAL_CALL SpellChecker::isValid( const OUString& rWord, const Locale& rLocale, |
386 | | const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) |
387 | 0 | { |
388 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
389 | |
|
390 | 0 | if (rLocale == Locale() || rWord.isEmpty()) |
391 | 0 | return true; |
392 | | |
393 | 0 | if (!hasLocale( rLocale )) |
394 | 0 | return true; |
395 | | |
396 | | // return sal_False to process SPELLML requests (they are longer than the header) |
397 | 0 | if (rWord.match(SPELL_XML, 0) && (rWord.getLength() > 10)) return false; |
398 | | |
399 | | // Get property values to be used. |
400 | | // These are be the default values set in the SN_LINGU_PROPERTIES |
401 | | // PropertySet which are overridden by the supplied ones from the |
402 | | // last argument. |
403 | | // You'll probably like to use a simpler solution than the provided |
404 | | // one using the PropertyHelper_Spell. |
405 | 0 | PropertyHelper_Spelling& rHelper = GetPropHelper(); |
406 | 0 | rHelper.SetTmpPropVals( rProperties ); |
407 | |
|
408 | 0 | int nInfo = 0; // return compound information, disable apostrophe conversion |
409 | 0 | sal_Int16 nFailure = GetSpellFailure( rWord, rLocale, nInfo ); |
410 | | // it contains non-ASCII apostrophe, and it was bad with ASCII conversion: |
411 | | // check the word with the original apostrophe character(s), too |
412 | 0 | if ( nFailure != -1 && nInfo & SPELL_NON_ASCII_APOSTROPHE ) { |
413 | 0 | nInfo = SPELL_NON_ASCII_APOSTROPHE; // disable apostrophe conversion |
414 | 0 | nFailure = GetSpellFailure( rWord, rLocale, nInfo ); |
415 | 0 | } |
416 | 0 | if (nFailure != -1 && !rWord.match(SPELL_XML, 0)) |
417 | 0 | { |
418 | 0 | LanguageType nLang = LinguLocaleToLanguage( rLocale ); |
419 | | // postprocess result for errors that should be ignored |
420 | 0 | const bool bIgnoreError = |
421 | 0 | (!rHelper.IsSpellUpperCase() && IsUpper( rWord, nLang )) || |
422 | 0 | (!rHelper.IsSpellWithDigits() && HasDigits( rWord )); |
423 | 0 | if (bIgnoreError) |
424 | 0 | nFailure = -1; |
425 | 0 | } |
426 | | //#define SPELL_COMPOUND 1 << 0 |
427 | | |
428 | | // valid word, but it's a rule-based compound word |
429 | 0 | if ( nFailure == -1 && (nInfo & SPELL_COMPOUND) ) |
430 | 0 | { |
431 | 0 | bool bHasHyphen = rWord.indexOf('-') > -1; |
432 | 0 | if ( (bHasHyphen && !rHelper.IsSpellHyphenatedCompound()) || |
433 | 0 | (!bHasHyphen && !rHelper.IsSpellClosedCompound()) ) |
434 | 0 | { |
435 | 0 | return false; |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | 0 | return (nFailure == -1); |
440 | 0 | } |
441 | | |
442 | | Reference< XSpellAlternatives > |
443 | | SpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale ) |
444 | 0 | { |
445 | | // Retrieves the return values for the 'spell' function call in case |
446 | | // of a misspelled word. |
447 | | // Especially it may give a list of suggested (correct) words: |
448 | 0 | Reference< XSpellAlternatives > xRes; |
449 | | // note: mutex is held by higher up by spell which covers both |
450 | |
|
451 | 0 | Hunspell* pMS = nullptr; |
452 | 0 | rtl_TextEncoding eEnc = RTL_TEXTENCODING_DONTKNOW; |
453 | | |
454 | | // first handle smart quotes (single and double) |
455 | 0 | OUStringBuffer rBuf(rWord); |
456 | 0 | sal_Int32 n = rBuf.getLength(); |
457 | 0 | sal_Unicode c; |
458 | 0 | for (sal_Int32 ix=0; ix < n; ix++) |
459 | 0 | { |
460 | 0 | c = rBuf[ix]; |
461 | 0 | if ((c == 0x201C) || (c == 0x201D)) |
462 | 0 | rBuf[ix] = u'"'; |
463 | 0 | if ((c == 0x2018) || (c == 0x2019)) |
464 | 0 | rBuf[ix] = u'\''; |
465 | 0 | } |
466 | 0 | OUString nWord(rBuf.makeStringAndClear()); |
467 | |
|
468 | 0 | if (n) |
469 | 0 | { |
470 | 0 | LanguageType nLang = LinguLocaleToLanguage( rLocale ); |
471 | 0 | int numsug = 0; |
472 | |
|
473 | 0 | Sequence< OUString > aStr( 0 ); |
474 | 0 | for (const auto& currDict : m_DictItems) |
475 | 0 | { |
476 | 0 | pMS = nullptr; |
477 | 0 | eEnc = RTL_TEXTENCODING_DONTKNOW; |
478 | |
|
479 | 0 | if (rLocale == currDict.m_aDLoc) |
480 | 0 | { |
481 | 0 | pMS = currDict.m_pDict.get(); |
482 | 0 | eEnc = currDict.m_aDEnc; |
483 | 0 | } |
484 | |
|
485 | 0 | if (pMS) |
486 | 0 | { |
487 | 0 | OString aWrd(OU2ENC(nWord,eEnc)); |
488 | 0 | #if defined(H_DEPRECATED) |
489 | 0 | std::vector<std::string> suglst = pMS->suggest(std::string(aWrd)); |
490 | 0 | if (!suglst.empty()) |
491 | 0 | { |
492 | 0 | aStr.realloc(numsug + suglst.size()); |
493 | 0 | OUString *pStr = aStr.getArray(); |
494 | 0 | for (size_t ii = 0; ii < suglst.size(); ++ii) |
495 | 0 | { |
496 | 0 | pStr[numsug + ii] = OUString(suglst[ii].c_str(), suglst[ii].size(), eEnc); |
497 | 0 | } |
498 | 0 | numsug += suglst.size(); |
499 | 0 | } |
500 | | #else |
501 | | char ** suglst = nullptr; |
502 | | int count = pMS->suggest(&suglst, aWrd.getStr()); |
503 | | if (count) |
504 | | { |
505 | | aStr.realloc( numsug + count ); |
506 | | OUString *pStr = aStr.getArray(); |
507 | | for (int ii=0; ii < count; ++ii) |
508 | | { |
509 | | OUString cvtwrd(suglst[ii],strlen(suglst[ii]),eEnc); |
510 | | pStr[numsug + ii] = cvtwrd; |
511 | | } |
512 | | numsug += count; |
513 | | } |
514 | | pMS->free_list(&suglst, count); |
515 | | #endif |
516 | 0 | } |
517 | 0 | } |
518 | | |
519 | | // now return an empty alternative for no suggestions or the list of alternatives if some found |
520 | 0 | xRes = SpellAlternatives::CreateSpellAlternatives( rWord, nLang, SpellFailure::SPELLING_ERROR, aStr ); |
521 | 0 | return xRes; |
522 | 0 | } |
523 | 0 | return xRes; |
524 | 0 | } |
525 | | |
526 | | Reference< XSpellAlternatives > SAL_CALL SpellChecker::spell( |
527 | | const OUString& rWord, const Locale& rLocale, |
528 | | const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) |
529 | 0 | { |
530 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
531 | |
|
532 | 0 | if (rLocale == Locale() || rWord.isEmpty()) |
533 | 0 | return nullptr; |
534 | | |
535 | 0 | if (!hasLocale( rLocale )) |
536 | 0 | return nullptr; |
537 | | |
538 | 0 | Reference< XSpellAlternatives > xAlt; |
539 | 0 | if (!isValid( rWord, rLocale, rProperties )) |
540 | 0 | { |
541 | 0 | xAlt = GetProposals( rWord, rLocale ); |
542 | 0 | } |
543 | 0 | return xAlt; |
544 | 0 | } |
545 | | |
546 | | sal_Bool SAL_CALL SpellChecker::addLinguServiceEventListener( |
547 | | const Reference< XLinguServiceEventListener >& rxLstnr ) |
548 | 0 | { |
549 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
550 | |
|
551 | 0 | bool bRes = false; |
552 | 0 | if (!m_bDisposing && rxLstnr.is()) |
553 | 0 | { |
554 | 0 | bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr ); |
555 | 0 | } |
556 | 0 | return bRes; |
557 | 0 | } |
558 | | |
559 | | sal_Bool SAL_CALL SpellChecker::removeLinguServiceEventListener( |
560 | | const Reference< XLinguServiceEventListener >& rxLstnr ) |
561 | 0 | { |
562 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
563 | |
|
564 | 0 | bool bRes = false; |
565 | 0 | if (!m_bDisposing && rxLstnr.is()) |
566 | 0 | { |
567 | 0 | bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr ); |
568 | 0 | } |
569 | 0 | return bRes; |
570 | 0 | } |
571 | | |
572 | | OUString SAL_CALL SpellChecker::getServiceDisplayName(const Locale& rLocale) |
573 | 0 | { |
574 | 0 | std::locale loc(Translate::Create("svt", LanguageTag(rLocale))); |
575 | 0 | return Translate::get(STR_DESCRIPTION_HUNSPELL, loc); |
576 | 0 | } |
577 | | |
578 | | void SAL_CALL SpellChecker::initialize( const Sequence< Any >& rArguments ) |
579 | 0 | { |
580 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
581 | |
|
582 | 0 | if (m_pPropHelper) |
583 | 0 | return; |
584 | | |
585 | 0 | sal_Int32 nLen = rArguments.getLength(); |
586 | 0 | if (2 == nLen) |
587 | 0 | { |
588 | 0 | Reference< XLinguProperties > xPropSet; |
589 | 0 | rArguments.getConstArray()[0] >>= xPropSet; |
590 | | // rArguments.getConstArray()[1] >>= xDicList; |
591 | | |
592 | | //! Pointer allows for access of the non-UNO functions. |
593 | | //! And the reference to the UNO-functions while increasing |
594 | | //! the ref-count and will implicitly free the memory |
595 | | //! when the object is no longer used. |
596 | 0 | m_pPropHelper.reset( new PropertyHelper_Spelling( static_cast<XSpellChecker *>(this), xPropSet ) ); |
597 | 0 | m_pPropHelper->AddAsPropListener(); //! after a reference is established |
598 | 0 | } |
599 | 0 | else { |
600 | 0 | OSL_FAIL( "wrong number of arguments in sequence" ); |
601 | 0 | } |
602 | 0 | } |
603 | | |
604 | | void SAL_CALL SpellChecker::dispose() |
605 | 0 | { |
606 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
607 | |
|
608 | 0 | if (!m_bDisposing) |
609 | 0 | { |
610 | 0 | m_bDisposing = true; |
611 | 0 | EventObject aEvtObj( static_cast<XSpellChecker *>(this) ); |
612 | 0 | m_aEvtListeners.disposeAndClear( aEvtObj ); |
613 | 0 | if (m_pPropHelper) |
614 | 0 | { |
615 | 0 | m_pPropHelper->RemoveAsPropListener(); |
616 | 0 | m_pPropHelper.reset(); |
617 | 0 | } |
618 | 0 | } |
619 | 0 | } |
620 | | |
621 | | void SAL_CALL SpellChecker::addEventListener( const Reference< XEventListener >& rxListener ) |
622 | 0 | { |
623 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
624 | |
|
625 | 0 | if (!m_bDisposing && rxListener.is()) |
626 | 0 | m_aEvtListeners.addInterface( rxListener ); |
627 | 0 | } |
628 | | |
629 | | void SAL_CALL SpellChecker::removeEventListener( const Reference< XEventListener >& rxListener ) |
630 | 0 | { |
631 | 0 | MutexGuard aGuard( GetLinguMutex() ); |
632 | |
|
633 | 0 | if (!m_bDisposing && rxListener.is()) |
634 | 0 | m_aEvtListeners.removeInterface( rxListener ); |
635 | 0 | } |
636 | | |
637 | | // Service specific part |
638 | | OUString SAL_CALL SpellChecker::getImplementationName() |
639 | 0 | { |
640 | 0 | return u"org.openoffice.lingu.MySpellSpellChecker"_ustr; |
641 | 0 | } |
642 | | |
643 | | sal_Bool SAL_CALL SpellChecker::supportsService( const OUString& ServiceName ) |
644 | 0 | { |
645 | 0 | return cppu::supportsService(this, ServiceName); |
646 | 0 | } |
647 | | |
648 | | Sequence< OUString > SAL_CALL SpellChecker::getSupportedServiceNames() |
649 | 0 | { |
650 | 0 | return { SN_SPELLCHECKER }; |
651 | 0 | } |
652 | | |
653 | | extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* |
654 | | lingucomponent_SpellChecker_get_implementation( |
655 | | css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) |
656 | 0 | { |
657 | 0 | return cppu::acquire(new SpellChecker()); |
658 | 0 | } |
659 | | |
660 | | |
661 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |