Line data Source code
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 <sal/config.h>
21 :
22 : #include <boost/noncopyable.hpp>
23 : #include <com/sun/star/uno/Reference.h>
24 : #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
25 : #include <com/sun/star/linguistic2/SpellFailure.hpp>
26 : #include <com/sun/star/registry/XRegistryKey.hpp>
27 :
28 : #include <cppuhelper/factory.hxx>
29 : #include <unotools/localedatawrapper.hxx>
30 : #include <comphelper/processfactory.hxx>
31 : #include <tools/debug.hxx>
32 : #include <svl/lngmisc.hxx>
33 : #include <osl/mutex.hxx>
34 :
35 : #include <vector>
36 :
37 : #include "spelldsp.hxx"
38 : #include "linguistic/spelldta.hxx"
39 : #include "lngsvcmgr.hxx"
40 : #include "linguistic/lngprops.hxx"
41 :
42 : using namespace osl;
43 : using namespace com::sun::star;
44 : using namespace com::sun::star::beans;
45 : using namespace com::sun::star::lang;
46 : using namespace com::sun::star::uno;
47 : using namespace com::sun::star::linguistic2;
48 : using namespace linguistic;
49 :
50 :
51 : // ProposalList: list of proposals for misspelled words
52 : // The order of strings in the array should be left unchanged because the
53 : // spellchecker should have put the more likely suggestions at the top.
54 : // New entries will be added to the end but duplicates are to be avoided.
55 : // Removing entries is done by assigning the empty string.
56 : // The sequence is constructed from all non empty strings in the original
57 : // while maintaining the order.
58 0 : class ProposalList: private boost::noncopyable
59 : {
60 : std::vector< OUString > aVec;
61 :
62 : sal_Bool HasEntry( const OUString &rText ) const;
63 :
64 : public:
65 0 : ProposalList() {}
66 :
67 : size_t Count() const;
68 : void Prepend( const OUString &rText );
69 : void Append( const OUString &rNew );
70 : void Append( const std::vector< OUString > &rNew );
71 : void Append( const Sequence< OUString > &rNew );
72 : Sequence< OUString > GetSequence() const;
73 : };
74 :
75 :
76 0 : sal_Bool ProposalList::HasEntry( const OUString &rText ) const
77 : {
78 0 : sal_Bool bFound = sal_False;
79 0 : size_t nCnt = aVec.size();
80 0 : for (size_t i = 0; !bFound && i < nCnt; ++i)
81 : {
82 0 : if (aVec[i] == rText)
83 0 : bFound = sal_True;
84 : }
85 0 : return bFound;
86 : }
87 :
88 0 : void ProposalList::Prepend( const OUString &rText )
89 : {
90 0 : if (!HasEntry( rText ))
91 0 : aVec.insert( aVec.begin(), rText );
92 0 : }
93 :
94 0 : void ProposalList::Append( const OUString &rText )
95 : {
96 0 : if (!HasEntry( rText ))
97 0 : aVec.push_back( rText );
98 0 : }
99 :
100 0 : void ProposalList::Append( const std::vector< OUString > &rNew )
101 : {
102 0 : size_t nLen = rNew.size();
103 0 : for ( size_t i = 0; i < nLen; ++i)
104 : {
105 0 : const OUString &rText = rNew[i];
106 0 : if (!HasEntry( rText ))
107 0 : Append( rText );
108 : }
109 0 : }
110 :
111 0 : void ProposalList::Append( const Sequence< OUString > &rNew )
112 : {
113 0 : sal_Int32 nLen = rNew.getLength();
114 0 : const OUString *pNew = rNew.getConstArray();
115 0 : for (sal_Int32 i = 0; i < nLen; ++i)
116 : {
117 0 : const OUString &rText = pNew[i];
118 0 : if (!HasEntry( rText ))
119 0 : Append( rText );
120 : }
121 0 : }
122 :
123 0 : size_t ProposalList::Count() const
124 : {
125 : // returns the number of non-empty strings in the vector
126 :
127 0 : size_t nRes = 0;
128 0 : size_t nLen = aVec.size();
129 0 : for (size_t i = 0; i < nLen; ++i)
130 : {
131 0 : if (!aVec[i].isEmpty())
132 0 : ++nRes;
133 : }
134 0 : return nRes;
135 : }
136 :
137 0 : Sequence< OUString > ProposalList::GetSequence() const
138 : {
139 0 : sal_Int32 nCount = Count();
140 0 : sal_Int32 nIdx = 0;
141 0 : Sequence< OUString > aRes( nCount );
142 0 : OUString *pRes = aRes.getArray();
143 0 : sal_Int32 nLen = aVec.size();
144 0 : for (sal_Int32 i = 0; i < nLen; ++i)
145 : {
146 0 : const OUString &rText = aVec[i];
147 : DBG_ASSERT( nIdx < nCount, "index our of range" );
148 0 : if (nIdx < nCount && !rText.isEmpty())
149 0 : pRes[ nIdx++ ] = rText;
150 : }
151 0 : return aRes;
152 : }
153 :
154 0 : sal_Bool SvcListHasLanguage(
155 : const LangSvcEntries_Spell &rEntry,
156 : LanguageType nLanguage )
157 : {
158 0 : sal_Bool bHasLanguage = sal_False;
159 0 : Locale aTmpLocale;
160 :
161 0 : const Reference< XSpellChecker > *pRef = rEntry.aSvcRefs .getConstArray();
162 0 : sal_Int32 nLen = rEntry.aSvcRefs.getLength();
163 0 : for (sal_Int32 k = 0; k < nLen && !bHasLanguage; ++k)
164 : {
165 0 : if (pRef[k].is())
166 : {
167 0 : if (aTmpLocale.Language.isEmpty())
168 0 : aTmpLocale = LanguageTag::convertToLocale( nLanguage );
169 0 : bHasLanguage = pRef[k]->hasLocale( aTmpLocale );
170 : }
171 : }
172 :
173 0 : return bHasLanguage;
174 : }
175 :
176 0 : SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
177 0 : rMgr (rLngSvcMgr)
178 : {
179 0 : pCache = NULL;
180 0 : pCharClass = NULL;
181 0 : }
182 :
183 :
184 0 : SpellCheckerDispatcher::~SpellCheckerDispatcher()
185 : {
186 0 : ClearSvcList();
187 0 : delete pCache;
188 0 : delete pCharClass;
189 0 : }
190 :
191 :
192 0 : void SpellCheckerDispatcher::ClearSvcList()
193 : {
194 : // release memory for each table entry
195 0 : SpellSvcByLangMap_t aTmp;
196 0 : aSvcMap.swap( aTmp );
197 0 : }
198 :
199 :
200 0 : Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
201 : throw(RuntimeException, std::exception)
202 : {
203 0 : MutexGuard aGuard( GetLinguMutex() );
204 :
205 0 : Sequence< Locale > aLocales( static_cast< sal_Int32 >(aSvcMap.size()) );
206 0 : Locale *pLocales = aLocales.getArray();
207 0 : SpellSvcByLangMap_t::const_iterator aIt;
208 0 : for (aIt = aSvcMap.begin(); aIt != aSvcMap.end(); ++aIt)
209 : {
210 0 : *pLocales++ = LanguageTag::convertToLocale( aIt->first );
211 : }
212 0 : return aLocales;
213 : }
214 :
215 :
216 0 : sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
217 : throw(RuntimeException, std::exception)
218 : {
219 0 : MutexGuard aGuard( GetLinguMutex() );
220 0 : SpellSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
221 0 : return aIt != aSvcMap.end();
222 : }
223 :
224 :
225 : sal_Bool SAL_CALL
226 0 : SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
227 : const PropertyValues& rProperties )
228 : throw(IllegalArgumentException, RuntimeException, std::exception)
229 : {
230 0 : MutexGuard aGuard( GetLinguMutex() );
231 0 : return isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, sal_True );
232 : }
233 :
234 :
235 : Reference< XSpellAlternatives > SAL_CALL
236 0 : SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
237 : const PropertyValues& rProperties )
238 : throw(IllegalArgumentException, RuntimeException, std::exception)
239 : {
240 0 : MutexGuard aGuard( GetLinguMutex() );
241 0 : return spell_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, sal_True );
242 : }
243 :
244 :
245 : // returns the overall result of cross-checking with all user-dictionaries
246 : // including the IgnoreAll list
247 0 : static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
248 : const OUString &rWord,
249 : LanguageType nLanguage )
250 : {
251 0 : Reference< XDictionaryEntry > xRes;
252 :
253 : // the order of winning from top to bottom is:
254 : // 1) IgnoreAll list will always win
255 : // 2) Negative dictionaries will win over positive dictionaries
256 0 : Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
257 0 : if (xIgnoreAll.is())
258 0 : xRes = xIgnoreAll->getEntry( rWord );
259 0 : if (!xRes.is())
260 : {
261 0 : Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
262 : Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
263 0 : rWord, nLanguage, false, true ) );
264 0 : if (xNegEntry.is())
265 0 : xRes = xNegEntry;
266 : else
267 : {
268 : Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
269 0 : rWord, nLanguage, true, true ) );
270 0 : if (xPosEntry.is())
271 0 : xRes = xPosEntry;
272 0 : }
273 : }
274 :
275 0 : return xRes;
276 : }
277 :
278 :
279 0 : sal_Bool SpellCheckerDispatcher::isValid_Impl(
280 : const OUString& rWord,
281 : LanguageType nLanguage,
282 : const PropertyValues& rProperties,
283 : sal_Bool bCheckDics)
284 : throw( RuntimeException, IllegalArgumentException )
285 : {
286 0 : MutexGuard aGuard( GetLinguMutex() );
287 :
288 0 : sal_Bool bRes = sal_True;
289 :
290 0 : if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
291 0 : return bRes;
292 :
293 : // search for entry with that language
294 0 : SpellSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
295 0 : LangSvcEntries_Spell *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
296 :
297 0 : if (pEntry)
298 : {
299 0 : OUString aChkWord( rWord );
300 0 : Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
301 :
302 : // replace typographical apostroph by ascii apostroph
303 0 : OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
304 : DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpectend length of quotation mark" );
305 0 : if (!aSingleQuote.isEmpty())
306 0 : aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
307 :
308 0 : RemoveHyphens( aChkWord );
309 0 : if (IsIgnoreControlChars( rProperties, GetPropSet() ))
310 0 : RemoveControlChars( aChkWord );
311 :
312 0 : sal_Int32 nLen = pEntry->aSvcRefs.getLength();
313 : DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
314 : "lng : sequence length mismatch");
315 : DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
316 : "lng : index out of range");
317 :
318 0 : sal_Int32 i = 0;
319 0 : sal_Bool bTmpRes = sal_True;
320 0 : sal_Bool bTmpResValid = sal_False;
321 :
322 : // try already instantiated services first
323 : {
324 : const Reference< XSpellChecker > *pRef =
325 0 : pEntry->aSvcRefs.getConstArray();
326 0 : while (i <= pEntry->nLastTriedSvcIndex
327 0 : && (!bTmpResValid || sal_False == bTmpRes))
328 : {
329 0 : bTmpResValid = sal_True;
330 0 : if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
331 : {
332 0 : bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
333 0 : if (!bTmpRes)
334 : {
335 0 : bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
336 :
337 : // Add correct words to the cache.
338 : // But not those that are correct only because of
339 : // the temporary supplied settings.
340 0 : if (bTmpRes && 0 == rProperties.getLength())
341 0 : GetCache().AddWord( aChkWord, nLanguage );
342 : }
343 : }
344 : else
345 0 : bTmpResValid = sal_False;
346 :
347 0 : if (bTmpResValid)
348 0 : bRes = bTmpRes;
349 :
350 0 : ++i;
351 : }
352 : }
353 :
354 : // if still no result instantiate new services and try those
355 0 : if ((!bTmpResValid || sal_False == bTmpRes)
356 0 : && pEntry->nLastTriedSvcIndex < nLen - 1)
357 : {
358 0 : const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
359 0 : Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
360 :
361 : Reference< XComponentContext > xContext(
362 0 : comphelper::getProcessComponentContext() );
363 :
364 : // build service initialization argument
365 0 : Sequence< Any > aArgs(2);
366 0 : aArgs.getArray()[0] <<= GetPropSet();
367 :
368 0 : while (i < nLen && (!bTmpResValid || sal_False == bTmpRes))
369 : {
370 : // create specific service via it's implementation name
371 0 : Reference< XSpellChecker > xSpell;
372 : try
373 : {
374 0 : xSpell = Reference< XSpellChecker >(
375 0 : xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
376 0 : pImplNames[i], aArgs, xContext ),
377 0 : UNO_QUERY );
378 : }
379 0 : catch (uno::Exception &)
380 : {
381 : DBG_ASSERT( false, "createInstanceWithArguments failed" );
382 : }
383 0 : pRef [i] = xSpell;
384 :
385 : Reference< XLinguServiceEventBroadcaster >
386 0 : xBroadcaster( xSpell, UNO_QUERY );
387 0 : if (xBroadcaster.is())
388 0 : rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
389 :
390 0 : bTmpResValid = sal_True;
391 0 : if (xSpell.is() && xSpell->hasLocale( aLocale ))
392 : {
393 0 : bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
394 0 : if (!bTmpRes)
395 : {
396 0 : bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
397 : // Add correct words to the cache.
398 : // But not those that are correct only because of
399 : // the temporary supplied settings.
400 0 : if (bTmpRes && 0 == rProperties.getLength())
401 0 : GetCache().AddWord( aChkWord, nLanguage );
402 : }
403 : }
404 : else
405 0 : bTmpResValid = sal_False;
406 0 : if (bTmpResValid)
407 0 : bRes = bTmpRes;
408 :
409 0 : pEntry->nLastTriedSvcIndex = (sal_Int16) i;
410 0 : ++i;
411 0 : }
412 :
413 : // if language is not supported by any of the services
414 : // remove it from the list.
415 0 : if (i == nLen)
416 : {
417 0 : if (!SvcListHasLanguage( *pEntry, nLanguage ))
418 0 : aSvcMap.erase( nLanguage );
419 0 : }
420 : }
421 :
422 : // cross-check against results from dictionaries which have precedence!
423 0 : if (bCheckDics &&
424 0 : GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
425 : {
426 0 : Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
427 0 : if (xTmp.is()) {
428 0 : bRes = !xTmp->isNegative();
429 : } else {
430 0 : setCharClass(LanguageTag(nLanguage));
431 0 : sal_uInt16 ct = capitalType(aChkWord, pCharClass);
432 0 : if (ct == CAPTYPE_INITCAP || ct == CAPTYPE_ALLCAP) {
433 0 : Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, pCharClass), nLanguage ) );
434 0 : if (xTmp2.is()) {
435 0 : bRes = !xTmp2->isNegative();
436 0 : }
437 : }
438 0 : }
439 0 : }
440 : }
441 :
442 0 : return bRes;
443 : }
444 :
445 :
446 0 : Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
447 : const OUString& rWord,
448 : LanguageType nLanguage,
449 : const PropertyValues& rProperties,
450 : sal_Bool bCheckDics )
451 : throw(IllegalArgumentException, RuntimeException)
452 : {
453 0 : MutexGuard aGuard( GetLinguMutex() );
454 :
455 0 : Reference< XSpellAlternatives > xRes;
456 :
457 0 : if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
458 0 : return xRes;
459 :
460 : // search for entry with that language
461 0 : SpellSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
462 0 : LangSvcEntries_Spell *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
463 :
464 0 : if (pEntry)
465 : {
466 0 : OUString aChkWord( rWord );
467 0 : Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
468 :
469 : // replace typographical apostroph by ascii apostroph
470 0 : OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
471 : DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpectend length of quotation mark" );
472 0 : if (!aSingleQuote.isEmpty())
473 0 : aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
474 :
475 0 : RemoveHyphens( aChkWord );
476 0 : if (IsIgnoreControlChars( rProperties, GetPropSet() ))
477 0 : RemoveControlChars( aChkWord );
478 :
479 0 : sal_Int32 nLen = pEntry->aSvcRefs.getLength();
480 : DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
481 : "lng : sequence length mismatch");
482 : DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
483 : "lng : index out of range");
484 :
485 0 : sal_Int32 i = 0;
486 0 : Reference< XSpellAlternatives > xTmpRes;
487 0 : sal_Bool bTmpResValid = sal_False;
488 :
489 : // try already instantiated services first
490 : {
491 0 : const Reference< XSpellChecker > *pRef = pEntry->aSvcRefs.getConstArray();
492 0 : sal_Int32 nNumSugestions = -1;
493 0 : while (i <= pEntry->nLastTriedSvcIndex
494 0 : && (!bTmpResValid || xTmpRes.is()) )
495 : {
496 0 : bTmpResValid = sal_True;
497 0 : if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
498 : {
499 0 : sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
500 0 : if (bOK)
501 0 : xTmpRes = NULL;
502 : else
503 : {
504 0 : xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
505 :
506 : // Add correct words to the cache.
507 : // But not those that are correct only because of
508 : // the temporary supplied settings.
509 0 : if (!xTmpRes.is() && 0 == rProperties.getLength())
510 0 : GetCache().AddWord( aChkWord, nLanguage );
511 : }
512 : }
513 : else
514 0 : bTmpResValid = sal_False;
515 :
516 : // return first found result if the word is not known by any checker.
517 : // But if that result has no suggestions use the first one that does
518 : // provide suggestions for the misspelled word.
519 0 : if (!xRes.is() && bTmpResValid)
520 : {
521 0 : xRes = xTmpRes;
522 0 : nNumSugestions = 0;
523 0 : if (xRes.is())
524 0 : nNumSugestions = xRes->getAlternatives().getLength();
525 : }
526 0 : sal_Int32 nTmpNumSugestions = 0;
527 0 : if (xTmpRes.is() && bTmpResValid)
528 0 : nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
529 0 : if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
530 : {
531 0 : xRes = xTmpRes;
532 0 : nNumSugestions = nTmpNumSugestions;
533 : }
534 :
535 0 : ++i;
536 : }
537 : }
538 :
539 : // if still no result instantiate new services and try those
540 0 : if ((!bTmpResValid || xTmpRes.is())
541 0 : && pEntry->nLastTriedSvcIndex < nLen - 1)
542 : {
543 0 : const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
544 0 : Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
545 :
546 : Reference< XComponentContext > xContext(
547 0 : comphelper::getProcessComponentContext() );
548 :
549 : // build service initialization argument
550 0 : Sequence< Any > aArgs(2);
551 0 : aArgs.getArray()[0] <<= GetPropSet();
552 :
553 0 : sal_Int32 nNumSugestions = -1;
554 0 : while (i < nLen && (!bTmpResValid || xTmpRes.is()))
555 : {
556 : // create specific service via it's implementation name
557 0 : Reference< XSpellChecker > xSpell;
558 : try
559 : {
560 0 : xSpell = Reference< XSpellChecker >(
561 0 : xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
562 0 : pImplNames[i], aArgs, xContext ),
563 0 : UNO_QUERY );
564 : }
565 0 : catch (uno::Exception &)
566 : {
567 : DBG_ASSERT( false, "createInstanceWithArguments failed" );
568 : }
569 0 : pRef [i] = xSpell;
570 :
571 : Reference< XLinguServiceEventBroadcaster >
572 0 : xBroadcaster( xSpell, UNO_QUERY );
573 0 : if (xBroadcaster.is())
574 0 : rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
575 :
576 0 : bTmpResValid = sal_True;
577 0 : if (xSpell.is() && xSpell->hasLocale( aLocale ))
578 : {
579 0 : sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
580 0 : if (bOK)
581 0 : xTmpRes = NULL;
582 : else
583 : {
584 0 : xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
585 :
586 : // Add correct words to the cache.
587 : // But not those that are correct only because of
588 : // the temporary supplied settings.
589 0 : if (!xTmpRes.is() && 0 == rProperties.getLength())
590 0 : GetCache().AddWord( aChkWord, nLanguage );
591 : }
592 : }
593 : else
594 0 : bTmpResValid = sal_False;
595 :
596 : // return first found result if the word is not known by any checker.
597 : // But if that result has no suggestions use the first one that does
598 : // provide suggestions for the misspelled word.
599 0 : if (!xRes.is() && bTmpResValid)
600 : {
601 0 : xRes = xTmpRes;
602 0 : nNumSugestions = 0;
603 0 : if (xRes.is())
604 0 : nNumSugestions = xRes->getAlternatives().getLength();
605 : }
606 0 : sal_Int32 nTmpNumSugestions = 0;
607 0 : if (xTmpRes.is() && bTmpResValid)
608 0 : nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
609 0 : if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
610 : {
611 0 : xRes = xTmpRes;
612 0 : nNumSugestions = nTmpNumSugestions;
613 : }
614 :
615 0 : pEntry->nLastTriedSvcIndex = (sal_Int16) i;
616 0 : ++i;
617 0 : }
618 :
619 : // if language is not supported by any of the services
620 : // remove it from the list.
621 0 : if (i == nLen)
622 : {
623 0 : if (!SvcListHasLanguage( *pEntry, nLanguage ))
624 0 : aSvcMap.erase( nLanguage );
625 0 : }
626 : }
627 :
628 : // if word is finally found to be correct
629 : // clear previously remembered alternatives
630 0 : if (bTmpResValid && !xTmpRes.is())
631 0 : xRes = NULL;
632 :
633 : // list of proposals found (to be checked against entries of
634 : // neagtive dictionaries)
635 0 : ProposalList aProposalList;
636 0 : sal_Int16 eFailureType = -1; // no failure
637 0 : if (xRes.is())
638 : {
639 0 : aProposalList.Append( xRes->getAlternatives() );
640 0 : eFailureType = xRes->getFailureType();
641 : }
642 0 : Reference< XSearchableDictionaryList > xDList;
643 0 : if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
644 0 : xDList = GetDicList();
645 :
646 : // cross-check against results from user-dictionaries which have precedence!
647 0 : if (bCheckDics && xDList.is())
648 : {
649 0 : Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
650 0 : if (xTmp.is())
651 : {
652 0 : if (xTmp->isNegative()) // positive entry found
653 : {
654 0 : eFailureType = SpellFailure::IS_NEGATIVE_WORD;
655 :
656 : // replacement text to be added to suggestions, if not empty
657 0 : OUString aAddRplcTxt( xTmp->getReplacementText() );
658 :
659 : // replacement text must not be in negative dictionary itself
660 0 : if (!aAddRplcTxt.isEmpty() &&
661 0 : !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
662 : {
663 0 : aProposalList.Prepend( aAddRplcTxt );
664 0 : }
665 : }
666 : else // positive entry found
667 : {
668 0 : xRes = NULL;
669 0 : eFailureType = -1; // no failure
670 : }
671 0 : }
672 : }
673 :
674 0 : if (eFailureType != -1) // word misspelled or found in negative user-dictionary
675 : {
676 : // search suitable user-dictionaries for suggestions that are
677 : // similar to the misspelled word
678 0 : std::vector< OUString > aDicListProps; // list of proposals from user-dictionaries
679 0 : SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
680 0 : aProposalList.Append( aDicListProps );
681 0 : Sequence< OUString > aProposals = aProposalList.GetSequence();
682 :
683 : // remove entries listed in negative dictionaries
684 : // (we don't want to display suggestions that will be regarded as misspelledlater on)
685 0 : if (bCheckDics && xDList.is())
686 0 : SeqRemoveNegEntries( aProposals, xDList, nLanguage );
687 :
688 0 : uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
689 0 : if (xSetAlt.is())
690 : {
691 0 : xSetAlt->setAlternatives( aProposals );
692 0 : xSetAlt->setFailureType( eFailureType );
693 : }
694 : else
695 : {
696 0 : if (xRes.is())
697 : {
698 : DBG_ASSERT( false, "XSetSpellAlternatives not implemented!" );
699 : }
700 0 : else if (aProposals.getLength() > 0)
701 : {
702 : // no xRes but Proposals found from the user-dictionaries.
703 : // Thus we need to create an xRes...
704 0 : xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
705 0 : SpellFailure::IS_NEGATIVE_WORD, aProposals );
706 : }
707 0 : }
708 0 : }
709 : }
710 :
711 0 : return xRes;
712 : }
713 :
714 0 : uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages( )
715 : throw (uno::RuntimeException, std::exception)
716 : {
717 0 : MutexGuard aGuard( GetLinguMutex() );
718 0 : uno::Sequence< Locale > aTmp( getLocales() );
719 0 : uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
720 0 : return aRes;
721 : }
722 :
723 :
724 0 : sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
725 : sal_Int16 nLanguage )
726 : throw (uno::RuntimeException, std::exception)
727 : {
728 0 : MutexGuard aGuard( GetLinguMutex() );
729 0 : return hasLocale( LanguageTag::convertToLocale( nLanguage) );
730 : }
731 :
732 :
733 0 : sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
734 : const OUString& rWord,
735 : sal_Int16 nLanguage,
736 : const uno::Sequence< beans::PropertyValue >& rProperties )
737 : throw (lang::IllegalArgumentException, uno::RuntimeException, std::exception)
738 : {
739 0 : MutexGuard aGuard( GetLinguMutex() );
740 0 : return isValid( rWord, LanguageTag::convertToLocale( nLanguage ), rProperties);
741 : }
742 :
743 :
744 0 : uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
745 : const OUString& rWord,
746 : sal_Int16 nLanguage,
747 : const uno::Sequence< beans::PropertyValue >& rProperties )
748 : throw (lang::IllegalArgumentException, uno::RuntimeException, std::exception)
749 : {
750 0 : MutexGuard aGuard( GetLinguMutex() );
751 0 : return spell( rWord, LanguageTag::convertToLocale( nLanguage), rProperties);
752 : }
753 :
754 :
755 0 : void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
756 : const Sequence< OUString > &rSvcImplNames )
757 : {
758 0 : MutexGuard aGuard( GetLinguMutex() );
759 :
760 0 : if (pCache)
761 0 : pCache->Flush(); // new services may spell differently...
762 :
763 0 : sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
764 :
765 0 : sal_Int32 nLen = rSvcImplNames.getLength();
766 0 : if (0 == nLen)
767 : // remove entry
768 0 : aSvcMap.erase( nLanguage );
769 : else
770 : {
771 : // modify/add entry
772 0 : LangSvcEntries_Spell *pEntry = aSvcMap[ nLanguage ].get();
773 0 : if (pEntry)
774 : {
775 0 : pEntry->Clear();
776 0 : pEntry->aSvcImplNames = rSvcImplNames;
777 0 : pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
778 : }
779 : else
780 : {
781 0 : boost::shared_ptr< LangSvcEntries_Spell > pTmpEntry( new LangSvcEntries_Spell( rSvcImplNames ) );
782 0 : pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
783 0 : aSvcMap[ nLanguage ] = pTmpEntry;
784 : }
785 0 : }
786 0 : }
787 :
788 :
789 : Sequence< OUString >
790 0 : SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
791 : {
792 0 : MutexGuard aGuard( GetLinguMutex() );
793 :
794 0 : Sequence< OUString > aRes;
795 :
796 : // search for entry with that language and use data from that
797 0 : sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
798 0 : const SpellSvcByLangMap_t::const_iterator aIt( aSvcMap.find( nLanguage ) );
799 0 : const LangSvcEntries_Spell *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
800 0 : if (pEntry)
801 0 : aRes = pEntry->aSvcImplNames;
802 :
803 0 : return aRes;
804 : }
805 :
806 :
807 0 : LinguDispatcher::DspType SpellCheckerDispatcher::GetDspType() const
808 : {
809 0 : return DSP_SPELL;
810 : }
811 :
812 0 : void SpellCheckerDispatcher::FlushSpellCache()
813 : {
814 0 : if (pCache)
815 0 : pCache->Flush();
816 0 : }
817 :
818 0 : void SpellCheckerDispatcher::setCharClass(const LanguageTag& rLanguageTag)
819 : {
820 0 : if (!pCharClass)
821 0 : pCharClass = new CharClass(rLanguageTag);
822 0 : pCharClass->setLanguageTag(rLanguageTag);
823 0 : }
824 :
825 :
826 :
827 0 : OUString SAL_CALL SpellCheckerDispatcher::makeLowerCase(const OUString& aTerm, CharClass * pCC)
828 : {
829 0 : if (pCC)
830 0 : return pCC->lowercase(aTerm);
831 0 : return aTerm;
832 : }
833 :
834 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|