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>
48 #include <sal/config.h>
49 #include <osl/conditn.hxx>
50 #include <osl/thread.hxx>
51 #include <cppuhelper/implbase4.hxx>
52 #include <cppuhelper/implementationentry.hxx>
53 #include <cppuhelper/interfacecontainer.h>
54 #include <cppuhelper/factory.hxx>
55 #include <i18npool/languagetag.hxx>
56 #include <comphelper/processfactory.hxx>
57 #include <comphelper/extract.hxx>
63 #include "linguistic/misc.hxx"
67 #include "gciterator.hxx"
69 using ::rtl::OUString
;
70 using namespace linguistic
;
71 using namespace ::com::sun::star
;
73 // forward declarations
74 static ::rtl::OUString
GrammarCheckingIterator_getImplementationName() throw();
75 static uno::Sequence
< OUString
> GrammarCheckingIterator_getSupportedServiceNames() throw();
79 // white space list: obtained from the fonts.config.txt of a Linux system.
80 static sal_Unicode aWhiteSpaces
[] =
83 0x00a0, /* NO-BREAK SPACE */
84 0x00ad, /* SOFT HYPHEN */
85 0x115f, /* HANGUL CHOSEONG FILLER */
86 0x1160, /* HANGUL JUNGSEONG FILLER */
87 0x1680, /* OGHAM SPACE MARK */
90 0x2002, /* EN SPACE */
91 0x2003, /* EM SPACE */
92 0x2004, /* THREE-PER-EM SPACE */
93 0x2005, /* FOUR-PER-EM SPACE */
94 0x2006, /* SIX-PER-EM SPACE */
95 0x2007, /* FIGURE SPACE */
96 0x2008, /* PUNCTUATION SPACE */
97 0x2009, /* THIN SPACE */
98 0x200a, /* HAIR SPACE */
99 0x200b, /* ZERO WIDTH SPACE */
100 0x200c, /* ZERO WIDTH NON-JOINER */
101 0x200d, /* ZERO WIDTH JOINER */
102 0x200e, /* LEFT-TO-RIGHT MARK */
103 0x200f, /* RIGHT-TO-LEFT MARK */
104 0x2028, /* LINE SEPARATOR */
105 0x2029, /* PARAGRAPH SEPARATOR */
106 0x202a, /* LEFT-TO-RIGHT EMBEDDING */
107 0x202b, /* RIGHT-TO-LEFT EMBEDDING */
108 0x202c, /* POP DIRECTIONAL FORMATTING */
109 0x202d, /* LEFT-TO-RIGHT OVERRIDE */
110 0x202e, /* RIGHT-TO-LEFT OVERRIDE */
111 0x202f, /* NARROW NO-BREAK SPACE */
112 0x205f, /* MEDIUM MATHEMATICAL SPACE */
113 0x2060, /* WORD JOINER */
114 0x2061, /* FUNCTION APPLICATION */
115 0x2062, /* INVISIBLE TIMES */
116 0x2063, /* INVISIBLE SEPARATOR */
117 0x206A, /* INHIBIT SYMMETRIC SWAPPING */
118 0x206B, /* ACTIVATE SYMMETRIC SWAPPING */
119 0x206C, /* INHIBIT ARABIC FORM SHAPING */
120 0x206D, /* ACTIVATE ARABIC FORM SHAPING */
121 0x206E, /* NATIONAL DIGIT SHAPES */
122 0x206F, /* NOMINAL DIGIT SHAPES */
123 0x3000, /* IDEOGRAPHIC SPACE */
124 0x3164, /* HANGUL FILLER */
125 0xfeff, /* ZERO WIDTH NO-BREAK SPACE */
126 0xffa0, /* HALFWIDTH HANGUL FILLER */
127 0xfff9, /* INTERLINEAR ANNOTATION ANCHOR */
128 0xfffa, /* INTERLINEAR ANNOTATION SEPARATOR */
129 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */
132 static int nWhiteSpaces
= sizeof( aWhiteSpaces
) / sizeof( aWhiteSpaces
[0] );
134 static bool lcl_IsWhiteSpace( sal_Unicode cChar
)
137 for (int i
= 0; i
< nWhiteSpaces
&& !bFound
; ++i
)
139 if (cChar
== aWhiteSpaces
[i
])
145 static sal_Int32
lcl_SkipWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
147 // note having nStartPos point right behind the string is OK since that one
148 // is a correct end-of-sentence position to be returned from a grammar checker...
150 const sal_Int32 nLen
= rText
.getLength();
151 bool bIllegalArgument
= false;
154 bIllegalArgument
= true;
157 if (nStartPos
> nLen
)
159 bIllegalArgument
= true;
162 if (bIllegalArgument
)
164 DBG_ASSERT( 0, "lcl_SkipWhiteSpaces: illegal arguments" );
167 sal_Int32 nRes
= nStartPos
;
168 if (0 <= nStartPos
&& nStartPos
< nLen
)
170 const sal_Unicode
*pText
= rText
.getStr() + nStartPos
;
171 while (nStartPos
< nLen
&& lcl_IsWhiteSpace( *pText
))
173 nRes
= pText
- rText
.getStr();
176 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_SkipWhiteSpaces return value out of range" );
180 static sal_Int32
lcl_BacktraceWhiteSpaces( const OUString
&rText
, sal_Int32 nStartPos
)
182 // note: having nStartPos point right behind the string is OK since that one
183 // is a correct end-of-sentence position to be returned from a grammar checker...
185 const sal_Int32 nLen
= rText
.getLength();
186 bool bIllegalArgument
= false;
189 bIllegalArgument
= true;
192 if (nStartPos
> nLen
)
194 bIllegalArgument
= true;
197 if (bIllegalArgument
)
199 DBG_ASSERT( 0, "lcl_BacktraceWhiteSpaces: illegal arguments" );
202 sal_Int32 nRes
= nStartPos
;
203 sal_Int32 nPosBefore
= nStartPos
- 1;
204 const sal_Unicode
*pStart
= rText
.getStr();
205 if (0 <= nPosBefore
&& nPosBefore
< nLen
&& lcl_IsWhiteSpace( pStart
[ nPosBefore
] ))
207 nStartPos
= nPosBefore
;
208 if (0 <= nStartPos
&& nStartPos
< nLen
)
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;
218 DBG_ASSERT( 0 <= nRes
&& nRes
<= nLen
, "lcl_BacktraceWhiteSpaces return value out of range" );
223 extern "C" void workerfunc (void * gci
)
225 ((GrammarCheckingIterator
*)gci
)->DequeueAndCheck();
228 static lang::Locale
lcl_GetPrimaryLanguageOfSentence(
229 uno::Reference
< text::XFlatParagraph
> xFlatPara
,
230 sal_Int32 nStartIndex
)
232 //get the language of the first word
233 return xFlatPara
->getLanguageOfText( nStartIndex
, 1 );
237 GrammarCheckingIterator::GrammarCheckingIterator( const uno::Reference
< lang::XMultiServiceFactory
> & rxMgr
) :
240 m_aCurCheckedDocId(),
241 m_bGCServicesChecked( sal_False
),
242 m_nDocIdCounter( 0 ),
243 m_nLastEndOfSentencePos( -1 ),
244 m_aEventListeners( MyMutex::get() ),
245 m_aNotifyListeners( MyMutex::get() )
247 m_thread
= osl_createThread( workerfunc
, this );
251 GrammarCheckingIterator::~GrammarCheckingIterator()
256 void GrammarCheckingIterator::TerminateThread()
260 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
264 m_aWakeUpThread
.set();
268 osl_joinWithThread(t
);
269 osl_destroyThread(t
);
273 sal_Int32
GrammarCheckingIterator::NextDocId()
275 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
276 m_nDocIdCounter
+= 1;
277 return m_nDocIdCounter
;
281 OUString
GrammarCheckingIterator::GetOrCreateDocId(
282 const uno::Reference
< lang::XComponent
> &xComponent
)
284 // internal method; will always be called with locked mutex
289 if (m_aDocIdMap
.find( xComponent
.get() ) != m_aDocIdMap
.end())
291 // return already existing entry
292 aRes
= m_aDocIdMap
[ xComponent
.get() ];
294 else // add new entry
296 sal_Int32 nRes
= NextDocId();
297 aRes
= OUString::valueOf( nRes
);
298 m_aDocIdMap
[ xComponent
.get() ] = aRes
;
299 xComponent
->addEventListener( this );
306 void GrammarCheckingIterator::AddEntry(
307 uno::WeakReference
< text::XFlatParagraphIterator
> xFlatParaIterator
,
308 uno::WeakReference
< text::XFlatParagraph
> xFlatPara
,
309 const OUString
& rDocId
,
310 sal_Int32 nStartIndex
,
311 sal_Bool bAutomatic
)
313 // we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
314 // but we always need a xFlatPara...
315 uno::Reference
< text::XFlatParagraph
> xPara( xFlatPara
);
319 aNewFPEntry
.m_xParaIterator
= xFlatParaIterator
;
320 aNewFPEntry
.m_xPara
= xFlatPara
;
321 aNewFPEntry
.m_aDocId
= rDocId
;
322 aNewFPEntry
.m_nStartIndex
= nStartIndex
;
323 aNewFPEntry
.m_bAutomatic
= bAutomatic
;
325 // add new entry to the end of this queue
326 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
327 m_aFPEntriesQueue
.push_back( aNewFPEntry
);
329 // wake up the thread in order to do grammar checking
330 m_aWakeUpThread
.set();
335 void GrammarCheckingIterator::ProcessResult(
336 const linguistic2::ProofreadingResult
&rRes
,
337 const uno::Reference
< text::XFlatParagraphIterator
> &rxFlatParagraphIterator
,
338 bool bIsAutomaticChecking
)
340 DBG_ASSERT( rRes
.xFlatParagraph
.is(), "xFlatParagraph is missing" );
341 //no guard necessary as no members are used
342 sal_Bool bContinueWithNextPara
= sal_False
;
343 if (!rRes
.xFlatParagraph
.is() || rRes
.xFlatParagraph
->isModified())
345 // if paragraph was modified/deleted meanwhile continue with the next one...
346 bContinueWithNextPara
= sal_True
;
348 else // paragraph is still unchanged...
350 // mark found errors...
352 sal_Int32 nTextLen
= rRes
.aText
.getLength();
353 bool bBoundariesOk
= 0 <= rRes
.nStartOfSentencePosition
&& rRes
.nStartOfSentencePosition
<= nTextLen
&&
354 0 <= rRes
.nBehindEndOfSentencePosition
&& rRes
.nBehindEndOfSentencePosition
<= nTextLen
&&
355 0 <= rRes
.nStartOfNextSentencePosition
&& rRes
.nStartOfNextSentencePosition
<= nTextLen
&&
356 rRes
.nStartOfSentencePosition
<= rRes
.nBehindEndOfSentencePosition
&&
357 rRes
.nBehindEndOfSentencePosition
<= rRes
.nStartOfNextSentencePosition
;
358 (void) bBoundariesOk
;
359 DBG_ASSERT( bBoundariesOk
, "inconsistent sentence boundaries" );
360 uno::Sequence
< linguistic2::SingleProofreadingError
> aErrors
= rRes
.aErrors
;
362 uno::Reference
< text::XMultiTextMarkup
> xMulti( rRes
.xFlatParagraph
, uno::UNO_QUERY
);
363 if (xMulti
.is()) // use new API for markups
367 // length = number of found errors + 1 sentence markup
368 sal_Int32 nErrors
= rRes
.aErrors
.getLength();
369 uno::Sequence
< text::TextMarkupDescriptor
> aDescriptors( nErrors
+ 1 );
370 text::TextMarkupDescriptor
* pDescriptors
= aDescriptors
.getArray();
372 // at pos 0 .. nErrors-1 -> all grammar errors
373 for (sal_Int32 i
= 0; i
< nErrors
; ++i
)
375 const linguistic2::SingleProofreadingError
&rError
= rRes
.aErrors
[i
];
376 text::TextMarkupDescriptor
&rDesc
= aDescriptors
[i
];
378 rDesc
.nType
= rError
.nErrorType
;
379 rDesc
.nOffset
= rError
.nErrorStart
;
380 rDesc
.nLength
= rError
.nErrorLength
;
382 // the proofreader may return SPELLING but right now our core
383 // does only handle PROOFREADING if the result is from the proofreader...
384 // (later on we may wish to color spelling errors found by the proofreader
385 // differently for example. But no special handling right now.
386 if (rDesc
.nType
== text::TextMarkupType::SPELLCHECK
)
387 rDesc
.nType
= text::TextMarkupType::PROOFREADING
;
390 // at pos nErrors -> sentence markup
391 // nSentenceLength: includes the white-spaces following the sentence end...
392 const sal_Int32 nSentenceLength
= rRes
.nStartOfNextSentencePosition
- rRes
.nStartOfSentencePosition
;
393 pDescriptors
[ nErrors
].nType
= text::TextMarkupType::SENTENCE
;
394 pDescriptors
[ nErrors
].nOffset
= rRes
.nStartOfSentencePosition
;
395 pDescriptors
[ nErrors
].nLength
= nSentenceLength
;
397 xMulti
->commitMultiTextMarkup( aDescriptors
) ;
399 catch (lang::IllegalArgumentException
&)
401 OSL_FAIL( "commitMultiTextMarkup: IllegalArgumentException exception caught" );
405 // other sentences left to be checked in this paragraph?
406 if (rRes
.nStartOfNextSentencePosition
< rRes
.aText
.getLength())
408 AddEntry( rxFlatParagraphIterator
, rRes
.xFlatParagraph
, rRes
.aDocumentIdentifier
, rRes
.nStartOfNextSentencePosition
, bIsAutomaticChecking
);
410 else // current paragraph finished
412 // set "already checked" flag for the current flat paragraph
413 if (rRes
.xFlatParagraph
.is())
414 rRes
.xFlatParagraph
->setChecked( text::TextMarkupType::PROOFREADING
, true );
416 bContinueWithNextPara
= sal_True
;
420 if (bContinueWithNextPara
)
422 // we need to continue with the next paragraph
423 uno::Reference
< text::XFlatParagraph
> xFlatParaNext
;
424 if (rxFlatParagraphIterator
.is())
425 xFlatParaNext
= rxFlatParagraphIterator
->getNextPara();
427 AddEntry( rxFlatParagraphIterator
, xFlatParaNext
, rRes
.aDocumentIdentifier
, 0, bIsAutomaticChecking
);
433 uno::Reference
< linguistic2::XProofreader
> GrammarCheckingIterator::GetGrammarChecker(
434 const lang::Locale
&rLocale
)
437 uno::Reference
< linguistic2::XProofreader
> xRes
;
439 // ---- THREAD SAFE START ----
440 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
442 // check supported locales for each grammarchecker if not already done
443 if (!m_bGCServicesChecked
)
445 GetConfiguredGCSvcs_Impl();
446 m_bGCServicesChecked
= sal_True
;
449 const LanguageType nLang
= LanguageTag( rLocale
).getLanguageType( false);
450 GCImplNames_t::const_iterator
aLangIt( m_aGCImplNamesByLang
.find( nLang
) );
451 if (aLangIt
!= m_aGCImplNamesByLang
.end()) // matching configured language found?
453 OUString
aSvcImplName( aLangIt
->second
);
454 GCReferences_t::const_iterator
aImplNameIt( m_aGCReferencesByService
.find( aSvcImplName
) );
455 if (aImplNameIt
!= m_aGCReferencesByService
.end()) // matching impl name found?
457 xRes
= aImplNameIt
->second
;
459 else // the service is to be instatiated here for the first time...
463 uno::Reference
< lang::XMultiServiceFactory
> xMgr(
464 comphelper::getProcessServiceFactory(), uno::UNO_QUERY_THROW
);
465 uno::Reference
< linguistic2::XProofreader
> xGC(
466 xMgr
->createInstance( aSvcImplName
), uno::UNO_QUERY_THROW
);
467 uno::Reference
< linguistic2::XSupportedLocales
> xSuppLoc( xGC
, uno::UNO_QUERY_THROW
);
469 if (xSuppLoc
->hasLocale( rLocale
))
471 m_aGCReferencesByService
[ aSvcImplName
] = xGC
;
474 uno::Reference
< linguistic2::XLinguServiceEventBroadcaster
> xBC( xGC
, uno::UNO_QUERY
);
476 xBC
->addLinguServiceEventListener( this );
480 DBG_ASSERT( 0, "grammar checker does not support required locale" );
483 catch (uno::Exception
&)
485 DBG_ASSERT( 0, "instantiating grammar checker failed" );
489 // ---- THREAD SAFE END ----
495 void GrammarCheckingIterator::DequeueAndCheck()
497 uno::Sequence
< sal_Int32
> aLangPortions
;
498 uno::Sequence
< lang::Locale
> aLangPortionsLocale
;
502 // ---- THREAD SAFE START ----
503 bool bQueueEmpty
= false;
505 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
510 bQueueEmpty
= m_aFPEntriesQueue
.empty();
512 // ---- THREAD SAFE END ----
516 uno::Reference
< text::XFlatParagraphIterator
> xFPIterator
;
517 uno::Reference
< text::XFlatParagraph
> xFlatPara
;
518 FPEntry aFPEntryItem
;
520 sal_Bool bModified
= sal_False
;
521 // ---- THREAD SAFE START ----
523 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
524 aFPEntryItem
= m_aFPEntriesQueue
.front();
525 xFPIterator
= aFPEntryItem
.m_xParaIterator
;
526 xFlatPara
= aFPEntryItem
.m_xPara
;
527 m_aCurCheckedDocId
= aFPEntryItem
.m_aDocId
;
528 aCurDocId
= m_aCurCheckedDocId
;
530 m_aFPEntriesQueue
.pop_front();
532 // ---- THREAD SAFE END ----
534 if (xFlatPara
.is() && xFPIterator
.is())
536 OUString
aCurTxt( xFlatPara
->getText() );
537 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, aFPEntryItem
.m_nStartIndex
);
539 bModified
= xFlatPara
->isModified();
542 // ---- THREAD SAFE START ----
543 ::osl::ClearableGuard
< ::osl::Mutex
> aGuard( MyMutex::get() );
545 sal_Int32 nStartPos
= aFPEntryItem
.m_nStartIndex
;
546 sal_Int32 nSuggestedEnd
= GetSuggestedEndOfSentence( aCurTxt
, nStartPos
, aCurLocale
);
547 DBG_ASSERT( (nSuggestedEnd
== 0 && aCurTxt
.isEmpty()) || nSuggestedEnd
> nStartPos
,
548 "nSuggestedEndOfSentencePos calculation failed?" );
550 linguistic2::ProofreadingResult aRes
;
552 uno::Reference
< linguistic2::XProofreader
> xGC( GetGrammarChecker( aCurLocale
), uno::UNO_QUERY
);
556 uno::Sequence
< beans::PropertyValue
> aEmptyProps
;
557 aRes
= xGC
->doProofreading( aCurDocId
, aCurTxt
, aCurLocale
, nStartPos
, nSuggestedEnd
, aEmptyProps
);
559 //!! work-around to prevent looping if the grammar checker
560 //!! failed to properly identify the sentence end
562 aRes
.nBehindEndOfSentencePosition
<= nStartPos
&&
563 aRes
.nBehindEndOfSentencePosition
!= nSuggestedEnd
566 DBG_ASSERT( 0, "!! Grammarchecker failed to provide end of sentence !!" );
567 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
570 aRes
.xFlatParagraph
= xFlatPara
;
571 aRes
.nStartOfSentencePosition
= nStartPos
;
575 // no grammar checker -> no error
576 // but we need to provide the data below in order to continue with the next sentence
577 aRes
.aDocumentIdentifier
= aCurDocId
;
578 aRes
.xFlatParagraph
= xFlatPara
;
579 aRes
.aText
= aCurTxt
;
580 aRes
.aLocale
= aCurLocale
;
581 aRes
.nStartOfSentencePosition
= nStartPos
;
582 aRes
.nBehindEndOfSentencePosition
= nSuggestedEnd
;
584 aRes
.nStartOfNextSentencePosition
= lcl_SkipWhiteSpaces( aCurTxt
, aRes
.nBehindEndOfSentencePosition
);
585 aRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces( aCurTxt
, aRes
.nStartOfNextSentencePosition
);
587 //guard has to be cleared as ProcessResult calls out of this class
589 ProcessResult( aRes
, xFPIterator
, aFPEntryItem
.m_bAutomatic
);
590 // ---- THREAD SAFE END ----
594 // the paragraph changed meanwhile... (and maybe is still edited)
595 // thus we simply continue to ask for the next to be checked.
596 uno::Reference
< text::XFlatParagraph
> xFlatParaNext( xFPIterator
->getNextPara() );
597 AddEntry( xFPIterator
, xFlatParaNext
, aCurDocId
, 0, aFPEntryItem
.m_bAutomatic
);
601 // ---- THREAD SAFE START ----
603 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
604 m_aCurCheckedDocId
= OUString();
606 // ---- THREAD SAFE END ----
610 // ---- THREAD SAFE START ----
612 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
617 // Check queue state again
618 if (m_aFPEntriesQueue
.empty())
619 m_aWakeUpThread
.reset();
621 // ---- THREAD SAFE END ----
623 //if the queue is empty
624 // IMPORTANT: Don't call condition.wait() with locked
625 // mutex. Otherwise you would keep out other threads
626 // to add entries to the queue! A condition is thread-
628 m_aWakeUpThread
.wait();
634 void SAL_CALL
GrammarCheckingIterator::startProofreading(
635 const uno::Reference
< ::uno::XInterface
> & xDoc
,
636 const uno::Reference
< text::XFlatParagraphIteratorProvider
> & xIteratorProvider
)
637 throw (uno::RuntimeException
, lang::IllegalArgumentException
)
639 // get paragraph to start checking with
640 const bool bAutomatic
= true;
641 uno::Reference
<text::XFlatParagraphIterator
> xFPIterator
= xIteratorProvider
->getFlatParagraphIterator(
642 text::TextMarkupType::PROOFREADING
, bAutomatic
);
643 uno::Reference
< text::XFlatParagraph
> xPara( xFPIterator
.is()? xFPIterator
->getFirstPara() : NULL
);
644 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
646 // ---- THREAD SAFE START ----
647 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
648 if (xPara
.is() && xComponent
.is())
650 OUString aDocId
= GetOrCreateDocId( xComponent
);
652 // create new entry and add it to queue
653 AddEntry( xFPIterator
, xPara
, aDocId
, 0, bAutomatic
);
655 // ---- THREAD SAFE END ----
659 linguistic2::ProofreadingResult SAL_CALL
GrammarCheckingIterator::checkSentenceAtPosition(
660 const uno::Reference
< uno::XInterface
>& xDoc
,
661 const uno::Reference
< text::XFlatParagraph
>& xFlatPara
,
662 const OUString
& rText
,
663 const lang::Locale
& rLocale
,
664 sal_Int32 nStartOfSentencePos
,
665 sal_Int32 nSuggestedEndOfSentencePos
,
666 sal_Int32 nErrorPosInPara
)
667 throw (lang::IllegalArgumentException
, uno::RuntimeException
)
671 // for the context menu...
673 linguistic2::ProofreadingResult aRes
;
675 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
676 if (xFlatPara
.is() && xComponent
.is() &&
677 ( nErrorPosInPara
< 0 || nErrorPosInPara
< rText
.getLength()))
679 // iterate through paragraph until we find the sentence we are interested in
680 linguistic2::ProofreadingResult aTmpRes
;
681 sal_Int32 nStartPos
= nStartOfSentencePos
>= 0 ? nStartOfSentencePos
: 0;
686 lang::Locale aCurLocale
= lcl_GetPrimaryLanguageOfSentence( xFlatPara
, nStartPos
);
687 sal_Int32 nOldStartOfSentencePos
= nStartPos
;
688 uno::Reference
< linguistic2::XProofreader
> xGC
;
691 // ---- THREAD SAFE START ----
693 ::osl::ClearableGuard
< ::osl::Mutex
> aGuard( MyMutex::get() );
694 aDocId
= GetOrCreateDocId( xComponent
);
695 nSuggestedEndOfSentencePos
= GetSuggestedEndOfSentence( rText
, nStartPos
, aCurLocale
);
696 DBG_ASSERT( nSuggestedEndOfSentencePos
> nStartPos
, "nSuggestedEndOfSentencePos calculation failed?" );
698 xGC
= GetGrammarChecker( aCurLocale
);
700 // ---- THREAD SAFE START ----
701 sal_Int32 nEndPos
= -1;
704 uno::Sequence
< beans::PropertyValue
> aEmptyProps
;
705 aTmpRes
= xGC
->doProofreading( aDocId
, rText
, aCurLocale
, nStartPos
, nSuggestedEndOfSentencePos
, aEmptyProps
);
707 //!! work-around to prevent looping if the grammar checker
708 //!! failed to properly identify the sentence end
709 if (aTmpRes
.nBehindEndOfSentencePosition
<= nStartPos
)
711 DBG_ASSERT( 0, "!! Grammarchecker failed to provide end of sentence !!" );
712 aTmpRes
.nBehindEndOfSentencePosition
= nSuggestedEndOfSentencePos
;
715 aTmpRes
.xFlatParagraph
= xFlatPara
;
716 aTmpRes
.nStartOfSentencePosition
= nStartPos
;
717 nEndPos
= aTmpRes
.nBehindEndOfSentencePosition
;
719 if ((nErrorPosInPara
< 0 || nStartPos
<= nErrorPosInPara
) && nErrorPosInPara
< nEndPos
)
722 if (nEndPos
== -1) // no result from grammar checker
723 nEndPos
= nSuggestedEndOfSentencePos
;
724 nStartPos
= lcl_SkipWhiteSpaces( rText
, nEndPos
);
725 aTmpRes
.nBehindEndOfSentencePosition
= nEndPos
;
726 aTmpRes
.nStartOfNextSentencePosition
= nStartPos
;
727 aTmpRes
.nBehindEndOfSentencePosition
= lcl_BacktraceWhiteSpaces( rText
, aTmpRes
.nStartOfNextSentencePosition
);
729 // prevent endless loop by forcefully advancing if needs be...
730 if (nStartPos
<= nOldStartOfSentencePos
)
732 DBG_ASSERT( 0, "end-of-sentence detection failed?" );
733 nStartPos
= nOldStartOfSentencePos
+ 1;
736 while (!bFound
&& nStartPos
< rText
.getLength());
738 if (bFound
&& !xFlatPara
->isModified())
746 sal_Int32
GrammarCheckingIterator::GetSuggestedEndOfSentence(
747 const OUString
&rText
,
748 sal_Int32 nSentenceStartPos
,
749 const lang::Locale
&rLocale
)
751 // internal method; will always be called with locked mutex
753 if (!m_xBreakIterator
.is())
755 uno::Reference
< uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
756 m_xBreakIterator
= i18n::BreakIterator::create(xContext
);
758 sal_Int32 nTextLen
= rText
.getLength();
759 sal_Int32 nEndPosition
;
760 sal_Int32 nTmpStartPos
= nSentenceStartPos
;
763 nEndPosition
= nTextLen
;
764 if (nTmpStartPos
< nTextLen
)
765 nEndPosition
= m_xBreakIterator
->endOfSentence( rText
, nTmpStartPos
, rLocale
);
766 if (nEndPosition
< 0)
767 nEndPosition
= nTextLen
;
771 while (nEndPosition
<= nSentenceStartPos
&& nEndPosition
< nTextLen
);
772 if (nEndPosition
> nTextLen
)
773 nEndPosition
= nTextLen
;
778 void SAL_CALL
GrammarCheckingIterator::resetIgnoreRules( )
779 throw (uno::RuntimeException
)
781 GCReferences_t::iterator
aIt( m_aGCReferencesByService
.begin() );
782 while (aIt
!= m_aGCReferencesByService
.end())
784 uno::Reference
< linguistic2::XProofreader
> xGC( aIt
->second
);
786 xGC
->resetIgnoreRules();
792 sal_Bool SAL_CALL
GrammarCheckingIterator::isProofreading(
793 const uno::Reference
< uno::XInterface
>& xDoc
)
794 throw (uno::RuntimeException
)
796 // ---- THREAD SAFE START ----
797 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
799 sal_Bool bRes
= sal_False
;
801 uno::Reference
< lang::XComponent
> xComponent( xDoc
, uno::UNO_QUERY
);
804 // if the component was already used in one of the two calls to check text
805 // i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
806 // m_aDocIdMap unless the document already disposed.
807 // If it is not found then it is not yet being checked (or requested to being checked)
808 const DocMap_t::const_iterator
aIt( m_aDocIdMap
.find( xComponent
.get() ) );
809 if (aIt
!= m_aDocIdMap
.end())
811 // check in document is checked automatically in the background...
812 OUString aDocId
= aIt
->second
;
813 if (!m_aCurCheckedDocId
.isEmpty() && m_aCurCheckedDocId
== aDocId
)
815 // an entry for that document was dequed and is currently being checked.
820 // we need to check if there is an entry for that document in the queue...
821 // That is the document is going to be checked sooner or later.
823 sal_Int32 nSize
= m_aFPEntriesQueue
.size();
824 for (sal_Int32 i
= 0; i
< nSize
&& !bRes
; ++i
)
826 if (aDocId
== m_aFPEntriesQueue
[i
].m_aDocId
)
832 // ---- THREAD SAFE END ----
838 void SAL_CALL
GrammarCheckingIterator::processLinguServiceEvent(
839 const linguistic2::LinguServiceEvent
& rLngSvcEvent
)
840 throw (uno::RuntimeException
)
842 if (rLngSvcEvent
.nEvent
== linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
)
846 uno::Reference
< uno::XInterface
> xThis( dynamic_cast< XLinguServiceEventBroadcaster
* >(this) );
847 linguistic2::LinguServiceEvent
aEvent( xThis
, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN
);
848 m_aNotifyListeners
.notifyEach(
849 &linguistic2::XLinguServiceEventListener::processLinguServiceEvent
,
852 catch (uno::RuntimeException
&)
856 catch (const ::uno::Exception
&rE
)
860 DBG_WARNING1("processLinguServiceEvent: exception:\n%s",
861 OUStringToOString(rE
.Message
, RTL_TEXTENCODING_UTF8
).getStr());
867 sal_Bool SAL_CALL
GrammarCheckingIterator::addLinguServiceEventListener(
868 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
869 throw (uno::RuntimeException
)
873 m_aNotifyListeners
.addInterface( xListener
);
879 sal_Bool SAL_CALL
GrammarCheckingIterator::removeLinguServiceEventListener(
880 const uno::Reference
< linguistic2::XLinguServiceEventListener
>& xListener
)
881 throw (uno::RuntimeException
)
885 m_aNotifyListeners
.removeInterface( xListener
);
891 void SAL_CALL
GrammarCheckingIterator::dispose()
892 throw (uno::RuntimeException
)
894 lang::EventObject
aEvt( (linguistic2::XProofreadingIterator
*) this );
895 m_aEventListeners
.disposeAndClear( aEvt
);
899 // ---- THREAD SAFE START ----
901 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
903 // releaase all UNO references
906 m_xBreakIterator
.clear();
908 // clear containers with UNO references AND have those references released
909 GCReferences_t aTmpEmpty1
;
911 FPQueue_t aTmpEmpty3
;
912 m_aGCReferencesByService
.swap( aTmpEmpty1
);
913 m_aDocIdMap
.swap( aTmpEmpty2
);
914 m_aFPEntriesQueue
.swap( aTmpEmpty3
);
916 // ---- THREAD SAFE END ----
920 void SAL_CALL
GrammarCheckingIterator::addEventListener(
921 const uno::Reference
< lang::XEventListener
>& xListener
)
922 throw (uno::RuntimeException
)
926 m_aEventListeners
.addInterface( xListener
);
931 void SAL_CALL
GrammarCheckingIterator::removeEventListener(
932 const uno::Reference
< lang::XEventListener
>& xListener
)
933 throw (uno::RuntimeException
)
937 m_aEventListeners
.removeInterface( xListener
);
942 void SAL_CALL
GrammarCheckingIterator::disposing( const lang::EventObject
&rSource
)
943 throw (uno::RuntimeException
)
945 // if the component (document) is disposing release all references
946 //!! There is no need to remove entries from the queue that are from this document
947 //!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
948 //!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
949 //!! And if an entry is currently checked by a grammar checker upon return the results
950 //!! should be ignored.
951 //!! Also GetOrCreateDocId will not use that very same Id again...
952 //!! All of the above resulting in that we only have to get rid of the implementation pointer here.
953 uno::Reference
< lang::XComponent
> xDoc( rSource
.Source
, uno::UNO_QUERY
);
956 // ---- THREAD SAFE START ----
957 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
958 m_aDocIdMap
.erase( xDoc
.get() );
959 // ---- THREAD SAFE END ----
964 uno::Reference
< util::XChangesBatch
> GrammarCheckingIterator::GetUpdateAccess() const
966 if (!m_xUpdateAccess
.is())
970 // get configuration provider
971 uno::Reference
< uno::XComponentContext
> xContext
= comphelper::getProcessComponentContext();
972 uno::Reference
< lang::XMultiServiceFactory
> xConfigurationProvider
=
973 configuration::theDefaultProvider::get( xContext
);
975 // get configuration update access
976 beans::PropertyValue aValue
;
977 aValue
.Name
= "nodepath";
978 aValue
.Value
= uno::makeAny( ::rtl::OUString("org.openoffice.Office.Linguistic/ServiceManager") );
979 uno::Sequence
< uno::Any
> aProps(1);
980 aProps
[0] <<= aValue
;
981 m_xUpdateAccess
= uno::Reference
< util::XChangesBatch
>(
982 xConfigurationProvider
->createInstanceWithArguments(
983 "com.sun.star.configuration.ConfigurationUpdateAccess", aProps
),
984 uno::UNO_QUERY_THROW
);
986 catch (uno::Exception
&)
991 return m_xUpdateAccess
;
995 void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
997 GCImplNames_t aTmpGCImplNamesByLang
;
1001 // get node names (locale iso strings) for configured grammar checkers
1002 uno::Reference
< container::XNameAccess
> xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW
);
1003 xNA
.set( xNA
->getByName( "GrammarCheckerList" ), uno::UNO_QUERY_THROW
);
1004 const uno::Sequence
< OUString
> aElementNames( xNA
->getElementNames() );
1005 const OUString
*pElementNames
= aElementNames
.getConstArray();
1007 sal_Int32 nLen
= aElementNames
.getLength();
1008 for (sal_Int32 i
= 0; i
< nLen
; ++i
)
1010 uno::Sequence
< OUString
> aImplNames
;
1011 uno::Any
aTmp( xNA
->getByName( pElementNames
[i
] ) );
1012 if (aTmp
>>= aImplNames
)
1014 if (aImplNames
.getLength() > 0)
1016 // only the first entry is used, there should be only one grammar checker per language
1017 const OUString
aImplName( aImplNames
[0] );
1018 const LanguageType nLang
= LanguageTag( pElementNames
[i
] ).getLanguageType();
1019 aTmpGCImplNamesByLang
[ nLang
] = aImplName
;
1024 DBG_ASSERT( 0, "failed to get aImplNames. Wrong type?" );
1028 catch (uno::Exception
&)
1030 DBG_ASSERT( 0, "exception caught. Failed to get configured services" );
1034 // ---- THREAD SAFE START ----
1035 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1036 m_aGCImplNamesByLang
= aTmpGCImplNamesByLang
;
1037 // ---- THREAD SAFE END ----
1044 sal_Bool SAL_CALL
GrammarCheckingIterator::supportsService(
1045 const OUString
& rServiceName
)
1046 throw(uno::RuntimeException
)
1048 uno::Sequence
< OUString
> aSNL
= getSupportedServiceNames();
1049 const OUString
* pArray
= aSNL
.getConstArray();
1050 for( sal_Int32 i
= 0; i
< aSNL
.getLength(); ++i
)
1051 if( pArray
[i
] == rServiceName
)
1057 OUString SAL_CALL
GrammarCheckingIterator::getImplementationName( ) throw (uno::RuntimeException
)
1059 return GrammarCheckingIterator_getImplementationName();
1063 uno::Sequence
< OUString
> SAL_CALL
GrammarCheckingIterator::getSupportedServiceNames( ) throw (uno::RuntimeException
)
1065 return GrammarCheckingIterator_getSupportedServiceNames();
1069 void GrammarCheckingIterator::SetServiceList(
1070 const lang::Locale
&rLocale
,
1071 const uno::Sequence
< OUString
> &rSvcImplNames
)
1073 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1075 LanguageType nLanguage
= LinguLocaleToLanguage( rLocale
);
1077 if (rSvcImplNames
.getLength() > 0)
1078 aImplName
= rSvcImplNames
[0]; // there is only one grammar checker per language
1080 if (!LinguIsUnspecified(nLanguage
) && nLanguage
!= LANGUAGE_DONTKNOW
)
1082 if (!aImplName
.isEmpty())
1083 m_aGCImplNamesByLang
[ nLanguage
] = aImplName
;
1085 m_aGCImplNamesByLang
.erase( nLanguage
);
1090 uno::Sequence
< OUString
> GrammarCheckingIterator::GetServiceList(
1091 const lang::Locale
&rLocale
) const
1093 ::osl::Guard
< ::osl::Mutex
> aGuard( MyMutex::get() );
1095 uno::Sequence
< OUString
> aRes(1);
1097 OUString aImplName
; // there is only one grammar checker per language
1098 LanguageType nLang
= LinguLocaleToLanguage( rLocale
);
1099 GCImplNames_t::const_iterator
aIt( m_aGCImplNamesByLang
.find( nLang
) );
1100 if (aIt
!= m_aGCImplNamesByLang
.end())
1101 aImplName
= aIt
->second
;
1103 if (!aImplName
.isEmpty())
1104 aRes
[0] = aImplName
;
1112 LinguDispatcher::DspType
GrammarCheckingIterator::GetDspType() const
1120 static OUString
GrammarCheckingIterator_getImplementationName() throw()
1122 return ::rtl::OUString( "com.sun.star.lingu2.ProofreadingIterator" );
1126 static uno::Sequence
< OUString
> GrammarCheckingIterator_getSupportedServiceNames() throw()
1128 uno::Sequence
< OUString
> aSNS( 1 );
1129 aSNS
.getArray()[0] = SN_GRAMMARCHECKINGITERATOR
;
1134 static uno::Reference
< uno::XInterface
> SAL_CALL
GrammarCheckingIterator_createInstance(
1135 const uno::Reference
< lang::XMultiServiceFactory
> & rxSMgr
)
1136 throw(uno::Exception
)
1138 return static_cast< ::cppu::OWeakObject
* >(new GrammarCheckingIterator( rxSMgr
));
1142 void * SAL_CALL
GrammarCheckingIterator_getFactory(
1143 const sal_Char
*pImplName
,
1144 lang::XMultiServiceFactory
*pServiceManager
,
1145 void * /*pRegistryKey*/ )
1148 if ( !GrammarCheckingIterator_getImplementationName().compareToAscii( pImplName
) )
1150 uno::Reference
< lang::XSingleServiceFactory
> xFactory
=
1151 cppu::createOneInstanceFactory(
1153 GrammarCheckingIterator_getImplementationName(),
1154 GrammarCheckingIterator_createInstance
,
1155 GrammarCheckingIterator_getSupportedServiceNames());
1156 // acquire, because we return an interface pointer instead of a reference
1157 xFactory
->acquire();
1158 pRet
= xFactory
.get();
1163 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */