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/container/XContentEnumerationAccess.hpp>
22 #include <com/sun/star/container/XEnumeration.hpp>
23 #include <com/sun/star/container/XNameAccess.hpp>
24 #include <com/sun/star/container/XNameContainer.hpp>
25 #include <com/sun/star/container/XNameReplace.hpp>
26 #include <com/sun/star/configuration/theDefaultProvider.hpp>
27 #include <com/sun/star/i18n/BreakIterator.hpp>
28 #include <com/sun/star/lang/XComponent.hpp>
29 #include <com/sun/star/lang/XServiceInfo.hpp>
30 #include <com/sun/star/lang/XMultiServiceFactory.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/registry/XRegistryKey.hpp>
39 #include <com/sun/star/text/TextMarkupType.hpp>
40 #include <com/sun/star/text/TextMarkupDescriptor.hpp>
41 #include <com/sun/star/text/XTextMarkup.hpp>
42 #include <com/sun/star/text/XMultiTextMarkup.hpp>
43 #include <com/sun/star/text/XFlatParagraph.hpp>
44 #include <com/sun/star/text/XFlatParagraphIterator.hpp>
45 #include <com/sun/star/uno/XComponentContext.hpp>
46 #include <com/sun/star/lang/XSingleComponentFactory.hpp>
47 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
49 #include <sal/config.h>
50 #include <sal/log.hxx>
51 #include <osl/conditn.hxx>
52 #include <osl/thread.hxx>
53 #include <cppuhelper/implementationentry.hxx>
54 #include <cppuhelper/interfacecontainer.h>
55 #include <cppuhelper/factory.hxx>
56 #include <cppuhelper/supportsservice.hxx>
57 #include <i18nlangtag/languagetag.hxx>
58 #include <comphelper/processfactory.hxx>
59 #include <comphelper/propertysequence.hxx>
60 #include <tools/debug.hxx>
61 #include <tools/diagnose_ex.h>
67 #include <linguistic/misc.hxx>
72 #include "gciterator.hxx"
74 using namespace linguistic
;
75 using namespace ::com::sun::star
;
77 static OUString
GrammarCheckingIterator_getImplementationName() throw();
78 static uno::Sequence
< OUString
> GrammarCheckingIterator_getSupportedServiceNames() throw();
80 // white space list: obtained from the fonts.config.txt of a Linux system.
81 static const sal_Unicode aWhiteSpaces
[] =
84 0x00a0, /* NO-BREAK SPACE */
85 0x00ad, /* SOFT HYPHEN */
86 0x115f, /* HANGUL CHOSEONG FILLER */
87 0x1160, /* HANGUL JUNGSEONG FILLER */
88 0x1680, /* OGHAM SPACE MARK */
91 0x2002, /* EN SPACE */
92 0x2003, /* EM SPACE */
93 0x2004, /* THREE-PER-EM SPACE */
94 0x2005, /* FOUR-PER-EM SPACE */
95 0x2006, /* SIX-PER-EM SPACE */
96 0x2007, /* FIGURE SPACE */
97 0x2008, /* PUNCTUATION SPACE */
98 0x2009, /* THIN SPACE */
99 0x200a, /* HAIR SPACE */
100 0x200b, /* ZERO WIDTH SPACE */
101 0x200c, /* ZERO WIDTH NON-JOINER */
102 0x200d, /* ZERO WIDTH JOINER */
103 0x200e, /* LEFT-TO-RIGHT MARK */
104 0x200f, /* RIGHT-TO-LEFT MARK */
105 0x2028, /* LINE SEPARATOR */
106 0x2029, /* PARAGRAPH SEPARATOR */
107 0x202a, /* LEFT-TO-RIGHT EMBEDDING */
108 0x202b, /* RIGHT-TO-LEFT EMBEDDING */
109 0x202c, /* POP DIRECTIONAL FORMATTING */
110 0x202d, /* LEFT-TO-RIGHT OVERRIDE */
111 0x202e, /* RIGHT-TO-LEFT OVERRIDE */
112 0x202f, /* NARROW NO-BREAK SPACE */
113 0x205f, /* MEDIUM MATHEMATICAL SPACE */
114 0x2060, /* WORD JOINER */
115 0x2061, /* FUNCTION APPLICATION */
116 0x2062, /* INVISIBLE TIMES */
117 0x2063, /* INVISIBLE SEPARATOR */
118 0x206A, /* INHIBIT SYMMETRIC SWAPPING */
119 0x206B, /* ACTIVATE SYMMETRIC SWAPPING */
120 0x206C, /* INHIBIT ARABIC FORM SHAPING */
121 0x206D, /* ACTIVATE ARABIC FORM SHAPING */
122 0x206E, /* NATIONAL DIGIT SHAPES */
123 0x206F, /* NOMINAL DIGIT SHAPES */
124 0x3000, /* IDEOGRAPHIC SPACE */
125 0x3164, /* HANGUL FILLER */
126 0xfeff, /* ZERO WIDTH NO-BREAK SPACE */
127 0xffa0, /* HALFWIDTH HANGUL FILLER */
128 0xfff9, /* INTERLINEAR ANNOTATION ANCHOR */
129 0xfffa, /* INTERLINEAR ANNOTATION SEPARATOR */
130 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */
133 static const int nWhiteSpaces
= SAL_N_ELEMENTS( aWhiteSpaces
);
135 static bool lcl_IsWhiteSpace( sal_Unicode cChar
)
138 for (int i
= 0; i
< nWhiteSpaces
&& !bFound
; ++i
)
140 if (cChar
== aWhiteSpaces
[i
])
146 static sal_Int32
lcl_SkipWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
148 // note having nStartPos point right behind the string is OK since that one
149 // is a correct end-of-sentence position to be returned from a grammar checker...
151 const sal_Int32 nLen
= rText
.getLength();
152 bool bIllegalArgument
= false;
155 bIllegalArgument
= true;
158 if (nStartPos
> nLen
)
160 bIllegalArgument
= true;
163 if (bIllegalArgument
)
165 SAL_WARN( "linguistic", "lcl_SkipWhiteSpaces: illegal arguments" );
168 sal_Int32 nRes
= nStartPos
;
169 if (0 <= nStartPos
&& nStartPos
< nLen
)
171 const sal_Unicode
* const pEnd
= rText
.getStr() + nLen
;
172 const sal_Unicode
*pText
= rText
.getStr() + nStartPos
;
173 while (pText
!= pEnd
&& lcl_IsWhiteSpace(*pText
))
175 nRes
= pText
- rText
.getStr();
178 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_SkipWhiteSpaces return value out of range" );
182 static sal_Int32
lcl_BacktraceWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
184 // note: having nStartPos point right behind the string is OK since that one
185 // is a correct end-of-sentence position to be returned from a grammar checker...
187 const sal_Int32 nLen
= rText
.getLength();
188 bool bIllegalArgument
= false;
191 bIllegalArgument
= true;
194 if (nStartPos
> nLen
)
196 bIllegalArgument
= true;
199 if (bIllegalArgument
)
201 SAL_WARN( "linguistic", "lcl_BacktraceWhiteSpaces: illegal arguments" );
204 sal_Int32 nRes
= nStartPos
;
205 sal_Int32 nPosBefore
= nStartPos
- 1;
206 const sal_Unicode
*pStart
= rText
.getStr();
207 if (0 <= nPosBefore
&& nPosBefore
< nLen
&& lcl_IsWhiteSpace( pStart
[ nPosBefore
] ))
209 nStartPos
= nPosBefore
;
210 const sal_Unicode
*pText
= rText
.getStr() + nStartPos
;
211 while (pText
> pStart
&& lcl_IsWhiteSpace( *pText
))
213 // now add 1 since we want to point to the first char after the last char in the sentence...
214 nRes
= pText
- pStart
+ 1;
217 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_BacktraceWhiteSpaces return value out of range" );
224 static void lcl_workerfunc (void * gci
)
226 osl_setThreadName("GrammarCheckingIterator");
228 static_cast<GrammarCheckingIterator
*>(gci
)->DequeueAndCheck();
233 static lang::Locale
lcl_GetPrimaryLanguageOfSentence(
234 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
235 sal_Int32 nStartIndex
)
237 //get the language of the first word
238 return xFlatPara
->getLanguageOfText( nStartIndex
, 1 );
242 LngXStringKeyMap::LngXStringKeyMap() {}
244 void SAL_CALL
LngXStringKeyMap::insertValue(const OUString
& aKey
, const css::uno::Any
& aValue
)
246 std::map
<OUString
, css::uno::Any
>::const_iterator aIter
= maMap
.find(aKey
);
247 if (aIter
!= maMap
.end())
248 throw css::container::ElementExistException();
250 maMap
[aKey
] = aValue
;
253 css::uno::Any SAL_CALL
LngXStringKeyMap::getValue(const OUString
& aKey
)
255 std::map
<OUString
, css::uno::Any
>::const_iterator aIter
= maMap
.find(aKey
);
256 if (aIter
== maMap
.end())
257 throw css::container::NoSuchElementException();
259 return (*aIter
).second
;
262 sal_Bool SAL_CALL
LngXStringKeyMap::hasValue(const OUString
& aKey
)
264 return maMap
.find(aKey
) != maMap
.end();
267 ::sal_Int32 SAL_CALL
LngXStringKeyMap::getCount() { return maMap
.size(); }
269 OUString SAL_CALL
LngXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex
)
271 if (static_cast<sal_uInt32
>(nIndex
) >= maMap
.size())
272 throw css::lang::IndexOutOfBoundsException();
277 css::uno::Any SAL_CALL
LngXStringKeyMap::getValueByIndex(::sal_Int32 nIndex
)
279 if (static_cast<sal_uInt32
>(nIndex
) >= maMap
.size())
280 throw css::lang::IndexOutOfBoundsException();
282 return css::uno::Any();
286 GrammarCheckingIterator::GrammarCheckingIterator() :
288 m_aCurCheckedDocId(),
289 m_bGCServicesChecked( false ),
290 m_nDocIdCounter( 0 ),
291 m_aEventListeners( MyMutex::get() ),
292 m_aNotifyListeners( MyMutex::get() )
298 GrammarCheckingIterator::~GrammarCheckingIterator()
303 void GrammarCheckingIterator::TerminateThread()
307 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
311 m_aWakeUpThread
.set();
315 osl_joinWithThread(t
);
316 osl_destroyThread(t
);
320 sal_Int32
GrammarCheckingIterator::NextDocId()
322 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
323 m_nDocIdCounter
+= 1;
324 return m_nDocIdCounter
;
328 OUString
GrammarCheckingIterator::GetOrCreateDocId(
329 const uno::Reference
< lang::XComponent
> &xComponent
)
331 // internal method; will always be called with locked mutex
336 if (m_aDocIdMap
.find( xComponent
.get() ) != m_aDocIdMap
.end())
338 // return already existing entry
339 aRes
= m_aDocIdMap
[ xComponent
.get() ];
341 else // add new entry
343 sal_Int32 nRes
= NextDocId();
344 aRes
= OUString::number( nRes
);
345 m_aDocIdMap
[ xComponent
.get() ] = aRes
;
346 xComponent
->addEventListener( this );
353 void GrammarCheckingIterator::AddEntry(
354 const uno::WeakReference
< text::XFlatParagraphIterator
>& xFlatParaIterator
,
355 const uno::WeakReference
< text::XFlatParagraph
>& xFlatPara
,
356 const OUString
& rDocId
,
357 sal_Int32 nStartIndex
,
360 // we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
361 // but we always need a xFlatPara...
362 uno::Reference
< text::XFlatParagraph
> xPara( xFlatPara
);
366 aNewFPEntry
.m_xParaIterator
= xFlatParaIterator
;
367 aNewFPEntry
.m_xPara
= xFlatPara
;
368 aNewFPEntry
.m_aDocId
= rDocId
;
369 aNewFPEntry
.m_nStartIndex
= nStartIndex
;
370 aNewFPEntry
.m_bAutomatic
= bAutomatic
;
372 // add new entry to the end of this queue
373 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
375 m_thread
= osl_createThread( lcl_workerfunc
, this );
376 m_aFPEntriesQueue
.push_back( aNewFPEntry
);
378 // wake up the thread in order to do grammar checking
379 m_aWakeUpThread
.set();
384 void GrammarCheckingIterator::ProcessResult(
385 const linguistic2::ProofreadingResult
&rRes
,
386 const uno::Reference
< text::XFlatParagraphIterator
> &rxFlatParagraphIterator
,
387 bool bIsAutomaticChecking
)
389 DBG_ASSERT( rRes
.xFlatParagraph
.is(), "xFlatParagraph is missing" );
390 //no guard necessary as no members are used
391 bool bContinueWithNextPara
= false;
392 if (!rRes
.xFlatParagraph
.is() || rRes
.xFlatParagraph
->isModified())
394 // if paragraph was modified/deleted meanwhile continue with the next one...
395 bContinueWithNextPara
= true;
397 else // paragraph is still unchanged...
399 // mark found errors...
401 sal_Int32 nTextLen
= rRes
.aText
.getLength();
402 bool bBoundariesOk
= 0 <= rRes
.nStartOfSentencePosition
&& rRes
.nStartOfSentencePosition
<= nTextLen
&&
403 0 <= rRes
.nBehindEndOfSentencePosition
&& rRes
.nBehindEndOfSentencePosition
<= nTextLen
&&
404 0 <= rRes
.nStartOfNextSentencePosition
&& rRes
.nStartOfNextSentencePosition
<= nTextLen
&&
405 rRes
.nStartOfSentencePosition
<= rRes
.nBehindEndOfSentencePosition
&&
406 rRes
.nBehindEndOfSentencePosition
<= rRes
.nStartOfNextSentencePosition
;
407 DBG_ASSERT( bBoundariesOk
, "inconsistent sentence boundaries" );
409 uno::Reference
< text::XMultiTextMarkup
> xMulti( rRes
.xFlatParagraph
, uno::UNO_QUERY
);
410 if (xMulti
.is()) // use new API for markups
414 // length = number of found errors + 1 sentence markup
415 sal_Int32 nErrors
= rRes
.aErrors
.getLength();
416 uno::Sequence
< text::TextMarkupDescriptor
> aDescriptors( nErrors
+ 1 );
417 text::TextMarkupDescriptor
* pDescriptors
= aDescriptors
.getArray();
419 // at pos 0 .. nErrors-1 -> all grammar errors
420 for (const linguistic2::SingleProofreadingError
&rError
: rRes
.aErrors
)
422 text::TextMarkupDescriptor
&rDesc
= *pDescriptors
++;
424 rDesc
.nType
= rError
.nErrorType
;
425 rDesc
.nOffset
= rError
.nErrorStart
;
426 rDesc
.nLength
= rError
.nErrorLength
;
428 // the proofreader may return SPELLING but right now our core
429 // does only handle PROOFREADING if the result is from the proofreader...
430 // (later on we may wish to color spelling errors found by the proofreader
431 // differently for example. But no special handling right now.
432 if (rDesc
.nType
== text::TextMarkupType::SPELLCHECK
)
433 rDesc
.nType
= text::TextMarkupType::PROOFREADING
;
435 uno::Reference
< container::XStringKeyMap
> xKeyMap(
436 new LngXStringKeyMap());
437 for( const beans::PropertyValue
& rProperty
: rError
.aProperties
)
439 if ( rProperty
.Name
== "LineColor" )
441 xKeyMap
->insertValue(rProperty
.Name
,
443 rDesc
.xMarkupInfoContainer
= xKeyMap
;
445 else if ( rProperty
.Name
== "LineType" )
447 xKeyMap
->insertValue(rProperty
.Name
,
449 rDesc
.xMarkupInfoContainer
= xKeyMap
;
454 // at pos nErrors -> sentence markup
455 // nSentenceLength: includes the white-spaces following the sentence end...
456 const sal_Int32 nSentenceLength
= rRes
.nStartOfNextSentencePosition
- rRes
.nStartOfSentencePosition
;
457 pDescriptors
->nType
= text::TextMarkupType::SENTENCE
;
458 pDescriptors
->nOffset
= rRes
.nStartOfSentencePosition
;
459 pDescriptors
->nLength
= nSentenceLength
;
461 xMulti
->commitMultiTextMarkup( aDescriptors
) ;
463 catch (lang::IllegalArgumentException
&)
465 OSL_FAIL( "commitMultiTextMarkup: IllegalArgumentException exception caught" );
469 // other sentences left to be checked in this paragraph?
470 if (rRes
.nStartOfNextSentencePosition
< rRes
.aText
.getLength())
472 AddEntry( rxFlatParagraphIterator
, rRes
.xFlatParagraph
, rRes
.aDocumentIdentifier
, rRes
.nStartOfNextSentencePosition
, bIsAutomaticChecking
);
474 else // current paragraph finished
476 // set "already checked" flag for the current flat paragraph
477 if (rRes
.xFlatParagraph
.is())
478 rRes
.xFlatParagraph
->setChecked( text::TextMarkupType::PROOFREADING
, true );
480 bContinueWithNextPara
= true;
484 if (bContinueWithNextPara
)
486 // we need to continue with the next paragraph
487 uno::Reference
< text::XFlatParagraph
> xFlatParaNext
;
488 if (rxFlatParagraphIterator
.is())
489 xFlatParaNext
= rxFlatParagraphIterator
->getNextPara();
491 AddEntry( rxFlatParagraphIterator
, xFlatParaNext
, rRes
.aDocumentIdentifier
, 0, bIsAutomaticChecking
);
497 uno::Reference
< linguistic2::XProofreader
> GrammarCheckingIterator::GetGrammarChecker(
498 const lang::Locale
&rLocale
)
500 uno::Reference
< linguistic2::XProofreader
> xRes
;
502 // ---- THREAD SAFE START ----
503 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
505 // check supported locales for each grammarchecker if not already done
506 if (!m_bGCServicesChecked
)
508 GetConfiguredGCSvcs_Impl();
509 m_bGCServicesChecked
= true;
512 const LanguageType nLang
= LanguageTag::convertToLanguageType( rLocale
, false);
513 GCImplNames_t::const_iterator
aLangIt( m_aGCImplNamesByLang
.find( nLang
) );
514 if (aLangIt
!= m_aGCImplNamesByLang
.end()) // matching configured language found?
516 OUString
aSvcImplName( aLangIt
->second
);
517 GCReferences_t::const_iterator
aImplNameIt( m_aGCReferencesByService
.find( aSvcImplName
) );
518 if (aImplNameIt
!= m_aGCReferencesByService
.end()) // matching impl name found?
520 xRes
= aImplNameIt
->second
;
522 else // the service is to be instantiated here for the first time...
526 uno::Reference
< uno::XComponentContext
> xContext( comphelper::getProcessComponentContext() );
527 uno::Reference
< linguistic2::XProofreader
> xGC(
528 xContext
->getServiceManager()->createInstanceWithContext(aSvcImplName
, xContext
),
529 uno::UNO_QUERY_THROW
);
530 uno::Reference
< linguistic2::XSupportedLocales
> xSuppLoc( xGC
, uno::UNO_QUERY_THROW
);
532 if (xSuppLoc
->hasLocale( rLocale
))
534 m_aGCReferencesByService
[ aSvcImplName
] = xGC
;
537 uno::Reference
< linguistic2::XLinguServiceEventBroadcaster
> xBC( xGC
, uno::UNO_QUERY
);
539 xBC
->addLinguServiceEventListener( this );
543 SAL_WARN( "linguistic", "grammar checker does not support required locale" );
546 catch (uno::Exception
&)
548 SAL_WARN( "linguistic", "instantiating grammar checker failed" );
552 // ---- THREAD SAFE END ----
557 static uno::Sequence
<beans::PropertyValue
>
558 lcl_makeProperties(uno::Reference
<text::XFlatParagraph
> const& xFlatPara
)
560 uno::Reference
<beans::XPropertySet
> const xProps(
561 xFlatPara
, uno::UNO_QUERY_THROW
);
562 return comphelper::InitPropertySequence({
563 { "FieldPositions", xProps
->getPropertyValue("FieldPositions") },
564 { "FootnotePositions", xProps
->getPropertyValue("FootnotePositions") }
568 void GrammarCheckingIterator::DequeueAndCheck()
572 // ---- THREAD SAFE START ----
573 bool bQueueEmpty
= false;
575 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
580 bQueueEmpty
= m_aFPEntriesQueue
.empty();
582 // ---- THREAD SAFE END ----
586 uno::Reference
< text::XFlatParagraphIterator
> xFPIterator
;
587 uno::Reference
< text::XFlatParagraph
> xFlatPara
;
588 FPEntry aFPEntryItem
;
590 // ---- THREAD SAFE START ----
592 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
593 aFPEntryItem
= m_aFPEntriesQueue
.front();
594 xFPIterator
= aFPEntryItem
.m_xParaIterator
;
595 xFlatPara
= aFPEntryItem
.m_xPara
;
596 m_aCurCheckedDocId
= aFPEntryItem
.m_aDocId
;
597 aCurDocId
= m_aCurCheckedDocId
;
599 m_aFPEntriesQueue
.pop_front();
601 // ---- THREAD SAFE END ----
603 if (xFlatPara
.is() && xFPIterator
.is())
607 OUString
aCurTxt( xFlatPara
->getText() );
608 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, aFPEntryItem
.m_nStartIndex
);
610 const bool bModified
= xFlatPara
->isModified();
613 linguistic2::ProofreadingResult aRes
;
615 // ---- THREAD SAFE START ----
617 osl::ClearableMutexGuard
aGuard(MyMutex::get());
619 sal_Int32 nStartPos
= aFPEntryItem
.m_nStartIndex
;
620 sal_Int32 nSuggestedEnd
621 = GetSuggestedEndOfSentence(aCurTxt
, nStartPos
, aCurLocale
);
622 DBG_ASSERT((nSuggestedEnd
== 0 && aCurTxt
.isEmpty())
623 || nSuggestedEnd
> nStartPos
,
624 "nSuggestedEndOfSentencePos calculation failed?");
626 uno::Reference
<linguistic2::XProofreader
> xGC
=
627 GetGrammarChecker(aCurLocale
);
631 uno::Sequence
<beans::PropertyValue
> const aProps(
632 lcl_makeProperties(xFlatPara
));
633 aRes
= xGC
->doProofreading(aCurDocId
, aCurTxt
, aCurLocale
,
634 nStartPos
, nSuggestedEnd
, aProps
);
636 //!! work-around to prevent looping if the grammar checker
637 //!! failed to properly identify the sentence end
638 if (aRes
.nBehindEndOfSentencePosition
<= nStartPos
639 && aRes
.nBehindEndOfSentencePosition
!= nSuggestedEnd
)
643 "!! Grammarchecker failed to provide end of sentence !!");
644 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
647 aRes
.xFlatParagraph
= xFlatPara
;
648 aRes
.nStartOfSentencePosition
= nStartPos
;
652 // no grammar checker -> no error
653 // but we need to provide the data below in order to continue with the next sentence
654 aRes
.aDocumentIdentifier
= aCurDocId
;
655 aRes
.xFlatParagraph
= xFlatPara
;
656 aRes
.aText
= aCurTxt
;
657 aRes
.aLocale
= aCurLocale
;
658 aRes
.nStartOfSentencePosition
= nStartPos
;
659 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
661 aRes
.nStartOfNextSentencePosition
662 = lcl_SkipWhiteSpaces(aCurTxt
, aRes
.nBehindEndOfSentencePosition
);
663 aRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces(
664 aCurTxt
, aRes
.nStartOfNextSentencePosition
);
666 //guard has to be cleared as ProcessResult calls out of this class
668 // ---- THREAD SAFE END ----
669 ProcessResult( aRes
, xFPIterator
, aFPEntryItem
.m_bAutomatic
);
673 // the paragraph changed meanwhile... (and maybe is still edited)
674 // thus we simply continue to ask for the next to be checked.
675 uno::Reference
< text::XFlatParagraph
> xFlatParaNext( xFPIterator
->getNextPara() );
676 AddEntry( xFPIterator
, xFlatParaNext
, aCurDocId
, 0, aFPEntryItem
.m_bAutomatic
);
679 catch (css::uno::Exception
&)
681 TOOLS_WARN_EXCEPTION("linguistic", "GrammarCheckingIterator::DequeueAndCheck ignoring");
685 // ---- THREAD SAFE START ----
687 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
688 m_aCurCheckedDocId
.clear();
690 // ---- THREAD SAFE END ----
694 // ---- THREAD SAFE START ----
696 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
701 // Check queue state again
702 if (m_aFPEntriesQueue
.empty())
703 m_aWakeUpThread
.reset();
705 // ---- THREAD SAFE END ----
707 //if the queue is empty
708 // IMPORTANT: Don't call condition.wait() with locked
709 // mutex. Otherwise you would keep out other threads
710 // to add entries to the queue! A condition is thread-
712 m_aWakeUpThread
.wait();
718 void SAL_CALL
GrammarCheckingIterator::startProofreading(
719 const uno::Reference
< ::uno::XInterface
> & xDoc
,
720 const uno::Reference
< text::XFlatParagraphIteratorProvider
> & xIteratorProvider
)
722 // get paragraph to start checking with
723 const bool bAutomatic
= true;
724 uno::Reference
<text::XFlatParagraphIterator
> xFPIterator
= xIteratorProvider
->getFlatParagraphIterator(
725 text::TextMarkupType::PROOFREADING
, bAutomatic
);
726 uno::Reference
< text::XFlatParagraph
> xPara( xFPIterator
.is()? xFPIterator
->getFirstPara() : nullptr );
727 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
729 // ---- THREAD SAFE START ----
730 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
731 if (xPara
.is() && xComponent
.is())
733 OUString aDocId
= GetOrCreateDocId( xComponent
);
735 // create new entry and add it to queue
736 AddEntry( xFPIterator
, xPara
, aDocId
, 0, bAutomatic
);
738 // ---- THREAD SAFE END ----
742 linguistic2::ProofreadingResult SAL_CALL
GrammarCheckingIterator::checkSentenceAtPosition(
743 const uno::Reference
< uno::XInterface
>& xDoc
,
744 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
745 const OUString
& rText
,
747 sal_Int32 nStartOfSentencePos
,
748 sal_Int32 nSuggestedEndOfSentencePos
,
749 sal_Int32 nErrorPosInPara
)
751 // for the context menu...
753 linguistic2::ProofreadingResult aRes
;
755 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
756 if (xFlatPara
.is() && xComponent
.is() &&
757 ( nErrorPosInPara
< 0 || nErrorPosInPara
< rText
.getLength()))
759 // iterate through paragraph until we find the sentence we are interested in
760 linguistic2::ProofreadingResult aTmpRes
;
761 sal_Int32 nStartPos
= nStartOfSentencePos
>= 0 ? nStartOfSentencePos
: 0;
766 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, nStartPos
);
767 sal_Int32 nOldStartOfSentencePos
= nStartPos
;
768 uno::Reference
< linguistic2::XProofreader
> xGC
;
771 // ---- THREAD SAFE START ----
773 ::osl::ClearableGuard
< ::osl::Mutex
> aGuard( MyMutex::get() );
774 aDocId
= GetOrCreateDocId( xComponent
);
775 nSuggestedEndOfSentencePos
= GetSuggestedEndOfSentence( rText
, nStartPos
, aCurLocale
);
776 DBG_ASSERT( nSuggestedEndOfSentencePos
> nStartPos
, "nSuggestedEndOfSentencePos calculation failed?" );
778 xGC
= GetGrammarChecker( aCurLocale
);
780 // ---- THREAD SAFE START ----
781 sal_Int32 nEndPos
= -1;
784 uno::Sequence
<beans::PropertyValue
> const aProps(
785 lcl_makeProperties(xFlatPara
));
786 aTmpRes
= xGC
->doProofreading( aDocId
, rText
,
787 aCurLocale
, nStartPos
, nSuggestedEndOfSentencePos
, aProps
);
789 //!! work-around to prevent looping if the grammar checker
790 //!! failed to properly identify the sentence end
791 if (aTmpRes
.nBehindEndOfSentencePosition
<= nStartPos
)
793 SAL_WARN( "linguistic", "!! Grammarchecker failed to provide end of sentence !!" );
794 aTmpRes
.nBehindEndOfSentencePosition
= nSuggestedEndOfSentencePos
;
797 aTmpRes
.xFlatParagraph
= xFlatPara
;
798 aTmpRes
.nStartOfSentencePosition
= nStartPos
;
799 nEndPos
= aTmpRes
.nBehindEndOfSentencePosition
;
801 if ((nErrorPosInPara
< 0 || nStartPos
<= nErrorPosInPara
) && nErrorPosInPara
< nEndPos
)
804 if (nEndPos
== -1) // no result from grammar checker
805 nEndPos
= nSuggestedEndOfSentencePos
;
806 nStartPos
= lcl_SkipWhiteSpaces( rText
, nEndPos
);
807 aTmpRes
.nBehindEndOfSentencePosition
= nEndPos
;
808 aTmpRes
.nStartOfNextSentencePosition
= nStartPos
;
809 aTmpRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces( rText
, aTmpRes
.nStartOfNextSentencePosition
);
811 // prevent endless loop by forcefully advancing if needs be...
812 if (nStartPos
<= nOldStartOfSentencePos
)
814 SAL_WARN( "linguistic", "end-of-sentence detection failed?" );
815 nStartPos
= nOldStartOfSentencePos
+ 1;
818 while (!bFound
&& nStartPos
< rText
.getLength());
820 if (bFound
&& !xFlatPara
->isModified())
828 sal_Int32
GrammarCheckingIterator::GetSuggestedEndOfSentence(
829 const OUString
&rText
,
830 sal_Int32 nSentenceStartPos
,
831 const lang::Locale
&rLocale
)
833 // internal method; will always be called with locked mutex
835 if (!m_xBreakIterator
.is())
837 uno::Reference
< uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
838 m_xBreakIterator
= i18n::BreakIterator::create(xContext
);
840 sal_Int32 nTextLen
= rText
.getLength();
841 sal_Int32
nEndPosition(0);
842 sal_Int32 nTmpStartPos
= nSentenceStartPos
;
845 sal_Int32
const nPrevEndPosition(nEndPosition
);
846 nEndPosition
= nTextLen
;
847 if (nTmpStartPos
< nTextLen
)
849 nEndPosition
= m_xBreakIterator
->endOfSentence( rText
, nTmpStartPos
, rLocale
);
850 if (nEndPosition
<= nPrevEndPosition
)
852 // fdo#68750 if there's no progress at all then presumably
853 // there's no end of sentence in this paragraph so just
854 // set the end position to end of paragraph
855 nEndPosition
= nTextLen
;
858 if (nEndPosition
< 0)
859 nEndPosition
= nTextLen
;
863 while (nEndPosition
<= nSentenceStartPos
&& nEndPosition
< nTextLen
);
864 if (nEndPosition
> nTextLen
)
865 nEndPosition
= nTextLen
;
870 void SAL_CALL
GrammarCheckingIterator::resetIgnoreRules( )
872 for (auto const& elem
: m_aGCReferencesByService
)
874 uno::Reference
< linguistic2::XProofreader
> xGC(elem
.second
);
876 xGC
->resetIgnoreRules();
881 sal_Bool SAL_CALL
GrammarCheckingIterator::isProofreading(
882 const uno::Reference
< uno::XInterface
>& xDoc
)
884 // ---- THREAD SAFE START ----
885 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
889 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
892 // if the component was already used in one of the two calls to check text
893 // i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
894 // m_aDocIdMap unless the document already disposed.
895 // If it is not found then it is not yet being checked (or requested to being checked)
896 const DocMap_t::const_iterator
aIt( m_aDocIdMap
.find( xComponent
.get() ) );
897 if (aIt
!= m_aDocIdMap
.end())
899 // check in document is checked automatically in the background...
900 OUString aDocId
= aIt
->second
;
901 if (!m_aCurCheckedDocId
.isEmpty() && m_aCurCheckedDocId
== aDocId
)
903 // an entry for that document was dequeued and is currently being checked.
908 // we need to check if there is an entry for that document in the queue...
909 // That is the document is going to be checked sooner or later.
911 sal_Int32 nSize
= m_aFPEntriesQueue
.size();
912 for (sal_Int32 i
= 0; i
< nSize
&& !bRes
; ++i
)
914 if (aDocId
== m_aFPEntriesQueue
[i
].m_aDocId
)
920 // ---- THREAD SAFE END ----
926 void SAL_CALL
GrammarCheckingIterator::processLinguServiceEvent(
927 const linguistic2::LinguServiceEvent
& rLngSvcEvent
)
929 if (rLngSvcEvent
.nEvent
== linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
)
933 uno::Reference
< uno::XInterface
> xThis( static_cast< OWeakObject
* >(this) );
934 linguistic2::LinguServiceEvent
aEvent( xThis
, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
);
935 m_aNotifyListeners
.notifyEach(
936 &linguistic2::XLinguServiceEventListener::processLinguServiceEvent
,
939 catch (uno::RuntimeException
&)
943 catch (const ::uno::Exception
&)
946 TOOLS_WARN_EXCEPTION("linguistic", "processLinguServiceEvent");
952 sal_Bool SAL_CALL
GrammarCheckingIterator::addLinguServiceEventListener(
953 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
957 m_aNotifyListeners
.addInterface( xListener
);
963 sal_Bool SAL_CALL
GrammarCheckingIterator::removeLinguServiceEventListener(
964 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
968 m_aNotifyListeners
.removeInterface( xListener
);
974 void SAL_CALL
GrammarCheckingIterator::dispose()
976 lang::EventObject
aEvt( static_cast<linguistic2::XProofreadingIterator
*>(this) );
977 m_aEventListeners
.disposeAndClear( aEvt
);
981 // ---- THREAD SAFE START ----
983 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
985 // release all UNO references
987 m_xBreakIterator
.clear();
989 // clear containers with UNO references AND have those references released
990 GCReferences_t aTmpEmpty1
;
992 FPQueue_t aTmpEmpty3
;
993 m_aGCReferencesByService
.swap( aTmpEmpty1
);
994 m_aDocIdMap
.swap( aTmpEmpty2
);
995 m_aFPEntriesQueue
.swap( aTmpEmpty3
);
997 // ---- THREAD SAFE END ----
1001 void SAL_CALL
GrammarCheckingIterator::addEventListener(
1002 const uno::Reference
< lang::XEventListener
>& xListener
)
1006 m_aEventListeners
.addInterface( xListener
);
1011 void SAL_CALL
GrammarCheckingIterator::removeEventListener(
1012 const uno::Reference
< lang::XEventListener
>& xListener
)
1016 m_aEventListeners
.removeInterface( xListener
);
1021 void SAL_CALL
GrammarCheckingIterator::disposing( const lang::EventObject
&rSource
)
1023 // if the component (document) is disposing release all references
1024 //!! There is no need to remove entries from the queue that are from this document
1025 //!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
1026 //!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
1027 //!! And if an entry is currently checked by a grammar checker upon return the results
1028 //!! should be ignored.
1029 //!! Also GetOrCreateDocId will not use that very same Id again...
1030 //!! All of the above resulting in that we only have to get rid of the implementation pointer here.
1031 uno::Reference
< lang::XComponent
> xDoc( rSource
.Source
, uno::UNO_QUERY
);
1034 // ---- THREAD SAFE START ----
1035 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1036 m_aDocIdMap
.erase( xDoc
.get() );
1037 // ---- THREAD SAFE END ----
1042 uno::Reference
< util::XChangesBatch
> const & GrammarCheckingIterator::GetUpdateAccess() const
1044 if (!m_xUpdateAccess
.is())
1048 // get configuration provider
1049 uno::Reference
< uno::XComponentContext
> xContext
= comphelper::getProcessComponentContext();
1050 uno::Reference
< lang::XMultiServiceFactory
> xConfigurationProvider
=
1051 configuration::theDefaultProvider::get( xContext
);
1053 // get configuration update access
1054 beans::PropertyValue aValue
;
1055 aValue
.Name
= "nodepath";
1056 aValue
.Value
<<= OUString("org.openoffice.Office.Linguistic/ServiceManager");
1057 uno::Sequence
< uno::Any
> aProps(1);
1058 aProps
[0] <<= aValue
;
1059 m_xUpdateAccess
.set(
1060 xConfigurationProvider
->createInstanceWithArguments(
1061 "com.sun.star.configuration.ConfigurationUpdateAccess", aProps
),
1062 uno::UNO_QUERY_THROW
);
1064 catch (uno::Exception
&)
1069 return m_xUpdateAccess
;
1073 void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
1075 GCImplNames_t aTmpGCImplNamesByLang
;
1079 // get node names (locale iso strings) for configured grammar checkers
1080 uno::Reference
< container::XNameAccess
> xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW
);
1081 xNA
.set( xNA
->getByName( "GrammarCheckerList" ), uno::UNO_QUERY_THROW
);
1082 const uno::Sequence
< OUString
> aElementNames( xNA
->getElementNames() );
1084 for (const OUString
& rElementName
: aElementNames
)
1086 uno::Sequence
< OUString
> aImplNames
;
1087 uno::Any
aTmp( xNA
->getByName( rElementName
) );
1088 if (aTmp
>>= aImplNames
)
1090 if (aImplNames
.hasElements())
1092 // only the first entry is used, there should be only one grammar checker per language
1093 const OUString
aImplName( aImplNames
[0] );
1094 const LanguageType nLang
= LanguageTag::convertToLanguageType( rElementName
);
1095 aTmpGCImplNamesByLang
[ nLang
] = aImplName
;
1100 SAL_WARN( "linguistic", "failed to get aImplNames. Wrong type?" );
1104 catch (uno::Exception
const &)
1106 TOOLS_WARN_EXCEPTION( "linguistic", "exception caught. Failed to get configured services" );
1110 // ---- THREAD SAFE START ----
1111 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1112 m_aGCImplNamesByLang
= aTmpGCImplNamesByLang
;
1113 // ---- THREAD SAFE END ----
1118 sal_Bool SAL_CALL
GrammarCheckingIterator::supportsService(
1119 const OUString
& rServiceName
)
1121 return cppu::supportsService(this, rServiceName
);
1125 OUString SAL_CALL
GrammarCheckingIterator::getImplementationName( )
1127 return GrammarCheckingIterator_getImplementationName();
1131 uno::Sequence
< OUString
> SAL_CALL
GrammarCheckingIterator::getSupportedServiceNames( )
1133 return GrammarCheckingIterator_getSupportedServiceNames();
1137 void GrammarCheckingIterator::SetServiceList(
1138 const lang::Locale
&rLocale
,
1139 const uno::Sequence
< OUString
> &rSvcImplNames
)
1141 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1143 LanguageType nLanguage
= LinguLocaleToLanguage( rLocale
);
1145 if (rSvcImplNames
.hasElements())
1146 aImplName
= rSvcImplNames
[0]; // there is only one grammar checker per language
1148 if (!LinguIsUnspecified(nLanguage
) && nLanguage
!= LANGUAGE_DONTKNOW
)
1150 if (!aImplName
.isEmpty())
1151 m_aGCImplNamesByLang
[ nLanguage
] = aImplName
;
1153 m_aGCImplNamesByLang
.erase( nLanguage
);
1158 uno::Sequence
< OUString
> GrammarCheckingIterator::GetServiceList(
1159 const lang::Locale
&rLocale
) const
1161 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1163 uno::Sequence
< OUString
> aRes(1);
1165 OUString aImplName
; // there is only one grammar checker per language
1166 LanguageType nLang
= LinguLocaleToLanguage( rLocale
);
1167 GCImplNames_t::const_iterator
aIt( m_aGCImplNamesByLang
.find( nLang
) );
1168 if (aIt
!= m_aGCImplNamesByLang
.end())
1169 aImplName
= aIt
->second
;
1171 if (!aImplName
.isEmpty())
1172 aRes
[0] = aImplName
;
1180 static OUString
GrammarCheckingIterator_getImplementationName() throw()
1182 return "com.sun.star.lingu2.ProofreadingIterator";
1186 static uno::Sequence
< OUString
> GrammarCheckingIterator_getSupportedServiceNames() throw()
1188 return { "com.sun.star.linguistic2.ProofreadingIterator" };
1191 /// @throws uno::Exception
1192 static uno::Reference
< uno::XInterface
> GrammarCheckingIterator_createInstance(
1193 const uno::Reference
< lang::XMultiServiceFactory
> & /*rxSMgr*/ )
1195 return static_cast< ::cppu::OWeakObject
* >(new GrammarCheckingIterator());
1199 void * GrammarCheckingIterator_getFactory(
1200 const sal_Char
*pImplName
,
1201 lang::XMultiServiceFactory
*pServiceManager
)
1203 void * pRet
= nullptr;
1204 if ( GrammarCheckingIterator_getImplementationName().equalsAscii( pImplName
) )
1206 uno::Reference
< lang::XSingleServiceFactory
> xFactory
=
1207 cppu::createOneInstanceFactory(
1209 GrammarCheckingIterator_getImplementationName(),
1210 GrammarCheckingIterator_createInstance
,
1211 GrammarCheckingIterator_getSupportedServiceNames());
1212 // acquire, because we return an interface pointer instead of a reference
1213 xFactory
->acquire();
1214 pRet
= xFactory
.get();
1219 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */