1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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/XDictionary.hpp>
31 #include <com/sun/star/linguistic2/XSupportedLocales.hpp>
32 #include <com/sun/star/linguistic2/XProofreader.hpp>
33 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
34 #include <com/sun/star/linguistic2/SingleProofreadingError.hpp>
35 #include <com/sun/star/linguistic2/ProofreadingResult.hpp>
36 #include <com/sun/star/linguistic2/LinguServiceEvent.hpp>
37 #include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
38 #include <com/sun/star/text/TextMarkupType.hpp>
39 #include <com/sun/star/text/TextMarkupDescriptor.hpp>
40 #include <com/sun/star/text/XMultiTextMarkup.hpp>
41 #include <com/sun/star/text/XFlatParagraph.hpp>
42 #include <com/sun/star/text/XFlatParagraphIterator.hpp>
43 #include <com/sun/star/uno/XComponentContext.hpp>
45 #include <sal/config.h>
46 #include <sal/log.hxx>
47 #include <o3tl/safeint.hxx>
48 #include <osl/conditn.hxx>
49 #include <cppuhelper/supportsservice.hxx>
50 #include <cppuhelper/weak.hxx>
51 #include <i18nlangtag/languagetag.hxx>
52 #include <comphelper/processfactory.hxx>
53 #include <comphelper/propertysequence.hxx>
54 #include <tools/debug.hxx>
55 #include <comphelper/diagnose_ex.hxx>
59 #include <linguistic/misc.hxx>
61 #include "gciterator.hxx"
63 using namespace linguistic
;
64 using namespace ::com::sun::star
;
66 // white space list: obtained from the fonts.config.txt of a Linux system.
67 const sal_Unicode aWhiteSpaces
[] =
70 0x00a0, /* NO-BREAK SPACE */
71 0x00ad, /* SOFT HYPHEN */
72 0x115f, /* HANGUL CHOSEONG FILLER */
73 0x1160, /* HANGUL JUNGSEONG FILLER */
74 0x1680, /* OGHAM SPACE MARK */
77 0x2002, /* EN SPACE */
78 0x2003, /* EM SPACE */
79 0x2004, /* THREE-PER-EM SPACE */
80 0x2005, /* FOUR-PER-EM SPACE */
81 0x2006, /* SIX-PER-EM SPACE */
82 0x2007, /* FIGURE SPACE */
83 0x2008, /* PUNCTUATION SPACE */
84 0x2009, /* THIN SPACE */
85 0x200a, /* HAIR SPACE */
86 0x200b, /* ZERO WIDTH SPACE */
87 0x200c, /* ZERO WIDTH NON-JOINER */
88 0x200d, /* ZERO WIDTH JOINER */
89 0x200e, /* LEFT-TO-RIGHT MARK */
90 0x200f, /* RIGHT-TO-LEFT MARK */
91 0x2028, /* LINE SEPARATOR */
92 0x2029, /* PARAGRAPH SEPARATOR */
93 0x202a, /* LEFT-TO-RIGHT EMBEDDING */
94 0x202b, /* RIGHT-TO-LEFT EMBEDDING */
95 0x202c, /* POP DIRECTIONAL FORMATTING */
96 0x202d, /* LEFT-TO-RIGHT OVERRIDE */
97 0x202e, /* RIGHT-TO-LEFT OVERRIDE */
98 0x202f, /* NARROW NO-BREAK SPACE */
99 0x205f, /* MEDIUM MATHEMATICAL SPACE */
100 0x2060, /* WORD JOINER */
101 0x2061, /* FUNCTION APPLICATION */
102 0x2062, /* INVISIBLE TIMES */
103 0x2063, /* INVISIBLE SEPARATOR */
104 0x206A, /* INHIBIT SYMMETRIC SWAPPING */
105 0x206B, /* ACTIVATE SYMMETRIC SWAPPING */
106 0x206C, /* INHIBIT ARABIC FORM SHAPING */
107 0x206D, /* ACTIVATE ARABIC FORM SHAPING */
108 0x206E, /* NATIONAL DIGIT SHAPES */
109 0x206F, /* NOMINAL DIGIT SHAPES */
110 0x3000, /* IDEOGRAPHIC SPACE */
111 0x3164, /* HANGUL FILLER */
112 0xfeff, /* ZERO WIDTH NO-BREAK SPACE */
113 0xffa0, /* HALFWIDTH HANGUL FILLER */
114 0xfff9, /* INTERLINEAR ANNOTATION ANCHOR */
115 0xfffa, /* INTERLINEAR ANNOTATION SEPARATOR */
116 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */
119 // Information about reason for proofreading (ProofInfo)
120 const sal_Int32 PROOFINFO_GET_PROOFRESULT
= 1;
121 const sal_Int32 PROOFINFO_MARK_PARAGRAPH
= 2;
123 const int nWhiteSpaces
= SAL_N_ELEMENTS( aWhiteSpaces
);
125 static bool lcl_IsWhiteSpace( sal_Unicode cChar
)
128 for (int i
= 0; i
< nWhiteSpaces
&& !bFound
; ++i
)
130 if (cChar
== aWhiteSpaces
[i
])
136 static sal_Int32
lcl_SkipWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
138 // note having nStartPos point right behind the string is OK since that one
139 // is a correct end-of-sentence position to be returned from a grammar checker...
141 const sal_Int32 nLen
= rText
.getLength();
142 bool bIllegalArgument
= false;
145 bIllegalArgument
= true;
148 if (nStartPos
> nLen
)
150 bIllegalArgument
= true;
153 if (bIllegalArgument
)
155 SAL_WARN( "linguistic", "lcl_SkipWhiteSpaces: illegal arguments" );
158 sal_Int32 nRes
= nStartPos
;
159 if (0 <= nStartPos
&& nStartPos
< nLen
)
161 const sal_Unicode
* const pEnd
= rText
.getStr() + nLen
;
162 const sal_Unicode
*pText
= rText
.getStr() + nStartPos
;
163 while (pText
!= pEnd
&& lcl_IsWhiteSpace(*pText
))
165 nRes
= pText
- rText
.getStr();
168 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_SkipWhiteSpaces return value out of range" );
172 static sal_Int32
lcl_BacktraceWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
174 // note: having nStartPos point right behind the string is OK since that one
175 // is a correct end-of-sentence position to be returned from a grammar checker...
177 const sal_Int32 nLen
= rText
.getLength();
178 bool bIllegalArgument
= false;
181 bIllegalArgument
= true;
184 if (nStartPos
> nLen
)
186 bIllegalArgument
= true;
189 if (bIllegalArgument
)
191 SAL_WARN( "linguistic", "lcl_BacktraceWhiteSpaces: illegal arguments" );
194 sal_Int32 nRes
= nStartPos
;
195 sal_Int32 nPosBefore
= nStartPos
- 1;
196 const sal_Unicode
*pStart
= rText
.getStr();
197 if (0 <= nPosBefore
&& nPosBefore
< nLen
&& lcl_IsWhiteSpace( pStart
[ nPosBefore
] ))
199 nStartPos
= nPosBefore
;
200 const sal_Unicode
*pText
= rText
.getStr() + nStartPos
;
201 while (pText
> pStart
&& lcl_IsWhiteSpace( *pText
))
203 // now add 1 since we want to point to the first char after the last char in the sentence...
204 nRes
= pText
- pStart
+ 1;
207 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_BacktraceWhiteSpaces return value out of range" );
214 static void lcl_workerfunc (void * gci
)
216 osl_setThreadName("GrammarCheckingIterator");
218 static_cast<GrammarCheckingIterator
*>(gci
)->DequeueAndCheck();
223 static lang::Locale
lcl_GetPrimaryLanguageOfSentence(
224 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
225 sal_Int32 nStartIndex
)
227 //get the language of the first word
228 return xFlatPara
->getLanguageOfText( nStartIndex
, 1 );
232 LngXStringKeyMap::LngXStringKeyMap() {}
234 void SAL_CALL
LngXStringKeyMap::insertValue(const OUString
& aKey
, const css::uno::Any
& aValue
)
236 std::map
<OUString
, css::uno::Any
>::const_iterator aIter
= maMap
.find(aKey
);
237 if (aIter
!= maMap
.end())
238 throw css::container::ElementExistException();
240 maMap
[aKey
] = aValue
;
243 css::uno::Any SAL_CALL
LngXStringKeyMap::getValue(const OUString
& aKey
)
245 std::map
<OUString
, css::uno::Any
>::const_iterator aIter
= maMap
.find(aKey
);
246 if (aIter
== maMap
.end())
247 throw css::container::NoSuchElementException();
249 return (*aIter
).second
;
252 sal_Bool SAL_CALL
LngXStringKeyMap::hasValue(const OUString
& aKey
)
254 return maMap
.find(aKey
) != maMap
.end();
257 ::sal_Int32 SAL_CALL
LngXStringKeyMap::getCount() { return maMap
.size(); }
259 OUString SAL_CALL
LngXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex
)
261 if (nIndex
< 0 || o3tl::make_unsigned(nIndex
) >= maMap
.size())
262 throw css::lang::IndexOutOfBoundsException();
267 css::uno::Any SAL_CALL
LngXStringKeyMap::getValueByIndex(::sal_Int32 nIndex
)
269 if (nIndex
< 0 || o3tl::make_unsigned(nIndex
) >= maMap
.size())
270 throw css::lang::IndexOutOfBoundsException();
272 return css::uno::Any();
276 osl::Mutex
& GrammarCheckingIterator::MyMutex()
278 static osl::Mutex SINGLETON
;
282 GrammarCheckingIterator::GrammarCheckingIterator() :
284 m_bGCServicesChecked( false ),
285 m_nDocIdCounter( 0 ),
287 m_aEventListeners( MyMutex() ),
288 m_aNotifyListeners( MyMutex() )
293 GrammarCheckingIterator::~GrammarCheckingIterator()
298 void GrammarCheckingIterator::TerminateThread()
302 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
306 m_aWakeUpThread
.set();
310 osl_joinWithThread(t
);
311 osl_destroyThread(t
);
313 // After m_bEnd was used to flag lcl_workerfunc to quit, now
314 // reset it so lcl_workerfunc could be relaunched later.
316 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
321 bool GrammarCheckingIterator::joinThreads()
328 sal_Int32
GrammarCheckingIterator::NextDocId()
330 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
331 m_nDocIdCounter
+= 1;
332 return m_nDocIdCounter
;
336 OUString
GrammarCheckingIterator::GetOrCreateDocId(
337 const uno::Reference
< lang::XComponent
> &xComponent
)
339 // internal method; will always be called with locked mutex
344 if (m_aDocIdMap
.find( xComponent
.get() ) != m_aDocIdMap
.end())
346 // return already existing entry
347 aRes
= m_aDocIdMap
[ xComponent
.get() ];
349 else // add new entry
351 sal_Int32 nRes
= NextDocId();
352 aRes
= OUString::number( nRes
);
353 m_aDocIdMap
[ xComponent
.get() ] = aRes
;
354 xComponent
->addEventListener( this );
361 void GrammarCheckingIterator::AddEntry(
362 const uno::Reference
< text::XFlatParagraphIterator
>& xFlatParaIterator
,
363 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
364 const OUString
& rDocId
,
365 sal_Int32 nStartIndex
,
368 // we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
369 // but we always need a xFlatPara...
374 aNewFPEntry
.m_xParaIterator
= xFlatParaIterator
;
375 aNewFPEntry
.m_xPara
= xFlatPara
;
376 aNewFPEntry
.m_aDocId
= rDocId
;
377 aNewFPEntry
.m_nStartIndex
= nStartIndex
;
378 aNewFPEntry
.m_bAutomatic
= bAutomatic
;
380 // add new entry to the end of this queue
381 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
383 m_thread
= osl_createThread( lcl_workerfunc
, this );
384 m_aFPEntriesQueue
.push_back( aNewFPEntry
);
386 // wake up the thread in order to do grammar checking
387 m_aWakeUpThread
.set();
391 void GrammarCheckingIterator::ProcessResult(
392 const linguistic2::ProofreadingResult
&rRes
,
393 const uno::Reference
< text::XFlatParagraphIterator
> &rxFlatParagraphIterator
,
394 bool bIsAutomaticChecking
)
396 DBG_ASSERT( rRes
.xFlatParagraph
.is(), "xFlatParagraph is missing" );
397 //no guard necessary as no members are used
398 bool bContinueWithNextPara
= false;
399 if (!rRes
.xFlatParagraph
.is() || rRes
.xFlatParagraph
->isModified())
401 // if paragraph was modified/deleted meanwhile continue with the next one...
402 bContinueWithNextPara
= true;
404 else // paragraph is still unchanged...
406 // mark found errors...
408 sal_Int32 nTextLen
= rRes
.aText
.getLength();
409 bool bBoundariesOk
= 0 <= rRes
.nStartOfSentencePosition
&& rRes
.nStartOfSentencePosition
<= nTextLen
&&
410 0 <= rRes
.nBehindEndOfSentencePosition
&& rRes
.nBehindEndOfSentencePosition
<= nTextLen
&&
411 0 <= rRes
.nStartOfNextSentencePosition
&& rRes
.nStartOfNextSentencePosition
<= nTextLen
&&
412 rRes
.nStartOfSentencePosition
<= rRes
.nBehindEndOfSentencePosition
&&
413 rRes
.nBehindEndOfSentencePosition
<= rRes
.nStartOfNextSentencePosition
;
414 DBG_ASSERT( bBoundariesOk
, "inconsistent sentence boundaries" );
416 uno::Reference
< text::XMultiTextMarkup
> xMulti( rRes
.xFlatParagraph
, uno::UNO_QUERY
);
417 if (xMulti
.is()) // use new API for markups
421 // length = number of found errors + 1 sentence markup
422 sal_Int32 nErrors
= rRes
.aErrors
.getLength();
423 uno::Sequence
< text::TextMarkupDescriptor
> aDescriptors( nErrors
+ 1 );
424 text::TextMarkupDescriptor
* pDescriptors
= aDescriptors
.getArray();
426 uno::Reference
< linguistic2::XDictionary
> xIgnoreAll
= ::GetIgnoreAllList();
427 sal_Int32 ignoredCount
= 0;
429 // at pos 0 .. nErrors-1 -> all grammar errors
430 for (const linguistic2::SingleProofreadingError
&rError
: rRes
.aErrors
)
432 OUString
word(rRes
.aText
.subView(rError
.nErrorStart
, rError
.nErrorLength
));
433 bool ignored
= xIgnoreAll
.is() && xIgnoreAll
->getEntry(word
).is();
437 text::TextMarkupDescriptor
&rDesc
= *pDescriptors
++;
439 rDesc
.nType
= rError
.nErrorType
;
440 rDesc
.nOffset
= rError
.nErrorStart
;
441 rDesc
.nLength
= rError
.nErrorLength
;
443 // the proofreader may return SPELLING but right now our core
444 // does only handle PROOFREADING if the result is from the proofreader...
445 // (later on we may wish to color spelling errors found by the proofreader
446 // differently for example. But no special handling right now.
447 if (rDesc
.nType
== text::TextMarkupType::SPELLCHECK
)
448 rDesc
.nType
= text::TextMarkupType::PROOFREADING
;
450 uno::Reference
< container::XStringKeyMap
> xKeyMap(new LngXStringKeyMap());
451 for( const beans::PropertyValue
& rProperty
: rError
.aProperties
)
453 if ( rProperty
.Name
== "LineColor" )
455 xKeyMap
->insertValue(rProperty
.Name
, rProperty
.Value
);
456 rDesc
.xMarkupInfoContainer
= xKeyMap
;
458 else if ( rProperty
.Name
== "LineType" )
460 xKeyMap
->insertValue(rProperty
.Name
, rProperty
.Value
);
461 rDesc
.xMarkupInfoContainer
= xKeyMap
;
469 if (ignoredCount
!= 0)
471 aDescriptors
.realloc(aDescriptors
.getLength() - ignoredCount
);
472 pDescriptors
= aDescriptors
.getArray();
473 pDescriptors
+= aDescriptors
.getLength() - 1;
476 // at pos nErrors -> sentence markup
477 // nSentenceLength: includes the white-spaces following the sentence end...
478 const sal_Int32 nSentenceLength
= rRes
.nStartOfNextSentencePosition
- rRes
.nStartOfSentencePosition
;
479 pDescriptors
->nType
= text::TextMarkupType::SENTENCE
;
480 pDescriptors
->nOffset
= rRes
.nStartOfSentencePosition
;
481 pDescriptors
->nLength
= nSentenceLength
;
483 xMulti
->commitMultiTextMarkup( aDescriptors
) ;
485 catch (lang::IllegalArgumentException
&)
487 TOOLS_WARN_EXCEPTION( "linguistic", "commitMultiTextMarkup" );
491 // other sentences left to be checked in this paragraph?
492 if (rRes
.nStartOfNextSentencePosition
< rRes
.aText
.getLength())
494 AddEntry( rxFlatParagraphIterator
, rRes
.xFlatParagraph
, rRes
.aDocumentIdentifier
, rRes
.nStartOfNextSentencePosition
, bIsAutomaticChecking
);
496 else // current paragraph finished
498 // set "already checked" flag for the current flat paragraph
499 if (rRes
.xFlatParagraph
.is())
500 rRes
.xFlatParagraph
->setChecked( text::TextMarkupType::PROOFREADING
, true );
502 bContinueWithNextPara
= true;
506 if (bContinueWithNextPara
)
508 // we need to continue with the next paragraph
509 if (rxFlatParagraphIterator
.is())
510 AddEntry(rxFlatParagraphIterator
, rxFlatParagraphIterator
->getNextPara(),
511 rRes
.aDocumentIdentifier
, 0, bIsAutomaticChecking
);
516 std::pair
<OUString
, std::optional
<OUString
>>
517 GrammarCheckingIterator::getServiceForLocale(const lang::Locale
& rLocale
) const
519 if (!rLocale
.Language
.isEmpty())
521 const OUString sBcp47
= LanguageTag::convertToBcp47(rLocale
, false);
522 GCImplNames_t::const_iterator
aLangIt(m_aGCImplNamesByLang
.find(sBcp47
));
523 if (aLangIt
!= m_aGCImplNamesByLang
.end())
524 return { aLangIt
->second
, {} };
526 for (const auto& sFallbackBcp47
: LanguageTag(rLocale
).getFallbackStrings(false))
528 aLangIt
= m_aGCImplNamesByLang
.find(sFallbackBcp47
);
529 if (aLangIt
!= m_aGCImplNamesByLang
.end())
530 return { aLangIt
->second
, sFallbackBcp47
};
538 uno::Reference
< linguistic2::XProofreader
> GrammarCheckingIterator::GetGrammarChecker(
539 lang::Locale
&rLocale
)
541 uno::Reference
< linguistic2::XProofreader
> xRes
;
543 // ---- THREAD SAFE START ----
544 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
546 // check supported locales for each grammarchecker if not already done
547 if (!m_bGCServicesChecked
)
549 GetConfiguredGCSvcs_Impl();
550 m_bGCServicesChecked
= true;
553 if (const auto [aSvcImplName
, oFallbackBcp47
] = getServiceForLocale(rLocale
);
554 !aSvcImplName
.isEmpty()) // matching configured language found?
557 rLocale
= LanguageTag::convertToLocale(*oFallbackBcp47
, false);
558 GCReferences_t::const_iterator
aImplNameIt( m_aGCReferencesByService
.find( aSvcImplName
) );
559 if (aImplNameIt
!= m_aGCReferencesByService
.end()) // matching impl name found?
561 xRes
= aImplNameIt
->second
;
563 else // the service is to be instantiated here for the first time...
567 const uno::Reference
< uno::XComponentContext
>& xContext( comphelper::getProcessComponentContext() );
568 uno::Reference
< linguistic2::XProofreader
> xGC(
569 xContext
->getServiceManager()->createInstanceWithContext(aSvcImplName
, xContext
),
570 uno::UNO_QUERY_THROW
);
571 uno::Reference
< linguistic2::XSupportedLocales
> xSuppLoc( xGC
, uno::UNO_QUERY_THROW
);
573 if (xSuppLoc
->hasLocale( rLocale
))
575 m_aGCReferencesByService
[ aSvcImplName
] = xGC
;
578 uno::Reference
< linguistic2::XLinguServiceEventBroadcaster
> xBC( xGC
, uno::UNO_QUERY
);
580 xBC
->addLinguServiceEventListener( this );
584 SAL_WARN( "linguistic", "grammar checker does not support required locale" );
587 catch (uno::Exception
&)
589 SAL_WARN( "linguistic", "instantiating grammar checker failed" );
593 else // not found - quite normal
595 SAL_INFO("linguistic", "No grammar checker found for \""
596 << LanguageTag::convertToBcp47(rLocale
, false) << "\"");
598 // ---- THREAD SAFE END ----
603 static uno::Sequence
<beans::PropertyValue
>
604 lcl_makeProperties(uno::Reference
<text::XFlatParagraph
> const& xFlatPara
, sal_Int32 nProofInfo
)
606 uno::Reference
<beans::XPropertySet
> const xProps(
607 xFlatPara
, uno::UNO_QUERY_THROW
);
608 css::uno::Any
a (nProofInfo
);
609 return comphelper::InitPropertySequence({
610 { "FieldPositions", xProps
->getPropertyValue(u
"FieldPositions"_ustr
) },
611 { "FootnotePositions", xProps
->getPropertyValue(u
"FootnotePositions"_ustr
) },
612 { "SortedTextId", xProps
->getPropertyValue(u
"SortedTextId"_ustr
) },
613 { "DocumentElementsCount", xProps
->getPropertyValue(u
"DocumentElementsCount"_ustr
) },
618 void GrammarCheckingIterator::DequeueAndCheck()
622 // ---- THREAD SAFE START ----
623 bool bQueueEmpty
= false;
625 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
630 bQueueEmpty
= m_aFPEntriesQueue
.empty();
632 // ---- THREAD SAFE END ----
636 uno::Reference
< text::XFlatParagraphIterator
> xFPIterator
;
637 uno::Reference
< text::XFlatParagraph
> xFlatPara
;
638 FPEntry aFPEntryItem
;
640 // ---- THREAD SAFE START ----
642 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
643 aFPEntryItem
= m_aFPEntriesQueue
.front();
644 xFPIterator
= aFPEntryItem
.m_xParaIterator
;
645 xFlatPara
= aFPEntryItem
.m_xPara
;
646 m_aCurCheckedDocId
= aFPEntryItem
.m_aDocId
;
647 aCurDocId
= m_aCurCheckedDocId
;
649 m_aFPEntriesQueue
.pop_front();
651 // ---- THREAD SAFE END ----
653 if (xFlatPara
.is() && xFPIterator
.is())
657 OUString
aCurTxt( xFlatPara
->getText() );
658 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, aFPEntryItem
.m_nStartIndex
);
660 const bool bModified
= xFlatPara
->isModified();
663 linguistic2::ProofreadingResult aRes
;
665 // ---- THREAD SAFE START ----
667 osl::ClearableMutexGuard
aGuard(MyMutex());
669 sal_Int32 nStartPos
= aFPEntryItem
.m_nStartIndex
;
670 sal_Int32 nSuggestedEnd
671 = GetSuggestedEndOfSentence(aCurTxt
, nStartPos
, aCurLocale
);
672 DBG_ASSERT((nSuggestedEnd
== 0 && aCurTxt
.isEmpty())
673 || nSuggestedEnd
> nStartPos
,
674 "nSuggestedEndOfSentencePos calculation failed?");
676 uno::Reference
<linguistic2::XProofreader
> xGC
=
677 GetGrammarChecker(aCurLocale
);
681 uno::Sequence
<beans::PropertyValue
> const aProps(
682 lcl_makeProperties(xFlatPara
, PROOFINFO_MARK_PARAGRAPH
));
683 aRes
= xGC
->doProofreading(aCurDocId
, aCurTxt
, aCurLocale
,
684 nStartPos
, nSuggestedEnd
, aProps
);
686 //!! work-around to prevent looping if the grammar checker
687 //!! failed to properly identify the sentence end
688 if (aRes
.nBehindEndOfSentencePosition
<= nStartPos
689 && aRes
.nBehindEndOfSentencePosition
!= nSuggestedEnd
)
693 "!! Grammarchecker failed to provide end of sentence !!");
694 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
697 aRes
.xFlatParagraph
= std::move(xFlatPara
);
698 aRes
.nStartOfSentencePosition
= nStartPos
;
702 // no grammar checker -> no error
703 // but we need to provide the data below in order to continue with the next sentence
704 aRes
.aDocumentIdentifier
= aCurDocId
;
705 aRes
.xFlatParagraph
= std::move(xFlatPara
);
706 aRes
.aText
= aCurTxt
;
707 aRes
.aLocale
= std::move(aCurLocale
);
708 aRes
.nStartOfSentencePosition
= nStartPos
;
709 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
711 aRes
.nStartOfNextSentencePosition
712 = lcl_SkipWhiteSpaces(aCurTxt
, aRes
.nBehindEndOfSentencePosition
);
713 aRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces(
714 aCurTxt
, aRes
.nStartOfNextSentencePosition
);
716 //guard has to be cleared as ProcessResult calls out of this class
718 // ---- THREAD SAFE END ----
719 ProcessResult( aRes
, xFPIterator
, aFPEntryItem
.m_bAutomatic
);
723 // the paragraph changed meanwhile... (and maybe is still edited)
724 // thus we simply continue to ask for the next to be checked.
725 uno::Reference
< text::XFlatParagraph
> xFlatParaNext( xFPIterator
->getNextPara() );
726 AddEntry( xFPIterator
, xFlatParaNext
, aCurDocId
, 0, aFPEntryItem
.m_bAutomatic
);
729 catch (css::uno::Exception
&)
731 TOOLS_WARN_EXCEPTION("linguistic", "GrammarCheckingIterator::DequeueAndCheck ignoring");
735 // ---- THREAD SAFE START ----
737 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
738 m_aCurCheckedDocId
.clear();
740 // ---- THREAD SAFE END ----
744 // ---- THREAD SAFE START ----
746 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
751 // Check queue state again
752 if (m_aFPEntriesQueue
.empty())
753 m_aWakeUpThread
.reset();
755 // ---- THREAD SAFE END ----
757 //if the queue is empty
758 // IMPORTANT: Don't call condition.wait() with locked
759 // mutex. Otherwise you would keep out other threads
760 // to add entries to the queue! A condition is thread-
762 m_aWakeUpThread
.wait();
768 void SAL_CALL
GrammarCheckingIterator::startProofreading(
769 const uno::Reference
< ::uno::XInterface
> & xDoc
,
770 const uno::Reference
< text::XFlatParagraphIteratorProvider
> & xIteratorProvider
)
772 // get paragraph to start checking with
773 const bool bAutomatic
= true;
774 uno::Reference
<text::XFlatParagraphIterator
> xFPIterator
= xIteratorProvider
->getFlatParagraphIterator(
775 text::TextMarkupType::PROOFREADING
, bAutomatic
);
776 uno::Reference
< text::XFlatParagraph
> xPara( xFPIterator
.is()? xFPIterator
->getFirstPara() : nullptr );
777 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
779 // ---- THREAD SAFE START ----
780 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
781 if (xPara
.is() && xComponent
.is())
783 OUString aDocId
= GetOrCreateDocId( xComponent
);
785 // create new entry and add it to queue
786 AddEntry( xFPIterator
, xPara
, aDocId
, 0, bAutomatic
);
788 // ---- THREAD SAFE END ----
792 linguistic2::ProofreadingResult SAL_CALL
GrammarCheckingIterator::checkSentenceAtPosition(
793 const uno::Reference
< uno::XInterface
>& xDoc
,
794 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
795 const OUString
& rText
,
797 sal_Int32 nStartOfSentencePos
,
798 sal_Int32 nSuggestedEndOfSentencePos
,
799 sal_Int32 nErrorPosInPara
)
801 // for the context menu...
803 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
804 const bool bDoCheck
= (xFlatPara
.is() && xComponent
.is() &&
805 ( nErrorPosInPara
< 0 || nErrorPosInPara
< rText
.getLength()));
808 return linguistic2::ProofreadingResult();
810 // iterate through paragraph until we find the sentence we are interested in
811 linguistic2::ProofreadingResult aTmpRes
;
812 sal_Int32 nStartPos
= nStartOfSentencePos
>= 0 ? nStartOfSentencePos
: 0;
817 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, nStartPos
);
818 sal_Int32 nOldStartOfSentencePos
= nStartPos
;
819 uno::Reference
< linguistic2::XProofreader
> xGC
;
822 // ---- THREAD SAFE START ----
824 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
825 aDocId
= GetOrCreateDocId( xComponent
);
826 nSuggestedEndOfSentencePos
= GetSuggestedEndOfSentence( rText
, nStartPos
, aCurLocale
);
827 DBG_ASSERT( nSuggestedEndOfSentencePos
> nStartPos
, "nSuggestedEndOfSentencePos calculation failed?" );
829 xGC
= GetGrammarChecker( aCurLocale
);
831 // ---- THREAD SAFE START ----
832 sal_Int32 nEndPos
= -1;
835 uno::Sequence
<beans::PropertyValue
> const aProps(
836 lcl_makeProperties(xFlatPara
, PROOFINFO_GET_PROOFRESULT
));
837 aTmpRes
= xGC
->doProofreading( aDocId
, rText
,
838 aCurLocale
, nStartPos
, nSuggestedEndOfSentencePos
, aProps
);
840 //!! work-around to prevent looping if the grammar checker
841 //!! failed to properly identify the sentence end
842 if (aTmpRes
.nBehindEndOfSentencePosition
<= nStartPos
)
844 SAL_WARN( "linguistic", "!! Grammarchecker failed to provide end of sentence !!" );
845 aTmpRes
.nBehindEndOfSentencePosition
= nSuggestedEndOfSentencePos
;
848 aTmpRes
.xFlatParagraph
= xFlatPara
;
849 aTmpRes
.nStartOfSentencePosition
= nStartPos
;
850 nEndPos
= aTmpRes
.nBehindEndOfSentencePosition
;
852 if ((nErrorPosInPara
< 0 || nStartPos
<= nErrorPosInPara
) && nErrorPosInPara
< nEndPos
)
855 if (nEndPos
== -1) // no result from grammar checker
856 nEndPos
= nSuggestedEndOfSentencePos
;
857 nStartPos
= lcl_SkipWhiteSpaces( rText
, nEndPos
);
858 aTmpRes
.nBehindEndOfSentencePosition
= nEndPos
;
859 aTmpRes
.nStartOfNextSentencePosition
= nStartPos
;
860 aTmpRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces( rText
, aTmpRes
.nStartOfNextSentencePosition
);
862 // prevent endless loop by forcefully advancing if needs be...
863 if (nStartPos
<= nOldStartOfSentencePos
)
865 SAL_WARN( "linguistic", "end-of-sentence detection failed?" );
866 nStartPos
= nOldStartOfSentencePos
+ 1;
869 while (!bFound
&& nStartPos
< rText
.getLength());
871 if (bFound
&& !xFlatPara
->isModified())
874 return linguistic2::ProofreadingResult();
877 sal_Int32
GrammarCheckingIterator::GetSuggestedEndOfSentence(
878 const OUString
&rText
,
879 sal_Int32 nSentenceStartPos
,
880 const lang::Locale
&rLocale
)
882 // internal method; will always be called with locked mutex
884 if (!m_xBreakIterator
.is())
886 const uno::Reference
< uno::XComponentContext
>& xContext
= ::comphelper::getProcessComponentContext();
887 m_xBreakIterator
= i18n::BreakIterator::create(xContext
);
889 sal_Int32 nTextLen
= rText
.getLength();
890 sal_Int32
nEndPosition(0);
891 sal_Int32 nTmpStartPos
= nSentenceStartPos
;
894 sal_Int32
const nPrevEndPosition(nEndPosition
);
895 nEndPosition
= nTextLen
;
896 if (nTmpStartPos
< nTextLen
)
898 nEndPosition
= m_xBreakIterator
->endOfSentence( rText
, nTmpStartPos
, rLocale
);
899 if (nEndPosition
<= nPrevEndPosition
)
901 // fdo#68750 if there's no progress at all then presumably
902 // there's no end of sentence in this paragraph so just
903 // set the end position to end of paragraph
904 nEndPosition
= nTextLen
;
907 if (nEndPosition
< 0)
908 nEndPosition
= nTextLen
;
912 while (nEndPosition
<= nSentenceStartPos
&& nEndPosition
< nTextLen
);
913 if (nEndPosition
> nTextLen
)
914 nEndPosition
= nTextLen
;
919 void SAL_CALL
GrammarCheckingIterator::resetIgnoreRules( )
921 for (auto const& elem
: m_aGCReferencesByService
)
923 uno::Reference
< linguistic2::XProofreader
> xGC(elem
.second
);
925 xGC
->resetIgnoreRules();
930 sal_Bool SAL_CALL
GrammarCheckingIterator::isProofreading(
931 const uno::Reference
< uno::XInterface
>& xDoc
)
933 // ---- THREAD SAFE START ----
934 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
938 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
941 // if the component was already used in one of the two calls to check text
942 // i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
943 // m_aDocIdMap unless the document already disposed.
944 // If it is not found then it is not yet being checked (or requested to being checked)
945 const DocMap_t::const_iterator
aIt( m_aDocIdMap
.find( xComponent
.get() ) );
946 if (aIt
!= m_aDocIdMap
.end())
948 // check in document is checked automatically in the background...
949 OUString aDocId
= aIt
->second
;
950 if (!m_aCurCheckedDocId
.isEmpty() && m_aCurCheckedDocId
== aDocId
)
952 // an entry for that document was dequeued and is currently being checked.
957 // we need to check if there is an entry for that document in the queue...
958 // That is the document is going to be checked sooner or later.
960 sal_Int32 nSize
= m_aFPEntriesQueue
.size();
961 for (sal_Int32 i
= 0; i
< nSize
&& !bRes
; ++i
)
963 if (aDocId
== m_aFPEntriesQueue
[i
].m_aDocId
)
969 // ---- THREAD SAFE END ----
975 void SAL_CALL
GrammarCheckingIterator::processLinguServiceEvent(
976 const linguistic2::LinguServiceEvent
& rLngSvcEvent
)
978 if (rLngSvcEvent
.nEvent
!= linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
)
983 uno::Reference
< uno::XInterface
> xThis( getXWeak() );
984 linguistic2::LinguServiceEvent
aEvent( xThis
, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
);
985 m_aNotifyListeners
.notifyEach(
986 &linguistic2::XLinguServiceEventListener::processLinguServiceEvent
,
989 catch (uno::RuntimeException
&)
993 catch (const ::uno::Exception
&)
996 TOOLS_WARN_EXCEPTION("linguistic", "processLinguServiceEvent");
1001 sal_Bool SAL_CALL
GrammarCheckingIterator::addLinguServiceEventListener(
1002 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
1006 m_aNotifyListeners
.addInterface( xListener
);
1012 sal_Bool SAL_CALL
GrammarCheckingIterator::removeLinguServiceEventListener(
1013 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
1017 m_aNotifyListeners
.removeInterface( xListener
);
1023 void SAL_CALL
GrammarCheckingIterator::dispose()
1025 lang::EventObject
aEvt( static_cast<linguistic2::XProofreadingIterator
*>(this) );
1026 m_aEventListeners
.disposeAndClear( aEvt
);
1030 // ---- THREAD SAFE START ----
1032 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
1034 // release all UNO references
1036 m_xBreakIterator
.clear();
1038 // clear containers with UNO references AND have those references released
1039 GCReferences_t aTmpEmpty1
;
1040 DocMap_t aTmpEmpty2
;
1041 FPQueue_t aTmpEmpty3
;
1042 m_aGCReferencesByService
.swap( aTmpEmpty1
);
1043 m_aDocIdMap
.swap( aTmpEmpty2
);
1044 m_aFPEntriesQueue
.swap( aTmpEmpty3
);
1046 // ---- THREAD SAFE END ----
1050 void SAL_CALL
GrammarCheckingIterator::addEventListener(
1051 const uno::Reference
< lang::XEventListener
>& xListener
)
1055 m_aEventListeners
.addInterface( xListener
);
1060 void SAL_CALL
GrammarCheckingIterator::removeEventListener(
1061 const uno::Reference
< lang::XEventListener
>& xListener
)
1065 m_aEventListeners
.removeInterface( xListener
);
1070 void SAL_CALL
GrammarCheckingIterator::disposing( const lang::EventObject
&rSource
)
1072 // if the component (document) is disposing release all references
1073 //!! There is no need to remove entries from the queue that are from this document
1074 //!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
1075 //!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
1076 //!! And if an entry is currently checked by a grammar checker upon return the results
1077 //!! should be ignored.
1078 //!! Also GetOrCreateDocId will not use that very same Id again...
1079 //!! All of the above resulting in that we only have to get rid of the implementation pointer here.
1080 uno::Reference
< lang::XComponent
> xDoc( rSource
.Source
, uno::UNO_QUERY
);
1083 // ---- THREAD SAFE START ----
1084 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
1085 m_aDocIdMap
.erase( xDoc
.get() );
1086 // ---- THREAD SAFE END ----
1091 uno::Reference
< util::XChangesBatch
> const & GrammarCheckingIterator::GetUpdateAccess() const
1093 if (!m_xUpdateAccess
.is())
1097 // get configuration provider
1098 const uno::Reference
< uno::XComponentContext
>& xContext
= comphelper::getProcessComponentContext();
1099 uno::Reference
< lang::XMultiServiceFactory
> xConfigurationProvider
=
1100 configuration::theDefaultProvider::get( xContext
);
1102 // get configuration update access
1103 beans::PropertyValue aValue
;
1104 aValue
.Name
= "nodepath";
1105 aValue
.Value
<<= u
"org.openoffice.Office.Linguistic/ServiceManager"_ustr
;
1106 uno::Sequence
< uno::Any
> aProps
{ uno::Any(aValue
) };
1107 m_xUpdateAccess
.set(
1108 xConfigurationProvider
->createInstanceWithArguments(
1109 u
"com.sun.star.configuration.ConfigurationUpdateAccess"_ustr
, aProps
),
1110 uno::UNO_QUERY_THROW
);
1112 catch (uno::Exception
&)
1117 return m_xUpdateAccess
;
1121 void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
1123 GCImplNames_t aTmpGCImplNamesByLang
;
1127 // get node names (locale iso strings) for configured grammar checkers
1128 uno::Reference
< container::XNameAccess
> xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW
);
1129 xNA
.set( xNA
->getByName( u
"GrammarCheckerList"_ustr
), uno::UNO_QUERY_THROW
);
1130 const uno::Sequence
< OUString
> aElementNames( xNA
->getElementNames() );
1132 for (const OUString
& rElementName
: aElementNames
)
1134 uno::Sequence
< OUString
> aImplNames
;
1135 uno::Any
aTmp( xNA
->getByName( rElementName
) );
1136 if (aTmp
>>= aImplNames
)
1138 if (aImplNames
.hasElements())
1140 // only the first entry is used, there should be only one grammar checker per language
1141 aTmpGCImplNamesByLang
[rElementName
] = aImplNames
[0];
1146 SAL_WARN( "linguistic", "failed to get aImplNames. Wrong type?" );
1150 catch (uno::Exception
const &)
1152 TOOLS_WARN_EXCEPTION( "linguistic", "exception caught. Failed to get configured services" );
1156 // ---- THREAD SAFE START ----
1157 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
1158 m_aGCImplNamesByLang
.swap(aTmpGCImplNamesByLang
);
1159 // ---- THREAD SAFE END ----
1164 sal_Bool SAL_CALL
GrammarCheckingIterator::supportsService(
1165 const OUString
& rServiceName
)
1167 return cppu::supportsService(this, rServiceName
);
1171 OUString SAL_CALL
GrammarCheckingIterator::getImplementationName( )
1173 return u
"com.sun.star.lingu2.ProofreadingIterator"_ustr
;
1177 uno::Sequence
< OUString
> SAL_CALL
GrammarCheckingIterator::getSupportedServiceNames( )
1179 return { u
"com.sun.star.linguistic2.ProofreadingIterator"_ustr
};
1183 void GrammarCheckingIterator::SetServiceList(
1184 const lang::Locale
&rLocale
,
1185 const uno::Sequence
< OUString
> &rSvcImplNames
)
1187 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
1189 OUString sBcp47
= LanguageTag::convertToBcp47(rLocale
, false);
1191 if (rSvcImplNames
.hasElements())
1192 aImplName
= rSvcImplNames
[0]; // there is only one grammar checker per language
1194 if (!LinguIsUnspecified(sBcp47
) && !sBcp47
.isEmpty())
1196 if (!aImplName
.isEmpty())
1197 m_aGCImplNamesByLang
[sBcp47
] = aImplName
;
1199 m_aGCImplNamesByLang
.erase(sBcp47
);
1204 uno::Sequence
< OUString
> GrammarCheckingIterator::GetServiceList(
1205 const lang::Locale
&rLocale
) const
1207 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex() );
1209 const OUString aImplName
= getServiceForLocale(rLocale
).first
; // there is only one grammar checker per language
1211 if (!aImplName
.isEmpty())
1212 return { aImplName
};
1217 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
1218 linguistic_GrammarCheckingIterator_get_implementation(
1219 css::uno::XComponentContext
* , css::uno::Sequence
<css::uno::Any
> const&)
1221 return cppu::acquire(new GrammarCheckingIterator());
1226 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */