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/macros.h>
21 #include <com/sun/star/beans/XPropertySet.hpp>
22 #include <com/sun/star/container/ElementExistException.hpp>
23 #include <com/sun/star/container/XNameAccess.hpp>
24 #include <com/sun/star/configuration/theDefaultProvider.hpp>
25 #include <com/sun/star/i18n/BreakIterator.hpp>
26 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
27 #include <com/sun/star/lang/XComponent.hpp>
28 #include <com/sun/star/lang/XServiceInfo.hpp>
29 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
30 #include <com/sun/star/linguistic2/XSupportedLocales.hpp>
31 #include <com/sun/star/linguistic2/XProofreader.hpp>
32 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
33 #include <com/sun/star/linguistic2/SingleProofreadingError.hpp>
34 #include <com/sun/star/linguistic2/ProofreadingResult.hpp>
35 #include <com/sun/star/linguistic2/LinguServiceEvent.hpp>
36 #include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
37 #include <com/sun/star/text/TextMarkupType.hpp>
38 #include <com/sun/star/text/TextMarkupDescriptor.hpp>
39 #include <com/sun/star/text/XMultiTextMarkup.hpp>
40 #include <com/sun/star/text/XFlatParagraph.hpp>
41 #include <com/sun/star/text/XFlatParagraphIterator.hpp>
42 #include <com/sun/star/uno/XComponentContext.hpp>
43 
44 #include <sal/config.h>
45 #include <sal/log.hxx>
46 #include <o3tl/safeint.hxx>
47 #include <osl/conditn.hxx>
48 #include <cppuhelper/supportsservice.hxx>
49 #include <cppuhelper/weak.hxx>
50 #include <i18nlangtag/languagetag.hxx>
51 #include <comphelper/processfactory.hxx>
52 #include <comphelper/propertysequence.hxx>
53 #include <tools/debug.hxx>
54 #include <tools/diagnose_ex.h>
55 
56 #include <map>
57 
58 #include <linguistic/misc.hxx>
59 
60 #include "gciterator.hxx"
61 
62 using namespace linguistic;
63 using namespace ::com::sun::star;
64 
65 // white space list: obtained from the fonts.config.txt of a Linux system.
66 const sal_Unicode aWhiteSpaces[] =
67 {
68     0x0020,   /* SPACE */
69     0x00a0,   /* NO-BREAK SPACE */
70     0x00ad,   /* SOFT HYPHEN */
71     0x115f,   /* HANGUL CHOSEONG FILLER */
72     0x1160,   /* HANGUL JUNGSEONG FILLER */
73     0x1680,   /* OGHAM SPACE MARK */
74     0x2000,   /* EN QUAD */
75     0x2001,   /* EM QUAD */
76     0x2002,   /* EN SPACE */
77     0x2003,   /* EM SPACE */
78     0x2004,   /* THREE-PER-EM SPACE */
79     0x2005,   /* FOUR-PER-EM SPACE */
80     0x2006,   /* SIX-PER-EM SPACE */
81     0x2007,   /* FIGURE SPACE */
82     0x2008,   /* PUNCTUATION SPACE */
83     0x2009,   /* THIN SPACE */
84     0x200a,   /* HAIR SPACE */
85     0x200b,   /* ZERO WIDTH SPACE */
86     0x200c,   /* ZERO WIDTH NON-JOINER */
87     0x200d,   /* ZERO WIDTH JOINER */
88     0x200e,   /* LEFT-TO-RIGHT MARK */
89     0x200f,   /* RIGHT-TO-LEFT MARK */
90     0x2028,   /* LINE SEPARATOR */
91     0x2029,   /* PARAGRAPH SEPARATOR */
92     0x202a,   /* LEFT-TO-RIGHT EMBEDDING */
93     0x202b,   /* RIGHT-TO-LEFT EMBEDDING */
94     0x202c,   /* POP DIRECTIONAL FORMATTING */
95     0x202d,   /* LEFT-TO-RIGHT OVERRIDE */
96     0x202e,   /* RIGHT-TO-LEFT OVERRIDE */
97     0x202f,   /* NARROW NO-BREAK SPACE */
98     0x205f,   /* MEDIUM MATHEMATICAL SPACE */
99     0x2060,   /* WORD JOINER */
100     0x2061,   /* FUNCTION APPLICATION */
101     0x2062,   /* INVISIBLE TIMES */
102     0x2063,   /* INVISIBLE SEPARATOR */
103     0x206A,   /* INHIBIT SYMMETRIC SWAPPING */
104     0x206B,   /* ACTIVATE SYMMETRIC SWAPPING */
105     0x206C,   /* INHIBIT ARABIC FORM SHAPING */
106     0x206D,   /* ACTIVATE ARABIC FORM SHAPING */
107     0x206E,   /* NATIONAL DIGIT SHAPES */
108     0x206F,   /* NOMINAL DIGIT SHAPES */
109     0x3000,   /* IDEOGRAPHIC SPACE */
110     0x3164,   /* HANGUL FILLER */
111     0xfeff,   /* ZERO WIDTH NO-BREAK SPACE */
112     0xffa0,   /* HALFWIDTH HANGUL FILLER */
113     0xfff9,   /* INTERLINEAR ANNOTATION ANCHOR */
114     0xfffa,   /* INTERLINEAR ANNOTATION SEPARATOR */
115     0xfffb    /* INTERLINEAR ANNOTATION TERMINATOR */
116 };
117 
118 //  Information about reason for proofreading (ProofInfo)
119    const sal_Int32 PROOFINFO_GET_PROOFRESULT = 1;
120    const sal_Int32 PROOFINFO_MARK_PARAGRAPH = 2;
121 
122 const int nWhiteSpaces = SAL_N_ELEMENTS( aWhiteSpaces );
123 
lcl_IsWhiteSpace(sal_Unicode cChar)124 static bool lcl_IsWhiteSpace( sal_Unicode cChar )
125 {
126     bool bFound = false;
127     for (int i = 0;  i < nWhiteSpaces && !bFound;  ++i)
128     {
129         if (cChar == aWhiteSpaces[i])
130             bFound = true;
131     }
132     return bFound;
133 }
134 
lcl_SkipWhiteSpaces(const OUString & rText,sal_Int32 nStartPos)135 static sal_Int32 lcl_SkipWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
136 {
137     // note having nStartPos point right behind the string is OK since that one
138     // is a correct end-of-sentence position to be returned from a grammar checker...
139 
140     const sal_Int32 nLen = rText.getLength();
141     bool bIllegalArgument = false;
142     if (nStartPos < 0)
143     {
144         bIllegalArgument = true;
145         nStartPos = 0;
146     }
147     if (nStartPos > nLen)
148     {
149         bIllegalArgument = true;
150         nStartPos = nLen;
151     }
152     if (bIllegalArgument)
153     {
154         SAL_WARN( "linguistic", "lcl_SkipWhiteSpaces: illegal arguments" );
155     }
156 
157     sal_Int32 nRes = nStartPos;
158     if (0 <= nStartPos && nStartPos < nLen)
159     {
160         const sal_Unicode* const pEnd = rText.getStr() + nLen;
161         const sal_Unicode *pText = rText.getStr() + nStartPos;
162         while (pText != pEnd && lcl_IsWhiteSpace(*pText))
163             ++pText;
164         nRes = pText - rText.getStr();
165     }
166 
167     DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_SkipWhiteSpaces return value out of range" );
168     return nRes;
169 }
170 
lcl_BacktraceWhiteSpaces(const OUString & rText,sal_Int32 nStartPos)171 static sal_Int32 lcl_BacktraceWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
172 {
173     // note: having nStartPos point right behind the string is OK since that one
174     // is a correct end-of-sentence position to be returned from a grammar checker...
175 
176     const sal_Int32 nLen = rText.getLength();
177     bool bIllegalArgument = false;
178     if (nStartPos < 0)
179     {
180         bIllegalArgument = true;
181         nStartPos = 0;
182     }
183     if (nStartPos > nLen)
184     {
185         bIllegalArgument = true;
186         nStartPos = nLen;
187     }
188     if (bIllegalArgument)
189     {
190         SAL_WARN( "linguistic", "lcl_BacktraceWhiteSpaces: illegal arguments" );
191     }
192 
193     sal_Int32 nRes = nStartPos;
194     sal_Int32 nPosBefore = nStartPos - 1;
195     const sal_Unicode *pStart = rText.getStr();
196     if (0 <= nPosBefore && nPosBefore < nLen && lcl_IsWhiteSpace( pStart[ nPosBefore ] ))
197     {
198         nStartPos = nPosBefore;
199         const sal_Unicode *pText = rText.getStr() + nStartPos;
200         while (pText > pStart && lcl_IsWhiteSpace( *pText ))
201             --pText;
202         // now add 1 since we want to point to the first char after the last char in the sentence...
203         nRes = pText - pStart + 1;
204     }
205 
206     DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_BacktraceWhiteSpaces return value out of range" );
207     return nRes;
208 }
209 
210 
211 extern "C" {
212 
lcl_workerfunc(void * gci)213 static void lcl_workerfunc (void * gci)
214 {
215     osl_setThreadName("GrammarCheckingIterator");
216 
217     static_cast<GrammarCheckingIterator*>(gci)->DequeueAndCheck();
218 }
219 
220 }
221 
lcl_GetPrimaryLanguageOfSentence(const uno::Reference<text::XFlatParagraph> & xFlatPara,sal_Int32 nStartIndex)222 static lang::Locale lcl_GetPrimaryLanguageOfSentence(
223     const uno::Reference< text::XFlatParagraph >& xFlatPara,
224     sal_Int32 nStartIndex )
225 {
226     //get the language of the first word
227     return xFlatPara->getLanguageOfText( nStartIndex, 1 );
228 }
229 
230 
LngXStringKeyMap()231 LngXStringKeyMap::LngXStringKeyMap() {}
232 
insertValue(const OUString & aKey,const css::uno::Any & aValue)233 void SAL_CALL LngXStringKeyMap::insertValue(const OUString& aKey, const css::uno::Any& aValue)
234 {
235     std::map<OUString, css::uno::Any>::const_iterator aIter = maMap.find(aKey);
236     if (aIter != maMap.end())
237         throw css::container::ElementExistException();
238 
239     maMap[aKey] = aValue;
240 }
241 
getValue(const OUString & aKey)242 css::uno::Any SAL_CALL LngXStringKeyMap::getValue(const OUString& aKey)
243 {
244     std::map<OUString, css::uno::Any>::const_iterator aIter = maMap.find(aKey);
245     if (aIter == maMap.end())
246         throw css::container::NoSuchElementException();
247 
248     return (*aIter).second;
249 }
250 
hasValue(const OUString & aKey)251 sal_Bool SAL_CALL LngXStringKeyMap::hasValue(const OUString& aKey)
252 {
253     return maMap.find(aKey) != maMap.end();
254 }
255 
getCount()256 ::sal_Int32 SAL_CALL LngXStringKeyMap::getCount() { return maMap.size(); }
257 
getKeyByIndex(::sal_Int32 nIndex)258 OUString SAL_CALL LngXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex)
259 {
260     if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maMap.size())
261         throw css::lang::IndexOutOfBoundsException();
262 
263     return OUString();
264 }
265 
getValueByIndex(::sal_Int32 nIndex)266 css::uno::Any SAL_CALL LngXStringKeyMap::getValueByIndex(::sal_Int32 nIndex)
267 {
268     if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maMap.size())
269         throw css::lang::IndexOutOfBoundsException();
270 
271     return css::uno::Any();
272 }
273 
274 
GrammarCheckingIterator()275 GrammarCheckingIterator::GrammarCheckingIterator() :
276     m_bEnd( false ),
277     m_aCurCheckedDocId(),
278     m_bGCServicesChecked( false ),
279     m_nDocIdCounter( 0 ),
280     m_thread(nullptr),
281     m_aEventListeners( MyMutex::get() ),
282     m_aNotifyListeners( MyMutex::get() )
283 {
284 }
285 
286 
~GrammarCheckingIterator()287 GrammarCheckingIterator::~GrammarCheckingIterator()
288 {
289     TerminateThread();
290 }
291 
TerminateThread()292 void GrammarCheckingIterator::TerminateThread()
293 {
294     oslThread t;
295     {
296         ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
297         t = m_thread;
298         m_thread = nullptr;
299         m_bEnd = true;
300         m_aWakeUpThread.set();
301     }
302     if (t != nullptr)
303     {
304         osl_joinWithThread(t);
305         osl_destroyThread(t);
306     }
307 }
308 
NextDocId()309 sal_Int32 GrammarCheckingIterator::NextDocId()
310 {
311     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
312     m_nDocIdCounter += 1;
313     return m_nDocIdCounter;
314 }
315 
316 
GetOrCreateDocId(const uno::Reference<lang::XComponent> & xComponent)317 OUString GrammarCheckingIterator::GetOrCreateDocId(
318     const uno::Reference< lang::XComponent > &xComponent )
319 {
320     // internal method; will always be called with locked mutex
321 
322     OUString aRes;
323     if (xComponent.is())
324     {
325         if (m_aDocIdMap.find( xComponent.get() ) != m_aDocIdMap.end())
326         {
327             // return already existing entry
328             aRes = m_aDocIdMap[ xComponent.get() ];
329         }
330         else // add new entry
331         {
332             sal_Int32 nRes = NextDocId();
333             aRes = OUString::number( nRes );
334             m_aDocIdMap[ xComponent.get() ] = aRes;
335             xComponent->addEventListener( this );
336         }
337     }
338     return aRes;
339 }
340 
341 
AddEntry(const uno::WeakReference<text::XFlatParagraphIterator> & xFlatParaIterator,const uno::WeakReference<text::XFlatParagraph> & xFlatPara,const OUString & rDocId,sal_Int32 nStartIndex,bool bAutomatic)342 void GrammarCheckingIterator::AddEntry(
343     const uno::WeakReference< text::XFlatParagraphIterator >& xFlatParaIterator,
344     const uno::WeakReference< text::XFlatParagraph >& xFlatPara,
345     const OUString & rDocId,
346     sal_Int32 nStartIndex,
347     bool bAutomatic )
348 {
349     // we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
350     // but we always need a xFlatPara...
351     uno::Reference< text::XFlatParagraph > xPara( xFlatPara );
352     if (!xPara.is())
353         return;
354 
355     FPEntry aNewFPEntry;
356     aNewFPEntry.m_xParaIterator = xFlatParaIterator;
357     aNewFPEntry.m_xPara         = xFlatPara;
358     aNewFPEntry.m_aDocId        = rDocId;
359     aNewFPEntry.m_nStartIndex   = nStartIndex;
360     aNewFPEntry.m_bAutomatic    = bAutomatic;
361 
362     // add new entry to the end of this queue
363     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
364     if (!m_thread)
365         m_thread = osl_createThread( lcl_workerfunc, this );
366     m_aFPEntriesQueue.push_back( aNewFPEntry );
367 
368     // wake up the thread in order to do grammar checking
369     m_aWakeUpThread.set();
370 }
371 
372 
ProcessResult(const linguistic2::ProofreadingResult & rRes,const uno::Reference<text::XFlatParagraphIterator> & rxFlatParagraphIterator,bool bIsAutomaticChecking)373 void GrammarCheckingIterator::ProcessResult(
374     const linguistic2::ProofreadingResult &rRes,
375     const uno::Reference< text::XFlatParagraphIterator > &rxFlatParagraphIterator,
376     bool bIsAutomaticChecking )
377 {
378     DBG_ASSERT( rRes.xFlatParagraph.is(), "xFlatParagraph is missing" );
379      //no guard necessary as no members are used
380     bool bContinueWithNextPara = false;
381     if (!rRes.xFlatParagraph.is() || rRes.xFlatParagraph->isModified())
382     {
383         // if paragraph was modified/deleted meanwhile continue with the next one...
384         bContinueWithNextPara = true;
385     }
386     else    // paragraph is still unchanged...
387     {
388         // mark found errors...
389 
390         sal_Int32 nTextLen = rRes.aText.getLength();
391         bool bBoundariesOk = 0 <= rRes.nStartOfSentencePosition     && rRes.nStartOfSentencePosition <= nTextLen &&
392                              0 <= rRes.nBehindEndOfSentencePosition && rRes.nBehindEndOfSentencePosition <= nTextLen &&
393                              0 <= rRes.nStartOfNextSentencePosition && rRes.nStartOfNextSentencePosition <= nTextLen &&
394                              rRes.nStartOfSentencePosition      <= rRes.nBehindEndOfSentencePosition &&
395                              rRes.nBehindEndOfSentencePosition  <= rRes.nStartOfNextSentencePosition;
396         DBG_ASSERT( bBoundariesOk, "inconsistent sentence boundaries" );
397 
398         uno::Reference< text::XMultiTextMarkup > xMulti( rRes.xFlatParagraph, uno::UNO_QUERY );
399         if (xMulti.is())    // use new API for markups
400         {
401             try
402             {
403                 // length = number of found errors + 1 sentence markup
404                 sal_Int32 nErrors = rRes.aErrors.getLength();
405                 uno::Sequence< text::TextMarkupDescriptor > aDescriptors( nErrors + 1 );
406                 text::TextMarkupDescriptor * pDescriptors = aDescriptors.getArray();
407 
408                 // at pos 0 .. nErrors-1 -> all grammar errors
409                 for (const linguistic2::SingleProofreadingError &rError : rRes.aErrors)
410                 {
411                     text::TextMarkupDescriptor &rDesc = *pDescriptors++;
412 
413                     rDesc.nType   = rError.nErrorType;
414                     rDesc.nOffset = rError.nErrorStart;
415                     rDesc.nLength = rError.nErrorLength;
416 
417                     // the proofreader may return SPELLING but right now our core
418                     // does only handle PROOFREADING if the result is from the proofreader...
419                     // (later on we may wish to color spelling errors found by the proofreader
420                     // differently for example. But no special handling right now.
421                     if (rDesc.nType == text::TextMarkupType::SPELLCHECK)
422                         rDesc.nType = text::TextMarkupType::PROOFREADING;
423 
424                     uno::Reference< container::XStringKeyMap > xKeyMap(
425                         new LngXStringKeyMap());
426                     for( const beans::PropertyValue& rProperty : rError.aProperties )
427                     {
428                         if ( rProperty.Name == "LineColor" )
429                         {
430                             xKeyMap->insertValue(rProperty.Name,
431                                                  rProperty.Value);
432                             rDesc.xMarkupInfoContainer = xKeyMap;
433                         }
434                         else if ( rProperty.Name == "LineType" )
435                         {
436                             xKeyMap->insertValue(rProperty.Name,
437                                                  rProperty.Value);
438                             rDesc.xMarkupInfoContainer = xKeyMap;
439                         }
440                     }
441                 }
442 
443                 // at pos nErrors -> sentence markup
444                 // nSentenceLength: includes the white-spaces following the sentence end...
445                 const sal_Int32 nSentenceLength = rRes.nStartOfNextSentencePosition - rRes.nStartOfSentencePosition;
446                 pDescriptors->nType   = text::TextMarkupType::SENTENCE;
447                 pDescriptors->nOffset = rRes.nStartOfSentencePosition;
448                 pDescriptors->nLength = nSentenceLength;
449 
450                 xMulti->commitMultiTextMarkup( aDescriptors ) ;
451             }
452             catch (lang::IllegalArgumentException &)
453             {
454                 TOOLS_WARN_EXCEPTION( "linguistic", "commitMultiTextMarkup" );
455             }
456         }
457 
458         // other sentences left to be checked in this paragraph?
459         if (rRes.nStartOfNextSentencePosition < rRes.aText.getLength())
460         {
461             AddEntry( rxFlatParagraphIterator, rRes.xFlatParagraph, rRes.aDocumentIdentifier, rRes.nStartOfNextSentencePosition, bIsAutomaticChecking );
462         }
463         else    // current paragraph finished
464         {
465             // set "already checked" flag for the current flat paragraph
466             if (rRes.xFlatParagraph.is())
467                 rRes.xFlatParagraph->setChecked( text::TextMarkupType::PROOFREADING, true );
468 
469             bContinueWithNextPara = true;
470         }
471     }
472 
473     if (bContinueWithNextPara)
474     {
475         // we need to continue with the next paragraph
476         uno::Reference< text::XFlatParagraph > xFlatParaNext;
477         if (rxFlatParagraphIterator.is())
478             xFlatParaNext = rxFlatParagraphIterator->getNextPara();
479         {
480             AddEntry( rxFlatParagraphIterator, xFlatParaNext, rRes.aDocumentIdentifier, 0, bIsAutomaticChecking );
481         }
482     }
483 }
484 
485 
GetGrammarChecker(const lang::Locale & rLocale)486 uno::Reference< linguistic2::XProofreader > GrammarCheckingIterator::GetGrammarChecker(
487     const lang::Locale &rLocale )
488 {
489     uno::Reference< linguistic2::XProofreader > xRes;
490 
491     // ---- THREAD SAFE START ----
492     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
493 
494     // check supported locales for each grammarchecker if not already done
495     if (!m_bGCServicesChecked)
496     {
497         GetConfiguredGCSvcs_Impl();
498         m_bGCServicesChecked = true;
499     }
500 
501     const LanguageType nLang = LanguageTag::convertToLanguageType( rLocale, false);
502     GCImplNames_t::const_iterator aLangIt( m_aGCImplNamesByLang.find( nLang ) );
503     if (aLangIt != m_aGCImplNamesByLang.end())  // matching configured language found?
504     {
505         OUString aSvcImplName( aLangIt->second );
506         GCReferences_t::const_iterator aImplNameIt( m_aGCReferencesByService.find( aSvcImplName ) );
507         if (aImplNameIt != m_aGCReferencesByService.end())  // matching impl name found?
508         {
509             xRes = aImplNameIt->second;
510         }
511         else    // the service is to be instantiated here for the first time...
512         {
513             try
514             {
515                 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
516                 uno::Reference< linguistic2::XProofreader > xGC(
517                         xContext->getServiceManager()->createInstanceWithContext(aSvcImplName, xContext),
518                         uno::UNO_QUERY_THROW );
519                 uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW );
520 
521                 if (xSuppLoc->hasLocale( rLocale ))
522                 {
523                     m_aGCReferencesByService[ aSvcImplName ] = xGC;
524                     xRes = xGC;
525 
526                     uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xBC( xGC, uno::UNO_QUERY );
527                     if (xBC.is())
528                         xBC->addLinguServiceEventListener( this );
529                 }
530                 else
531                 {
532                     SAL_WARN( "linguistic", "grammar checker does not support required locale" );
533                 }
534             }
535             catch (uno::Exception &)
536             {
537                 SAL_WARN( "linguistic", "instantiating grammar checker failed" );
538             }
539         }
540     }
541     // ---- THREAD SAFE END ----
542 
543     return xRes;
544 }
545 
546 static uno::Sequence<beans::PropertyValue>
lcl_makeProperties(uno::Reference<text::XFlatParagraph> const & xFlatPara,sal_Int32 nProofInfo)547 lcl_makeProperties(uno::Reference<text::XFlatParagraph> const& xFlatPara, sal_Int32 nProofInfo)
548 {
549     uno::Reference<beans::XPropertySet> const xProps(
550             xFlatPara, uno::UNO_QUERY_THROW);
551     css::uno::Any a (nProofInfo);
552     return comphelper::InitPropertySequence({
553         { "FieldPositions", xProps->getPropertyValue("FieldPositions") },
554         { "FootnotePositions", xProps->getPropertyValue("FootnotePositions") },
555         { "ProofInfo", a }
556     });
557 }
558 
DequeueAndCheck()559 void GrammarCheckingIterator::DequeueAndCheck()
560 {
561     for (;;)
562     {
563         // ---- THREAD SAFE START ----
564         bool bQueueEmpty = false;
565         {
566             ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
567             if (m_bEnd)
568             {
569                 break;
570             }
571             bQueueEmpty = m_aFPEntriesQueue.empty();
572         }
573         // ---- THREAD SAFE END ----
574 
575         if (!bQueueEmpty)
576         {
577             uno::Reference< text::XFlatParagraphIterator > xFPIterator;
578             uno::Reference< text::XFlatParagraph > xFlatPara;
579             FPEntry aFPEntryItem;
580             OUString aCurDocId;
581             // ---- THREAD SAFE START ----
582             {
583                 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
584                 aFPEntryItem        = m_aFPEntriesQueue.front();
585                 xFPIterator         = aFPEntryItem.m_xParaIterator;
586                 xFlatPara           = aFPEntryItem.m_xPara;
587                 m_aCurCheckedDocId  = aFPEntryItem.m_aDocId;
588                 aCurDocId = m_aCurCheckedDocId;
589 
590                 m_aFPEntriesQueue.pop_front();
591             }
592             // ---- THREAD SAFE END ----
593 
594             if (xFlatPara.is() && xFPIterator.is())
595             {
596                 try
597                 {
598                     OUString aCurTxt( xFlatPara->getText() );
599                     lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, aFPEntryItem.m_nStartIndex );
600 
601                     const bool bModified = xFlatPara->isModified();
602                     if (!bModified)
603                     {
604                         linguistic2::ProofreadingResult aRes;
605 
606                         // ---- THREAD SAFE START ----
607                         {
608                             osl::ClearableMutexGuard aGuard(MyMutex::get());
609 
610                             sal_Int32 nStartPos = aFPEntryItem.m_nStartIndex;
611                             sal_Int32 nSuggestedEnd
612                                 = GetSuggestedEndOfSentence(aCurTxt, nStartPos, aCurLocale);
613                             DBG_ASSERT((nSuggestedEnd == 0 && aCurTxt.isEmpty())
614                                            || nSuggestedEnd > nStartPos,
615                                        "nSuggestedEndOfSentencePos calculation failed?");
616 
617                             uno::Reference<linguistic2::XProofreader> xGC =
618                                 GetGrammarChecker(aCurLocale);
619                             if (xGC.is())
620                             {
621                                 aGuard.clear();
622                                 uno::Sequence<beans::PropertyValue> const aProps(
623                                     lcl_makeProperties(xFlatPara, PROOFINFO_MARK_PARAGRAPH));
624                                 aRes = xGC->doProofreading(aCurDocId, aCurTxt, aCurLocale,
625                                                            nStartPos, nSuggestedEnd, aProps);
626 
627                                 //!! work-around to prevent looping if the grammar checker
628                                 //!! failed to properly identify the sentence end
629                                 if (aRes.nBehindEndOfSentencePosition <= nStartPos
630                                     && aRes.nBehindEndOfSentencePosition != nSuggestedEnd)
631                                 {
632                                     SAL_WARN(
633                                         "linguistic",
634                                         "!! Grammarchecker failed to provide end of sentence !!");
635                                     aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
636                                 }
637 
638                                 aRes.xFlatParagraph = xFlatPara;
639                                 aRes.nStartOfSentencePosition = nStartPos;
640                             }
641                             else
642                             {
643                                 // no grammar checker -> no error
644                                 // but we need to provide the data below in order to continue with the next sentence
645                                 aRes.aDocumentIdentifier = aCurDocId;
646                                 aRes.xFlatParagraph = xFlatPara;
647                                 aRes.aText = aCurTxt;
648                                 aRes.aLocale = aCurLocale;
649                                 aRes.nStartOfSentencePosition = nStartPos;
650                                 aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
651                             }
652                             aRes.nStartOfNextSentencePosition
653                                 = lcl_SkipWhiteSpaces(aCurTxt, aRes.nBehindEndOfSentencePosition);
654                             aRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces(
655                                 aCurTxt, aRes.nStartOfNextSentencePosition);
656 
657                             //guard has to be cleared as ProcessResult calls out of this class
658                         }
659                         // ---- THREAD SAFE END ----
660                         ProcessResult( aRes, xFPIterator, aFPEntryItem.m_bAutomatic );
661                     }
662                     else
663                     {
664                         // the paragraph changed meanwhile... (and maybe is still edited)
665                         // thus we simply continue to ask for the next to be checked.
666                         uno::Reference< text::XFlatParagraph > xFlatParaNext( xFPIterator->getNextPara() );
667                         AddEntry( xFPIterator, xFlatParaNext, aCurDocId, 0, aFPEntryItem.m_bAutomatic );
668                     }
669                 }
670                 catch (css::uno::Exception &)
671                 {
672                     TOOLS_WARN_EXCEPTION("linguistic", "GrammarCheckingIterator::DequeueAndCheck ignoring");
673                 }
674             }
675 
676             // ---- THREAD SAFE START ----
677             {
678                 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
679                 m_aCurCheckedDocId.clear();
680             }
681             // ---- THREAD SAFE END ----
682         }
683         else
684         {
685             // ---- THREAD SAFE START ----
686             {
687                 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
688                 if (m_bEnd)
689                 {
690                     break;
691                 }
692                 // Check queue state again
693                 if (m_aFPEntriesQueue.empty())
694                     m_aWakeUpThread.reset();
695             }
696             // ---- THREAD SAFE END ----
697 
698             //if the queue is empty
699             // IMPORTANT: Don't call condition.wait() with locked
700             // mutex. Otherwise you would keep out other threads
701             // to add entries to the queue! A condition is thread-
702             // safe implemented.
703             m_aWakeUpThread.wait();
704         }
705     }
706 }
707 
708 
startProofreading(const uno::Reference<::uno::XInterface> & xDoc,const uno::Reference<text::XFlatParagraphIteratorProvider> & xIteratorProvider)709 void SAL_CALL GrammarCheckingIterator::startProofreading(
710     const uno::Reference< ::uno::XInterface > & xDoc,
711     const uno::Reference< text::XFlatParagraphIteratorProvider > & xIteratorProvider )
712 {
713     // get paragraph to start checking with
714     const bool bAutomatic = true;
715     uno::Reference<text::XFlatParagraphIterator> xFPIterator = xIteratorProvider->getFlatParagraphIterator(
716             text::TextMarkupType::PROOFREADING, bAutomatic );
717     uno::Reference< text::XFlatParagraph > xPara( xFPIterator.is()? xFPIterator->getFirstPara() : nullptr );
718     uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
719 
720     // ---- THREAD SAFE START ----
721     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
722     if (xPara.is() && xComponent.is())
723     {
724         OUString aDocId = GetOrCreateDocId( xComponent );
725 
726         // create new entry and add it to queue
727         AddEntry( xFPIterator, xPara, aDocId, 0, bAutomatic );
728     }
729     // ---- THREAD SAFE END ----
730 }
731 
732 
checkSentenceAtPosition(const uno::Reference<uno::XInterface> & xDoc,const uno::Reference<text::XFlatParagraph> & xFlatPara,const OUString & rText,const lang::Locale &,sal_Int32 nStartOfSentencePos,sal_Int32 nSuggestedEndOfSentencePos,sal_Int32 nErrorPosInPara)733 linguistic2::ProofreadingResult SAL_CALL GrammarCheckingIterator::checkSentenceAtPosition(
734     const uno::Reference< uno::XInterface >& xDoc,
735     const uno::Reference< text::XFlatParagraph >& xFlatPara,
736     const OUString& rText,
737     const lang::Locale&,
738     sal_Int32 nStartOfSentencePos,
739     sal_Int32 nSuggestedEndOfSentencePos,
740     sal_Int32 nErrorPosInPara )
741 {
742     // for the context menu...
743 
744     linguistic2::ProofreadingResult aRes;
745 
746     uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
747     if (xFlatPara.is() && xComponent.is() &&
748         ( nErrorPosInPara < 0 || nErrorPosInPara < rText.getLength()))
749     {
750         // iterate through paragraph until we find the sentence we are interested in
751         linguistic2::ProofreadingResult aTmpRes;
752         sal_Int32 nStartPos = nStartOfSentencePos >= 0 ? nStartOfSentencePos : 0;
753 
754         bool bFound = false;
755         do
756         {
757             lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, nStartPos );
758             sal_Int32 nOldStartOfSentencePos = nStartPos;
759             uno::Reference< linguistic2::XProofreader > xGC;
760             OUString aDocId;
761 
762             // ---- THREAD SAFE START ----
763             {
764                 ::osl::ClearableGuard< ::osl::Mutex > aGuard( MyMutex::get() );
765                 aDocId = GetOrCreateDocId( xComponent );
766                 nSuggestedEndOfSentencePos = GetSuggestedEndOfSentence( rText, nStartPos, aCurLocale );
767                 DBG_ASSERT( nSuggestedEndOfSentencePos > nStartPos, "nSuggestedEndOfSentencePos calculation failed?" );
768 
769                 xGC = GetGrammarChecker( aCurLocale );
770             }
771             // ---- THREAD SAFE START ----
772             sal_Int32 nEndPos = -1;
773             if (xGC.is())
774             {
775                 uno::Sequence<beans::PropertyValue> const aProps(
776                         lcl_makeProperties(xFlatPara, PROOFINFO_GET_PROOFRESULT));
777                 aTmpRes = xGC->doProofreading( aDocId, rText,
778                     aCurLocale, nStartPos, nSuggestedEndOfSentencePos, aProps );
779 
780                 //!! work-around to prevent looping if the grammar checker
781                 //!! failed to properly identify the sentence end
782                 if (aTmpRes.nBehindEndOfSentencePosition <= nStartPos)
783                 {
784                     SAL_WARN( "linguistic", "!! Grammarchecker failed to provide end of sentence !!" );
785                     aTmpRes.nBehindEndOfSentencePosition = nSuggestedEndOfSentencePos;
786                 }
787 
788                 aTmpRes.xFlatParagraph           = xFlatPara;
789                 aTmpRes.nStartOfSentencePosition = nStartPos;
790                 nEndPos = aTmpRes.nBehindEndOfSentencePosition;
791 
792                 if ((nErrorPosInPara< 0 || nStartPos <= nErrorPosInPara) && nErrorPosInPara < nEndPos)
793                     bFound = true;
794             }
795             if (nEndPos == -1) // no result from grammar checker
796                 nEndPos = nSuggestedEndOfSentencePos;
797             nStartPos = lcl_SkipWhiteSpaces( rText, nEndPos );
798             aTmpRes.nBehindEndOfSentencePosition = nEndPos;
799             aTmpRes.nStartOfNextSentencePosition = nStartPos;
800             aTmpRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces( rText, aTmpRes.nStartOfNextSentencePosition );
801 
802             // prevent endless loop by forcefully advancing if needs be...
803             if (nStartPos <= nOldStartOfSentencePos)
804             {
805                 SAL_WARN( "linguistic", "end-of-sentence detection failed?" );
806                 nStartPos = nOldStartOfSentencePos + 1;
807             }
808         }
809         while (!bFound && nStartPos < rText.getLength());
810 
811         if (bFound && !xFlatPara->isModified())
812             aRes = aTmpRes;
813     }
814 
815     return aRes;
816 }
817 
818 
GetSuggestedEndOfSentence(const OUString & rText,sal_Int32 nSentenceStartPos,const lang::Locale & rLocale)819 sal_Int32 GrammarCheckingIterator::GetSuggestedEndOfSentence(
820     const OUString &rText,
821     sal_Int32 nSentenceStartPos,
822     const lang::Locale &rLocale )
823 {
824     // internal method; will always be called with locked mutex
825 
826     if (!m_xBreakIterator.is())
827     {
828         uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
829         m_xBreakIterator = i18n::BreakIterator::create(xContext);
830     }
831     sal_Int32 nTextLen = rText.getLength();
832     sal_Int32 nEndPosition(0);
833     sal_Int32 nTmpStartPos = nSentenceStartPos;
834     do
835     {
836         sal_Int32 const nPrevEndPosition(nEndPosition);
837         nEndPosition = nTextLen;
838         if (nTmpStartPos < nTextLen)
839         {
840             nEndPosition = m_xBreakIterator->endOfSentence( rText, nTmpStartPos, rLocale );
841             if (nEndPosition <= nPrevEndPosition)
842             {
843                 // fdo#68750 if there's no progress at all then presumably
844                 // there's no end of sentence in this paragraph so just
845                 // set the end position to end of paragraph
846                 nEndPosition = nTextLen;
847             }
848         }
849         if (nEndPosition < 0)
850             nEndPosition = nTextLen;
851 
852         ++nTmpStartPos;
853     }
854     while (nEndPosition <= nSentenceStartPos && nEndPosition < nTextLen);
855     if (nEndPosition > nTextLen)
856         nEndPosition = nTextLen;
857     return nEndPosition;
858 }
859 
860 
resetIgnoreRules()861 void SAL_CALL GrammarCheckingIterator::resetIgnoreRules(  )
862 {
863     for (auto const& elem : m_aGCReferencesByService)
864     {
865         uno::Reference< linguistic2::XProofreader > xGC(elem.second);
866         if (xGC.is())
867             xGC->resetIgnoreRules();
868     }
869 }
870 
871 
isProofreading(const uno::Reference<uno::XInterface> & xDoc)872 sal_Bool SAL_CALL GrammarCheckingIterator::isProofreading(
873     const uno::Reference< uno::XInterface >& xDoc )
874 {
875     // ---- THREAD SAFE START ----
876     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
877 
878     bool bRes = false;
879 
880     uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
881     if (xComponent.is())
882     {
883         // if the component was already used in one of the two calls to check text
884         // i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
885         // m_aDocIdMap unless the document already disposed.
886         // If it is not found then it is not yet being checked (or requested to being checked)
887         const DocMap_t::const_iterator aIt( m_aDocIdMap.find( xComponent.get() ) );
888         if (aIt != m_aDocIdMap.end())
889         {
890             // check in document is checked automatically in the background...
891             OUString aDocId = aIt->second;
892             if (!m_aCurCheckedDocId.isEmpty() && m_aCurCheckedDocId == aDocId)
893             {
894                 // an entry for that document was dequeued and is currently being checked.
895                 bRes = true;
896             }
897             else
898             {
899                 // we need to check if there is an entry for that document in the queue...
900                 // That is the document is going to be checked sooner or later.
901 
902                 sal_Int32 nSize = m_aFPEntriesQueue.size();
903                 for (sal_Int32 i = 0; i < nSize && !bRes; ++i)
904                 {
905                     if (aDocId == m_aFPEntriesQueue[i].m_aDocId)
906                         bRes = true;
907                 }
908             }
909         }
910     }
911     // ---- THREAD SAFE END ----
912 
913     return bRes;
914 }
915 
916 
processLinguServiceEvent(const linguistic2::LinguServiceEvent & rLngSvcEvent)917 void SAL_CALL GrammarCheckingIterator::processLinguServiceEvent(
918     const linguistic2::LinguServiceEvent& rLngSvcEvent )
919 {
920     if (rLngSvcEvent.nEvent != linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN)
921         return;
922 
923     try
924     {
925          uno::Reference< uno::XInterface > xThis( static_cast< OWeakObject * >(this) );
926          linguistic2::LinguServiceEvent aEvent( xThis, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN );
927          m_aNotifyListeners.notifyEach(
928                 &linguistic2::XLinguServiceEventListener::processLinguServiceEvent,
929                 aEvent);
930     }
931     catch (uno::RuntimeException &)
932     {
933          throw;
934     }
935     catch (const ::uno::Exception &)
936     {
937         // ignore
938         TOOLS_WARN_EXCEPTION("linguistic", "processLinguServiceEvent");
939     }
940 }
941 
942 
addLinguServiceEventListener(const uno::Reference<linguistic2::XLinguServiceEventListener> & xListener)943 sal_Bool SAL_CALL GrammarCheckingIterator::addLinguServiceEventListener(
944     const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
945 {
946     if (xListener.is())
947     {
948         m_aNotifyListeners.addInterface( xListener );
949     }
950     return true;
951 }
952 
953 
removeLinguServiceEventListener(const uno::Reference<linguistic2::XLinguServiceEventListener> & xListener)954 sal_Bool SAL_CALL GrammarCheckingIterator::removeLinguServiceEventListener(
955     const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
956 {
957     if (xListener.is())
958     {
959         m_aNotifyListeners.removeInterface( xListener );
960     }
961     return true;
962 }
963 
964 
dispose()965 void SAL_CALL GrammarCheckingIterator::dispose()
966 {
967     lang::EventObject aEvt( static_cast<linguistic2::XProofreadingIterator *>(this) );
968     m_aEventListeners.disposeAndClear( aEvt );
969 
970     TerminateThread();
971 
972     // ---- THREAD SAFE START ----
973     {
974         ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
975 
976         // release all UNO references
977 
978         m_xBreakIterator.clear();
979 
980         // clear containers with UNO references AND have those references released
981         GCReferences_t  aTmpEmpty1;
982         DocMap_t        aTmpEmpty2;
983         FPQueue_t       aTmpEmpty3;
984         m_aGCReferencesByService.swap( aTmpEmpty1 );
985         m_aDocIdMap.swap( aTmpEmpty2 );
986         m_aFPEntriesQueue.swap( aTmpEmpty3 );
987     }
988     // ---- THREAD SAFE END ----
989 }
990 
991 
addEventListener(const uno::Reference<lang::XEventListener> & xListener)992 void SAL_CALL GrammarCheckingIterator::addEventListener(
993     const uno::Reference< lang::XEventListener >& xListener )
994 {
995     if (xListener.is())
996     {
997         m_aEventListeners.addInterface( xListener );
998     }
999 }
1000 
1001 
removeEventListener(const uno::Reference<lang::XEventListener> & xListener)1002 void SAL_CALL GrammarCheckingIterator::removeEventListener(
1003     const uno::Reference< lang::XEventListener >& xListener )
1004 {
1005     if (xListener.is())
1006     {
1007         m_aEventListeners.removeInterface( xListener );
1008     }
1009 }
1010 
1011 
disposing(const lang::EventObject & rSource)1012 void SAL_CALL GrammarCheckingIterator::disposing( const lang::EventObject &rSource )
1013 {
1014     // if the component (document) is disposing release all references
1015     //!! There is no need to remove entries from the queue that are from this document
1016     //!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
1017     //!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
1018     //!! And if an entry is currently checked by a grammar checker upon return the results
1019     //!! should be ignored.
1020     //!! Also GetOrCreateDocId will not use that very same Id again...
1021     //!! All of the above resulting in that we only have to get rid of the implementation pointer here.
1022     uno::Reference< lang::XComponent > xDoc( rSource.Source, uno::UNO_QUERY );
1023     if (xDoc.is())
1024     {
1025         // ---- THREAD SAFE START ----
1026         ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
1027         m_aDocIdMap.erase( xDoc.get() );
1028         // ---- THREAD SAFE END ----
1029     }
1030 }
1031 
1032 
GetUpdateAccess() const1033 uno::Reference< util::XChangesBatch > const & GrammarCheckingIterator::GetUpdateAccess() const
1034 {
1035     if (!m_xUpdateAccess.is())
1036     {
1037         try
1038         {
1039             // get configuration provider
1040             uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
1041             uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider =
1042                     configuration::theDefaultProvider::get( xContext );
1043 
1044             // get configuration update access
1045             beans::PropertyValue aValue;
1046             aValue.Name  = "nodepath";
1047             aValue.Value <<= OUString("org.openoffice.Office.Linguistic/ServiceManager");
1048             uno::Sequence< uno::Any > aProps(1);
1049             aProps[0] <<= aValue;
1050             m_xUpdateAccess.set(
1051                     xConfigurationProvider->createInstanceWithArguments(
1052                         "com.sun.star.configuration.ConfigurationUpdateAccess", aProps ),
1053                         uno::UNO_QUERY_THROW );
1054         }
1055         catch (uno::Exception &)
1056         {
1057         }
1058     }
1059 
1060     return m_xUpdateAccess;
1061 }
1062 
1063 
GetConfiguredGCSvcs_Impl()1064 void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
1065 {
1066     GCImplNames_t   aTmpGCImplNamesByLang;
1067 
1068     try
1069     {
1070         // get node names (locale iso strings) for configured grammar checkers
1071         uno::Reference< container::XNameAccess > xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW );
1072         xNA.set( xNA->getByName( "GrammarCheckerList" ), uno::UNO_QUERY_THROW );
1073         const uno::Sequence< OUString > aElementNames( xNA->getElementNames() );
1074 
1075         for (const OUString& rElementName : aElementNames)
1076         {
1077             uno::Sequence< OUString > aImplNames;
1078             uno::Any aTmp( xNA->getByName( rElementName ) );
1079             if (aTmp >>= aImplNames)
1080             {
1081                 if (aImplNames.hasElements())
1082                 {
1083                     // only the first entry is used, there should be only one grammar checker per language
1084                     const OUString aImplName( aImplNames[0] );
1085                     const LanguageType nLang = LanguageTag::convertToLanguageType( rElementName );
1086                     aTmpGCImplNamesByLang[ nLang ] = aImplName;
1087                 }
1088             }
1089             else
1090             {
1091                 SAL_WARN( "linguistic", "failed to get aImplNames. Wrong type?" );
1092             }
1093         }
1094     }
1095     catch (uno::Exception const &)
1096     {
1097         TOOLS_WARN_EXCEPTION( "linguistic", "exception caught. Failed to get configured services" );
1098     }
1099 
1100     {
1101         // ---- THREAD SAFE START ----
1102         ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
1103         m_aGCImplNamesByLang     = aTmpGCImplNamesByLang;
1104         // ---- THREAD SAFE END ----
1105     }
1106 }
1107 
1108 
supportsService(const OUString & rServiceName)1109 sal_Bool SAL_CALL GrammarCheckingIterator::supportsService(
1110     const OUString & rServiceName )
1111 {
1112     return cppu::supportsService(this, rServiceName);
1113 }
1114 
1115 
getImplementationName()1116 OUString SAL_CALL GrammarCheckingIterator::getImplementationName(  )
1117 {
1118     return "com.sun.star.lingu2.ProofreadingIterator";
1119 }
1120 
1121 
getSupportedServiceNames()1122 uno::Sequence< OUString > SAL_CALL GrammarCheckingIterator::getSupportedServiceNames(  )
1123 {
1124     return  { "com.sun.star.linguistic2.ProofreadingIterator" };
1125 }
1126 
1127 
SetServiceList(const lang::Locale & rLocale,const uno::Sequence<OUString> & rSvcImplNames)1128 void GrammarCheckingIterator::SetServiceList(
1129     const lang::Locale &rLocale,
1130     const uno::Sequence< OUString > &rSvcImplNames )
1131 {
1132     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
1133 
1134     LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
1135     OUString aImplName;
1136     if (rSvcImplNames.hasElements())
1137         aImplName = rSvcImplNames[0];   // there is only one grammar checker per language
1138 
1139     if (!LinguIsUnspecified(nLanguage) && nLanguage != LANGUAGE_DONTKNOW)
1140     {
1141         if (!aImplName.isEmpty())
1142             m_aGCImplNamesByLang[ nLanguage ] = aImplName;
1143         else
1144             m_aGCImplNamesByLang.erase( nLanguage );
1145     }
1146 }
1147 
1148 
GetServiceList(const lang::Locale & rLocale) const1149 uno::Sequence< OUString > GrammarCheckingIterator::GetServiceList(
1150     const lang::Locale &rLocale ) const
1151 {
1152     ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
1153 
1154     OUString aImplName;     // there is only one grammar checker per language
1155     LanguageType nLang  = LinguLocaleToLanguage( rLocale );
1156     GCImplNames_t::const_iterator aIt( m_aGCImplNamesByLang.find( nLang ) );
1157     if (aIt != m_aGCImplNamesByLang.end())
1158         aImplName = aIt->second;
1159 
1160     if (!aImplName.isEmpty())
1161         return { aImplName };
1162     return {};
1163 }
1164 
1165 
1166 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
linguistic_GrammarCheckingIterator_get_implementation(css::uno::XComponentContext *,css::uno::Sequence<css::uno::Any> const &)1167 linguistic_GrammarCheckingIterator_get_implementation(
1168     css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
1169 {
1170     return cppu::acquire(new GrammarCheckingIterator());
1171 }
1172 
1173 
1174 
1175 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1176