Version 4.0.2.1, tag libreoffice-4.0.2.1
[LibreOffice.git] / linguistic / source / gciterator.cxx
blob792d406a3e845eccc41192d1dfb8e7a1186bf596
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
59 #include <deque>
60 #include <map>
61 #include <vector>
63 #include "linguistic/misc.hxx"
64 #include "defs.hxx"
65 #include "lngopt.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[] =
82 0x0020, /* SPACE */
83 0x00a0, /* NO-BREAK SPACE */
84 0x00ad, /* SOFT HYPHEN */
85 0x115f, /* HANGUL CHOSEONG FILLER */
86 0x1160, /* HANGUL JUNGSEONG FILLER */
87 0x1680, /* OGHAM SPACE MARK */
88 0x2000, /* EN QUAD */
89 0x2001, /* EM QUAD */
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 )
136 bool bFound = false;
137 for (int i = 0; i < nWhiteSpaces && !bFound; ++i)
139 if (cChar == aWhiteSpaces[i])
140 bFound = true;
142 return bFound;
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;
152 if (nStartPos < 0)
154 bIllegalArgument = true;
155 nStartPos = 0;
157 if (nStartPos > nLen)
159 bIllegalArgument = true;
160 nStartPos = nLen;
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 ))
172 ++pText;
173 nRes = pText - rText.getStr();
176 DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_SkipWhiteSpaces return value out of range" );
177 return nRes;
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;
187 if (nStartPos < 0)
189 bIllegalArgument = true;
190 nStartPos = 0;
192 if (nStartPos > nLen)
194 bIllegalArgument = true;
195 nStartPos = nLen;
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 ))
212 --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" );
219 return nRes;
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 ) :
238 m_xMSF( rxMgr ),
239 m_bEnd( sal_False ),
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()
253 TerminateThread();
256 void GrammarCheckingIterator::TerminateThread()
258 oslThread t;
260 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
261 t = m_thread;
262 m_thread = 0;
263 m_bEnd = sal_True;
264 m_aWakeUpThread.set();
266 if (t != 0)
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
286 OUString aRes;
287 if (xComponent.is())
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 );
302 return aRes;
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 );
316 if (xPara.is())
318 FPEntry aNewFPEntry;
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 )
436 (void) 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;
472 xRes = xGC;
474 uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xBC( xGC, uno::UNO_QUERY );
475 if (xBC.is())
476 xBC->addLinguServiceEventListener( this );
478 else
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 ----
491 return xRes;
495 void GrammarCheckingIterator::DequeueAndCheck()
497 uno::Sequence< sal_Int32 > aLangPortions;
498 uno::Sequence< lang::Locale > aLangPortionsLocale;
500 for (;;)
502 // ---- THREAD SAFE START ----
503 bool bQueueEmpty = false;
505 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
506 if (m_bEnd)
508 break;
510 bQueueEmpty = m_aFPEntriesQueue.empty();
512 // ---- THREAD SAFE END ----
514 if (!bQueueEmpty)
516 uno::Reference< text::XFlatParagraphIterator > xFPIterator;
517 uno::Reference< text::XFlatParagraph > xFlatPara;
518 FPEntry aFPEntryItem;
519 OUString aCurDocId;
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();
540 if (!bModified)
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 );
553 if (xGC.is())
555 aGuard.clear();
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
561 if (
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;
573 else
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
588 aGuard.clear();
589 ProcessResult( aRes, xFPIterator, aFPEntryItem.m_bAutomatic );
590 // ---- THREAD SAFE END ----
592 else
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 ----
608 else
610 // ---- THREAD SAFE START ----
612 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
613 if (m_bEnd)
615 break;
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-
627 // safe implemented.
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)
669 (void) rLocale;
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;
683 bool bFound = false;
686 lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, nStartPos );
687 sal_Int32 nOldStartOfSentencePos = nStartPos;
688 uno::Reference< linguistic2::XProofreader > xGC;
689 OUString aDocId;
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;
702 if (xGC.is())
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)
720 bFound = true;
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())
739 aRes = aTmpRes;
742 return aRes;
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;
769 ++nTmpStartPos;
771 while (nEndPosition <= nSentenceStartPos && nEndPosition < nTextLen);
772 if (nEndPosition > nTextLen)
773 nEndPosition = nTextLen;
774 return nEndPosition;
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 );
785 if (xGC.is())
786 xGC->resetIgnoreRules();
787 ++aIt;
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 );
802 if (xComponent.is())
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.
816 bRes = sal_True;
818 else
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)
827 bRes = sal_True;
832 // ---- THREAD SAFE END ----
834 return bRes;
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,
850 aEvent);
852 catch (uno::RuntimeException &)
854 throw;
856 catch (const ::uno::Exception &rE)
858 (void) rE;
859 // ignore
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)
871 if (xListener.is())
873 m_aNotifyListeners.addInterface( xListener );
875 return sal_True;
879 sal_Bool SAL_CALL GrammarCheckingIterator::removeLinguServiceEventListener(
880 const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
881 throw (uno::RuntimeException)
883 if (xListener.is())
885 m_aNotifyListeners.removeInterface( xListener );
887 return sal_True;
891 void SAL_CALL GrammarCheckingIterator::dispose()
892 throw (uno::RuntimeException)
894 lang::EventObject aEvt( (linguistic2::XProofreadingIterator *) this );
895 m_aEventListeners.disposeAndClear( aEvt );
897 TerminateThread();
899 // ---- THREAD SAFE START ----
901 ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
903 // releaase all UNO references
905 m_xMSF.clear();
906 m_xBreakIterator.clear();
908 // clear containers with UNO references AND have those references released
909 GCReferences_t aTmpEmpty1;
910 DocMap_t aTmpEmpty2;
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)
924 if (xListener.is())
926 m_aEventListeners.addInterface( xListener );
931 void SAL_CALL GrammarCheckingIterator::removeEventListener(
932 const uno::Reference< lang::XEventListener >& xListener )
933 throw (uno::RuntimeException)
935 if (xListener.is())
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 );
954 if (xDoc.is())
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;
1022 else
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 )
1052 return sal_True;
1053 return sal_False;
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 );
1076 OUString aImplName;
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;
1084 else
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;
1105 else
1106 aRes.realloc(0);
1108 return aRes;
1112 LinguDispatcher::DspType GrammarCheckingIterator::GetDspType() const
1114 return DSP_GRAMMAR;
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 ;
1130 return aSNS;
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*/ )
1147 void * pRet = 0;
1148 if ( !GrammarCheckingIterator_getImplementationName().compareToAscii( pImplName ) )
1150 uno::Reference< lang::XSingleServiceFactory > xFactory =
1151 cppu::createOneInstanceFactory(
1152 pServiceManager,
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();
1160 return pRet;
1163 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */