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