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