blob: 1c555995007669bd1e6d71c13c943c5c183ce517 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
#include "precompiled_linguistic.hxx"
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/container/XEnumeration.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/container/XNameReplace.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/linguistic2/XSupportedLocales.hpp>
#include <com/sun/star/linguistic2/XProofreader.hpp>
#include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
#include <com/sun/star/linguistic2/SingleProofreadingError.hpp>
#include <com/sun/star/linguistic2/ProofreadingResult.hpp>
#include <com/sun/star/linguistic2/LinguServiceEvent.hpp>
#include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
#include <com/sun/star/registry/XRegistryKey.hpp>
#include <com/sun/star/text/TextMarkupType.hpp>
#include <com/sun/star/text/TextMarkupDescriptor.hpp>
#include <com/sun/star/text/XTextMarkup.hpp>
#include <com/sun/star/text/XMultiTextMarkup.hpp>
#include <com/sun/star/text/XFlatParagraph.hpp>
#include <com/sun/star/text/XFlatParagraphIterator.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/lang/XSingleComponentFactory.hpp>
#include <sal/config.h>
#include <osl/conditn.hxx>
#include <osl/thread.hxx>
#include <cppuhelper/implbase4.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <cppuhelper/interfacecontainer.h>
#include <cppuhelper/factory.hxx>
#include <i18npool/mslangid.hxx>
#include <unotools/processfactory.hxx>
#include <comphelper/extract.hxx>
#include <deque>
#include <map>
#include <vector>
#include "linguistic/misc.hxx"
#include "defs.hxx"
#include "lngopt.hxx"
#include "gciterator.hxx"
using ::rtl::OUString;
using namespace linguistic;
using namespace ::com::sun::star;
// forward declarations
static ::rtl::OUString GrammarCheckingIterator_getImplementationName() throw();
static uno::Sequence< OUString > GrammarCheckingIterator_getSupportedServiceNames() throw();
//////////////////////////////////////////////////////////////////////
// white space list: obtained from the fonts.config.txt of a Linux system.
static sal_Unicode aWhiteSpaces[] =
{
0x0020, /* SPACE */
0x00a0, /* NO-BREAK SPACE */
0x00ad, /* SOFT HYPHEN */
0x115f, /* HANGUL CHOSEONG FILLER */
0x1160, /* HANGUL JUNGSEONG FILLER */
0x1680, /* OGHAM SPACE MARK */
0x2000, /* EN QUAD */
0x2001, /* EM QUAD */
0x2002, /* EN SPACE */
0x2003, /* EM SPACE */
0x2004, /* THREE-PER-EM SPACE */
0x2005, /* FOUR-PER-EM SPACE */
0x2006, /* SIX-PER-EM SPACE */
0x2007, /* FIGURE SPACE */
0x2008, /* PUNCTUATION SPACE */
0x2009, /* THIN SPACE */
0x200a, /* HAIR SPACE */
0x200b, /* ZERO WIDTH SPACE */
0x200c, /* ZERO WIDTH NON-JOINER */
0x200d, /* ZERO WIDTH JOINER */
0x200e, /* LEFT-TO-RIGHT MARK */
0x200f, /* RIGHT-TO-LEFT MARK */
0x2028, /* LINE SEPARATOR */
0x2029, /* PARAGRAPH SEPARATOR */
0x202a, /* LEFT-TO-RIGHT EMBEDDING */
0x202b, /* RIGHT-TO-LEFT EMBEDDING */
0x202c, /* POP DIRECTIONAL FORMATTING */
0x202d, /* LEFT-TO-RIGHT OVERRIDE */
0x202e, /* RIGHT-TO-LEFT OVERRIDE */
0x202f, /* NARROW NO-BREAK SPACE */
0x205f, /* MEDIUM MATHEMATICAL SPACE */
0x2060, /* WORD JOINER */
0x2061, /* FUNCTION APPLICATION */
0x2062, /* INVISIBLE TIMES */
0x2063, /* INVISIBLE SEPARATOR */
0x206A, /* INHIBIT SYMMETRIC SWAPPING */
0x206B, /* ACTIVATE SYMMETRIC SWAPPING */
0x206C, /* INHIBIT ARABIC FORM SHAPING */
0x206D, /* ACTIVATE ARABIC FORM SHAPING */
0x206E, /* NATIONAL DIGIT SHAPES */
0x206F, /* NOMINAL DIGIT SHAPES */
0x3000, /* IDEOGRAPHIC SPACE */
0x3164, /* HANGUL FILLER */
0xfeff, /* ZERO WIDTH NO-BREAK SPACE */
0xffa0, /* HALFWIDTH HANGUL FILLER */
0xfff9, /* INTERLINEAR ANNOTATION ANCHOR */
0xfffa, /* INTERLINEAR ANNOTATION SEPARATOR */
0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */
};
static int nWhiteSpaces = sizeof( aWhiteSpaces ) / sizeof( aWhiteSpaces[0] );
static bool lcl_IsWhiteSpace( sal_Unicode cChar )
{
bool bFound = false;
for (int i = 0; i < nWhiteSpaces && !bFound; ++i)
{
if (cChar == aWhiteSpaces[i])
bFound = true;
}
return bFound;
}
static sal_Int32 lcl_SkipWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
{
// note having nStartPos point right behind the string is OK since that one
// is a correct end-of-sentence position to be returned from a grammar checker...
const sal_Int32 nLen = rText.getLength();
bool bIllegalArgument = false;
if (nStartPos < 0)
{
bIllegalArgument = true;
nStartPos = 0;
}
if (nStartPos > nLen)
{
bIllegalArgument = true;
nStartPos = nLen;
}
if (bIllegalArgument)
{
DBG_ASSERT( 0, "lcl_SkipWhiteSpaces: illegal arguments" );
}
sal_Int32 nRes = nStartPos;
if (0 <= nStartPos && nStartPos < nLen)
{
const sal_Unicode *pText = rText.getStr() + nStartPos;
while (nStartPos < nLen && lcl_IsWhiteSpace( *pText ))
++pText;
nRes = pText - rText.getStr();
}
DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_SkipWhiteSpaces return value out of range" );
return nRes;
}
static sal_Int32 lcl_BacktraceWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
{
// note: having nStartPos point right behind the string is OK since that one
// is a correct end-of-sentence position to be returned from a grammar checker...
const sal_Int32 nLen = rText.getLength();
bool bIllegalArgument = false;
if (nStartPos < 0)
{
bIllegalArgument = true;
nStartPos = 0;
}
if (nStartPos > nLen)
{
bIllegalArgument = true;
nStartPos = nLen;
}
if (bIllegalArgument)
{
DBG_ASSERT( 0, "lcl_BacktraceWhiteSpaces: illegal arguments" );
}
sal_Int32 nRes = nStartPos;
sal_Int32 nPosBefore = nStartPos - 1;
const sal_Unicode *pStart = rText.getStr();
if (0 <= nPosBefore && nPosBefore < nLen && lcl_IsWhiteSpace( pStart[ nPosBefore ] ))
{
nStartPos = nPosBefore;
if (0 <= nStartPos && nStartPos < nLen)
{
const sal_Unicode *pText = rText.getStr() + nStartPos;
while (pText > pStart && lcl_IsWhiteSpace( *pText ))
--pText;
// now add 1 since we want to point to the first char after the last char in the sentence...
nRes = pText - pStart + 1;
}
}
DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_BacktraceWhiteSpaces return value out of range" );
return nRes;
}
//////////////////////////////////////////////////////////////////////
extern "C" void workerfunc (void * gci)
{
((GrammarCheckingIterator*)gci)->DequeueAndCheck();
}
static lang::Locale lcl_GetPrimaryLanguageOfSentence(
uno::Reference< text::XFlatParagraph > xFlatPara,
sal_Int32 nStartIndex )
{
//get the language of the first word
return xFlatPara->getLanguageOfText( nStartIndex, 1 );
}
//////////////////////////////////////////////////////////////////////
/*
class MyThread : punlic osl::Thread
{
void run ()
{
DequeueAndCheck();
}
void own_terminate ()
{
m_bEnd = true;
wait (3000);
terminate ();
}
}
MyThread m_aQueue;
vois startGrammarChecking()
{
if (!m_aQueue.isRunning ())
m_aQueue.create ();
}
void stopGrammarChecking ()
{
if (m_aQueue.isRunning ())
m_aQueue.own_terminate ();
}
*/
GrammarCheckingIterator::GrammarCheckingIterator( const uno::Reference< lang::XMultiServiceFactory > & rxMgr ) :
m_xMSF( rxMgr ),
m_bEnd( sal_False ),
m_aCurCheckedDocId(),
m_bGCServicesChecked( sal_False ),
m_nDocIdCounter( 0 ),
m_nLastEndOfSentencePos( -1 ),
m_aEventListeners( MyMutex::get() ),
m_aNotifyListeners( MyMutex::get() )
{
osl_createThread( workerfunc, this );
}
GrammarCheckingIterator::~GrammarCheckingIterator()
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
}
sal_Int32 GrammarCheckingIterator::NextDocId()
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_nDocIdCounter += 1;
return m_nDocIdCounter;
}
OUString GrammarCheckingIterator::GetOrCreateDocId(
const uno::Reference< lang::XComponent > &xComponent )
{
// internal method; will always be called with locked mutex
OUString aRes;
if (xComponent.is())
{
if (m_aDocIdMap.find( xComponent.get() ) != m_aDocIdMap.end())
{
// return already existing entry
aRes = m_aDocIdMap[ xComponent.get() ];
}
else // add new entry
{
sal_Int32 nRes = NextDocId();
aRes = OUString::valueOf( nRes );
m_aDocIdMap[ xComponent.get() ] = aRes;
xComponent->addEventListener( this );
}
}
return aRes;
}
void GrammarCheckingIterator::AddEntry(
uno::WeakReference< text::XFlatParagraphIterator > xFlatParaIterator,
uno::WeakReference< text::XFlatParagraph > xFlatPara,
const OUString & rDocId,
sal_Int32 nStartIndex,
sal_Bool bAutomatic )
{
// we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
// but we always need a xFlatPara...
uno::Reference< text::XFlatParagraph > xPara( xFlatPara );
if (xPara.is())
{
FPEntry aNewFPEntry;
aNewFPEntry.m_xParaIterator = xFlatParaIterator;
aNewFPEntry.m_xPara = xFlatPara;
aNewFPEntry.m_aDocId = rDocId;
aNewFPEntry.m_nStartIndex = nStartIndex;
aNewFPEntry.m_bAutomatic = bAutomatic;
// add new entry to the end of this queue
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aFPEntriesQueue.push_back( aNewFPEntry );
// wake up the thread in order to do grammar checking
m_aWakeUpThread.set();
}
}
void GrammarCheckingIterator::ProcessResult(
const linguistic2::ProofreadingResult &rRes,
const uno::Reference< text::XFlatParagraphIterator > &rxFlatParagraphIterator,
bool bIsAutomaticChecking )
{
DBG_ASSERT( rRes.xFlatParagraph.is(), "xFlatParagraph is missing" );
//no guard necessary as no members are used
sal_Bool bContinueWithNextPara = sal_False;
if (!rRes.xFlatParagraph.is() || rRes.xFlatParagraph->isModified())
{
// if paragraph was modified/deleted meanwhile continue with the next one...
bContinueWithNextPara = sal_True;
}
else // paragraph is still unchanged...
{
//
// mark found errors...
//
sal_Int32 nTextLen = rRes.aText.getLength();
bool bBoundariesOk = 0 <= rRes.nStartOfSentencePosition && rRes.nStartOfSentencePosition <= nTextLen &&
0 <= rRes.nBehindEndOfSentencePosition && rRes.nBehindEndOfSentencePosition <= nTextLen &&
0 <= rRes.nStartOfNextSentencePosition && rRes.nStartOfNextSentencePosition <= nTextLen &&
rRes.nStartOfSentencePosition <= rRes.nBehindEndOfSentencePosition &&
rRes.nBehindEndOfSentencePosition <= rRes.nStartOfNextSentencePosition;
(void) bBoundariesOk;
DBG_ASSERT( bBoundariesOk, "inconsistent sentence boundaries" );
uno::Sequence< linguistic2::SingleProofreadingError > aErrors = rRes.aErrors;
uno::Reference< text::XMultiTextMarkup > xMulti( rRes.xFlatParagraph, uno::UNO_QUERY );
if (xMulti.is()) // use new API for markups
{
try
{
// length = number of found errors + 1 sentence markup
sal_Int32 nErrors = rRes.aErrors.getLength();
uno::Sequence< text::TextMarkupDescriptor > aDescriptors( nErrors + 1 );
text::TextMarkupDescriptor * pDescriptors = aDescriptors.getArray();
// at pos 0 .. nErrors-1 -> all grammar errors
for (sal_Int32 i = 0; i < nErrors; ++i)
{
const linguistic2::SingleProofreadingError &rError = rRes.aErrors[i];
text::TextMarkupDescriptor &rDesc = aDescriptors[i];
rDesc.nType = rError.nErrorType;
rDesc.nOffset = rError.nErrorStart;
rDesc.nLength = rError.nErrorLength;
// the proofreader may return SPELLING but right now our core
// does only handle PROOFREADING if the result is from the proofreader...
// (later on we may wish to color spelling errors found by the proofreader
// differently for example. But no special handling right now.
if (rDesc.nType == text::TextMarkupType::SPELLCHECK)
rDesc.nType = text::TextMarkupType::PROOFREADING;
}
// at pos nErrors -> sentence markup
// nSentenceLength: includes the white-spaces following the sentence end...
const sal_Int32 nSentenceLength = rRes.nStartOfNextSentencePosition - rRes.nStartOfSentencePosition;
pDescriptors[ nErrors ].nType = text::TextMarkupType::SENTENCE;
pDescriptors[ nErrors ].nOffset = rRes.nStartOfSentencePosition;
pDescriptors[ nErrors ].nLength = nSentenceLength;
xMulti->commitMultiTextMarkup( aDescriptors ) ;
}
catch (lang::IllegalArgumentException &)
{
DBG_ERROR( "commitMultiTextMarkup: IllegalArgumentException exception caught" );
}
}
// other sentences left to be checked in this paragraph?
if (rRes.nStartOfNextSentencePosition < rRes.aText.getLength())
{
AddEntry( rxFlatParagraphIterator, rRes.xFlatParagraph, rRes.aDocumentIdentifier, rRes.nStartOfNextSentencePosition, bIsAutomaticChecking );
}
else // current paragraph finished
{
// set "already checked" flag for the current flat paragraph
if (rRes.xFlatParagraph.is())
rRes.xFlatParagraph->setChecked( text::TextMarkupType::PROOFREADING, true );
bContinueWithNextPara = sal_True;
}
}
if (bContinueWithNextPara)
{
// we need to continue with the next paragraph
uno::Reference< text::XFlatParagraph > xFlatParaNext;
if (rxFlatParagraphIterator.is())
xFlatParaNext = rxFlatParagraphIterator->getNextPara();
{
AddEntry( rxFlatParagraphIterator, xFlatParaNext, rRes.aDocumentIdentifier, 0, bIsAutomaticChecking );
}
}
}
uno::Reference< linguistic2::XProofreader > GrammarCheckingIterator::GetGrammarChecker(
const lang::Locale &rLocale )
{
(void) rLocale;
uno::Reference< linguistic2::XProofreader > xRes;
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
// check supported locales for each grammarchecker if not already done
if (!m_bGCServicesChecked)
{
//GetAvailableGCSvcs_Impl();
GetConfiguredGCSvcs_Impl();
//GetMatchingGCSvcs_Impl();
m_bGCServicesChecked = sal_True;
}
const LanguageType nLang = MsLangId::convertLocaleToLanguage( rLocale );
GCImplNames_t::const_iterator aLangIt( m_aGCImplNamesByLang.find( nLang ) );
if (aLangIt != m_aGCImplNamesByLang.end()) // matching configured language found?
{
OUString aSvcImplName( aLangIt->second );
GCReferences_t::const_iterator aImplNameIt( m_aGCReferencesByService.find( aSvcImplName ) );
if (aImplNameIt != m_aGCReferencesByService.end()) // matching impl name found?
{
xRes = aImplNameIt->second;
}
else // the service is to be instatiated here for the first time...
{
try
{
uno::Reference< lang::XMultiServiceFactory > xMgr(
utl::getProcessServiceFactory(), uno::UNO_QUERY_THROW );
uno::Reference< linguistic2::XProofreader > xGC(
xMgr->createInstance( aSvcImplName ), uno::UNO_QUERY_THROW );
uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW );
if (xSuppLoc->hasLocale( rLocale ))
{
m_aGCReferencesByService[ aSvcImplName ] = xGC;
xRes = xGC;
uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xBC( xGC, uno::UNO_QUERY );
if (xBC.is())
xBC->addLinguServiceEventListener( this );
}
else
{
DBG_ASSERT( 0, "grammar checker does not support required locale" );
}
}
catch (uno::Exception &)
{
DBG_ASSERT( 0, "instantiating grammar checker failed" );
}
}
}
// ---- THREAD SAFE END ----
return xRes;
}
void GrammarCheckingIterator::DequeueAndCheck()
{
uno::Sequence< sal_Int32 > aLangPortions;
uno::Sequence< lang::Locale > aLangPortionsLocale;
// ---- THREAD SAFE START ----
bool bEnd = false;
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
bEnd = m_bEnd;
}
// ---- THREAD SAFE END ----
while (!bEnd)
{
// ---- THREAD SAFE START ----
bool bQueueEmpty = false;
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
bQueueEmpty = m_aFPEntriesQueue.empty();
}
// ---- THREAD SAFE END ----
if (!bQueueEmpty)
{
uno::Reference< text::XFlatParagraphIterator > xFPIterator;
uno::Reference< text::XFlatParagraph > xFlatPara;
FPEntry aFPEntryItem;
OUString aCurDocId;
sal_Bool bModified = sal_False;
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
aFPEntryItem = m_aFPEntriesQueue.front();
xFPIterator = aFPEntryItem.m_xParaIterator;
xFlatPara = aFPEntryItem.m_xPara;
m_aCurCheckedDocId = aFPEntryItem.m_aDocId;
aCurDocId = m_aCurCheckedDocId;
m_aFPEntriesQueue.pop_front();
}
// ---- THREAD SAFE END ----
if (xFlatPara.is() && xFPIterator.is())
{
OUString aCurTxt( xFlatPara->getText() );
lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, aFPEntryItem.m_nStartIndex );
bModified = xFlatPara->isModified();
if (!bModified)
{
// ---- THREAD SAFE START ----
::osl::ClearableGuard< ::osl::Mutex > aGuard( MyMutex::get() );
sal_Int32 nStartPos = aFPEntryItem.m_nStartIndex;
sal_Int32 nSuggestedEnd = GetSuggestedEndOfSentence( aCurTxt, nStartPos, aCurLocale );
DBG_ASSERT( (nSuggestedEnd == 0 && aCurTxt.getLength() == 0) || nSuggestedEnd > nStartPos,
"nSuggestedEndOfSentencePos calculation failed?" );
linguistic2::ProofreadingResult aRes;
uno::Reference< linguistic2::XProofreader > xGC( GetGrammarChecker( aCurLocale ), uno::UNO_QUERY );
if (xGC.is())
{
aGuard.clear();
uno::Sequence< beans::PropertyValue > aEmptyProps;
aRes = xGC->doProofreading( aCurDocId, aCurTxt, aCurLocale, nStartPos, nSuggestedEnd, aEmptyProps );
//!! work-around to prevent looping if the grammar checker
//!! failed to properly identify the sentence end
if (aRes.nBehindEndOfSentencePosition <= nStartPos)
{
DBG_ASSERT( 0, "!! Grammarchecker failed to provide end of sentence !!" );
aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
}
aRes.xFlatParagraph = xFlatPara;
aRes.nStartOfSentencePosition = nStartPos;
}
else
{
// no grammar checker -> no error
// but we need to provide the data below in order to continue with the next sentence
aRes.aDocumentIdentifier = aCurDocId;
aRes.xFlatParagraph = xFlatPara;
aRes.aText = aCurTxt;
aRes.aLocale = aCurLocale;
aRes.nStartOfSentencePosition = nStartPos;
aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
}
aRes.nStartOfNextSentencePosition = lcl_SkipWhiteSpaces( aCurTxt, aRes.nBehindEndOfSentencePosition );
aRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces( aCurTxt, aRes.nStartOfNextSentencePosition );
//guard has to be cleared as ProcessResult calls out of this class
aGuard.clear();
ProcessResult( aRes, xFPIterator, aFPEntryItem.m_bAutomatic );
// ---- THREAD SAFE END ----
}
else
{
// the paragraph changed meanwhile... (and maybe is still edited)
// thus we simply continue to ask for the next to be checked.
uno::Reference< text::XFlatParagraph > xFlatParaNext( xFPIterator->getNextPara() );
AddEntry( xFPIterator, xFlatParaNext, aCurDocId, 0, aFPEntryItem.m_bAutomatic );
}
}
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aCurCheckedDocId = OUString();
}
// ---- THREAD SAFE END ----
}
else
{
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
// Check queue state again
if (m_aFPEntriesQueue.empty())
m_aWakeUpThread.reset();
}
// ---- THREAD SAFE END ----
//if the queue is empty
// IMPORTANT: Don't call condition.wait() with locked
// mutex. Otherwise you would keep out other threads
// to add entries to the queue! A condition is thread-
// safe implemented.
m_aWakeUpThread.wait();
}
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
bEnd = m_bEnd;
}
// ---- THREAD SAFE END ----
}
//!! This one must be the very last statement to call in this function !!
m_aRequestEndThread.set();
}
void SAL_CALL GrammarCheckingIterator::startProofreading(
const uno::Reference< ::uno::XInterface > & xDoc,
const uno::Reference< text::XFlatParagraphIteratorProvider > & xIteratorProvider )
throw (uno::RuntimeException, lang::IllegalArgumentException)
{
// get paragraph to start checking with
const bool bAutomatic = true;
uno::Reference<text::XFlatParagraphIterator> xFPIterator = xIteratorProvider->getFlatParagraphIterator(
text::TextMarkupType::PROOFREADING, bAutomatic );
uno::Reference< text::XFlatParagraph > xPara( xFPIterator.is()? xFPIterator->getFirstPara() : NULL );
uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
if (xPara.is() && xComponent.is())
{
OUString aDocId = GetOrCreateDocId( xComponent );
// create new entry and add it to queue
AddEntry( xFPIterator, xPara, aDocId, 0, bAutomatic );
}
// ---- THREAD SAFE END ----
}
linguistic2::ProofreadingResult SAL_CALL GrammarCheckingIterator::checkSentenceAtPosition(
const uno::Reference< uno::XInterface >& xDoc,
const uno::Reference< text::XFlatParagraph >& xFlatPara,
const OUString& rText,
const lang::Locale& rLocale,
sal_Int32 nStartOfSentencePos,
sal_Int32 nSuggestedEndOfSentencePos,
sal_Int32 nErrorPosInPara )
throw (lang::IllegalArgumentException, uno::RuntimeException)
{
(void) rLocale;
// for the context menu...
linguistic2::ProofreadingResult aRes;
uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
if (xFlatPara.is() && xComponent.is() &&
( nErrorPosInPara < 0 || nErrorPosInPara < rText.getLength()))
{
// iterate through paragraph until we find the sentence we are interested in
linguistic2::ProofreadingResult aTmpRes;
sal_Int32 nStartPos = nStartOfSentencePos >= 0 ? nStartOfSentencePos : 0;
bool bFound = false;
do
{
lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, nStartPos );
sal_Int32 nOldStartOfSentencePos = nStartPos;
uno::Reference< linguistic2::XProofreader > xGC;
OUString aDocId;
// ---- THREAD SAFE START ----
{
::osl::ClearableGuard< ::osl::Mutex > aGuard( MyMutex::get() );
aDocId = GetOrCreateDocId( xComponent );
nSuggestedEndOfSentencePos = GetSuggestedEndOfSentence( rText, nStartPos, aCurLocale );
DBG_ASSERT( nSuggestedEndOfSentencePos > nStartPos, "nSuggestedEndOfSentencePos calculation failed?" );
xGC = GetGrammarChecker( aCurLocale );
}
// ---- THREAD SAFE START ----
sal_Int32 nEndPos = -1;
if (xGC.is())
{
uno::Sequence< beans::PropertyValue > aEmptyProps;
aTmpRes = xGC->doProofreading( aDocId, rText, aCurLocale, nStartPos, nSuggestedEndOfSentencePos, aEmptyProps );
//!! work-around to prevent looping if the grammar checker
//!! failed to properly identify the sentence end
if (aTmpRes.nBehindEndOfSentencePosition <= nStartPos)
{
DBG_ASSERT( 0, "!! Grammarchecker failed to provide end of sentence !!" );
aTmpRes.nBehindEndOfSentencePosition = nSuggestedEndOfSentencePos;
}
aTmpRes.xFlatParagraph = xFlatPara;
aTmpRes.nStartOfSentencePosition = nStartPos;
nEndPos = aTmpRes.nBehindEndOfSentencePosition;
if ((nErrorPosInPara< 0 || nStartPos <= nErrorPosInPara) && nErrorPosInPara < nEndPos)
bFound = true;
}
if (nEndPos == -1) // no result from grammar checker
nEndPos = nSuggestedEndOfSentencePos;
nStartPos = lcl_SkipWhiteSpaces( rText, nEndPos );
aTmpRes.nBehindEndOfSentencePosition = nEndPos;
aTmpRes.nStartOfNextSentencePosition = nStartPos;
aTmpRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces( rText, aTmpRes.nStartOfNextSentencePosition );
// prevent endless loop by forcefully advancing if needs be...
if (nStartPos <= nOldStartOfSentencePos)
{
DBG_ASSERT( 0, "end-of-sentence detection failed?" );
nStartPos = nOldStartOfSentencePos + 1;
}
}
while (!bFound && nStartPos < rText.getLength());
if (bFound && !xFlatPara->isModified())
aRes = aTmpRes;
}
return aRes;
}
sal_Int32 GrammarCheckingIterator::GetSuggestedEndOfSentence(
const OUString &rText,
sal_Int32 nSentenceStartPos,
const lang::Locale &rLocale )
{
// internal method; will always be called with locked mutex
uno::Reference< i18n::XBreakIterator > xBreakIterator;
if (!m_xBreakIterator.is())
{
uno::Reference< lang::XMultiServiceFactory > xMSF = ::comphelper::getProcessServiceFactory();
if ( xMSF.is() )
xBreakIterator = uno::Reference < i18n::XBreakIterator >( xMSF->createInstance(
::rtl::OUString::createFromAscii("com.sun.star.i18n.BreakIterator") ), uno::UNO_QUERY );
}
sal_Int32 nTextLen = rText.getLength();
sal_Int32 nEndPosition = nTextLen;
if (m_xBreakIterator.is())
{
sal_Int32 nTmpStartPos = nSentenceStartPos;
do
{
nEndPosition = nTextLen;
if (nTmpStartPos < nTextLen)
nEndPosition = m_xBreakIterator->endOfSentence( rText, nTmpStartPos, rLocale );
if (nEndPosition < 0)
nEndPosition = nTextLen;
++nTmpStartPos;
}
while (nEndPosition <= nSentenceStartPos && nEndPosition < nTextLen);
if (nEndPosition > nTextLen)
nEndPosition = nTextLen;
}
return nEndPosition;
}
void SAL_CALL GrammarCheckingIterator::resetIgnoreRules( )
throw (uno::RuntimeException)
{
GCReferences_t::iterator aIt( m_aGCReferencesByService.begin() );
while (aIt != m_aGCReferencesByService.end())
{
uno::Reference< linguistic2::XProofreader > xGC( aIt->second );
if (xGC.is())
xGC->resetIgnoreRules();
++aIt;
}
}
sal_Bool SAL_CALL GrammarCheckingIterator::isProofreading(
const uno::Reference< uno::XInterface >& xDoc )
throw (uno::RuntimeException)
{
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
sal_Bool bRes = sal_False;
uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
if (xComponent.is())
{
// if the component was already used in one of the two calls to check text
// i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
// m_aDocIdMap unless the document already disposed.
// If it is not found then it is not yet being checked (or requested to being checked)
const DocMap_t::const_iterator aIt( m_aDocIdMap.find( xComponent.get() ) );
if (aIt != m_aDocIdMap.end())
{
// check in document is checked automatically in the background...
OUString aDocId = aIt->second;
if (m_aCurCheckedDocId.getLength() > 0 && m_aCurCheckedDocId == aDocId)
{
// an entry for that document was dequed and is currently being checked.
bRes = sal_True;
}
else
{
// we need to check if there is an entry for that document in the queue...
// That is the document is going to be checked sooner or later.
sal_Int32 nSize = m_aFPEntriesQueue.size();
for (sal_Int32 i = 0; i < nSize && !bRes; ++i)
{
if (aDocId == m_aFPEntriesQueue[i].m_aDocId)
bRes = sal_True;
}
}
}
}
// ---- THREAD SAFE END ----
return bRes;
}
void SAL_CALL GrammarCheckingIterator::processLinguServiceEvent(
const linguistic2::LinguServiceEvent& rLngSvcEvent )
throw (uno::RuntimeException)
{
if (rLngSvcEvent.nEvent == linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN)
{
try
{
uno::Reference< uno::XInterface > xThis( dynamic_cast< XLinguServiceEventBroadcaster * >(this) );
linguistic2::LinguServiceEvent aEvent( xThis, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN );
m_aNotifyListeners.notifyEach(
&linguistic2::XLinguServiceEventListener::processLinguServiceEvent,
aEvent);
}
catch (uno::RuntimeException &)
{
throw;
}
catch (::uno::Exception &rE)
{
(void) rE;
// ignore
DBG_WARNING1("processLinguServiceEvent: exception:\n%s",
OUStringToOString(rE.Message, RTL_TEXTENCODING_UTF8).getStr());
}
}
}
sal_Bool SAL_CALL GrammarCheckingIterator::addLinguServiceEventListener(
const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
throw (uno::RuntimeException)
{
if (xListener.is())
{
// ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aNotifyListeners.addInterface( xListener );
}
return sal_True;
}
sal_Bool SAL_CALL GrammarCheckingIterator::removeLinguServiceEventListener(
const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
throw (uno::RuntimeException)
{
if (xListener.is())
{
// ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aNotifyListeners.removeInterface( xListener );
}
return sal_True;
}
void SAL_CALL GrammarCheckingIterator::dispose()
throw (uno::RuntimeException)
{
lang::EventObject aEvt( (linguistic2::XProofreadingIterator *) this );
m_aEventListeners.disposeAndClear( aEvt );
//
// now end the thread...
//
m_aRequestEndThread.reset();
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_bEnd = sal_True;
}
// ---- THREAD SAFE END ----
m_aWakeUpThread.set();
const TimeValue aTime = { 3, 0 }; // wait 3 seconds...
m_aRequestEndThread.wait( &aTime );
// if the call ends because of time-out we will end anyway...
// ---- THREAD SAFE START ----
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
// releaase all UNO references
m_xMSF.clear();
m_xBreakIterator.clear();
// clear containers with UNO references AND have those references released
GCReferences_t aTmpEmpty1;
DocMap_t aTmpEmpty2;
FPQueue_t aTmpEmpty3;
m_aGCReferencesByService.swap( aTmpEmpty1 );
m_aDocIdMap.swap( aTmpEmpty2 );
m_aFPEntriesQueue.swap( aTmpEmpty3 );
}
// ---- THREAD SAFE END ----
}
void SAL_CALL GrammarCheckingIterator::addEventListener(
const uno::Reference< lang::XEventListener >& xListener )
throw (uno::RuntimeException)
{
if (xListener.is())
{
// ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aEventListeners.addInterface( xListener );
}
}
void SAL_CALL GrammarCheckingIterator::removeEventListener(
const uno::Reference< lang::XEventListener >& xListener )
throw (uno::RuntimeException)
{
if (xListener.is())
{
// ::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aEventListeners.removeInterface( xListener );
}
}
void SAL_CALL GrammarCheckingIterator::disposing( const lang::EventObject &rSource )
throw (uno::RuntimeException)
{
// if the component (document) is disposing release all references
//!! There is no need to remove entries from the queue that are from this document
//!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
//!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
//!! And if an entry is currently checked by a grammar checker upon return the results
//!! should be ignored.
//!! Also GetOrCreateDocId will not use that very same Id again...
//!! All of the above resulting in that we only have to get rid of the implementation pointer here.
uno::Reference< lang::XComponent > xDoc( rSource.Source, uno::UNO_QUERY );
if (xDoc.is())
{
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aDocIdMap.erase( xDoc.get() );
// ---- THREAD SAFE END ----
}
}
uno::Reference< util::XChangesBatch > GrammarCheckingIterator::GetUpdateAccess() const
{
if (!m_xUpdateAccess.is())
{
try
{
// get configuration provider
uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider;
uno::Reference< lang::XMultiServiceFactory > xMgr = utl::getProcessServiceFactory();
if (xMgr.is())
{
xConfigurationProvider = uno::Reference< lang::XMultiServiceFactory > (
xMgr->createInstance( OUString( RTL_CONSTASCII_USTRINGPARAM(
"com.sun.star.configuration.ConfigurationProvider" ) ) ),
uno::UNO_QUERY_THROW ) ;
}
// get configuration update access
beans::PropertyValue aValue;
aValue.Name = A2OU( "nodepath" );
aValue.Value = uno::makeAny( A2OU("org.openoffice.Office.Linguistic/ServiceManager") );
uno::Sequence< uno::Any > aProps(1);
aProps[0] <<= aValue;
m_xUpdateAccess = uno::Reference< util::XChangesBatch >(
xConfigurationProvider->createInstanceWithArguments(
A2OU( "com.sun.star.configuration.ConfigurationUpdateAccess" ), aProps ),
uno::UNO_QUERY_THROW );
}
catch (uno::Exception &)
{
}
}
return m_xUpdateAccess;
}
void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
{
GCImplNames_t aTmpGCImplNamesByLang;
try
{
// get node names (locale iso strings) for configured grammar checkers
uno::Reference< container::XNameAccess > xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW );
xNA.set( xNA->getByName( A2OU("GrammarCheckerList") ), uno::UNO_QUERY_THROW );
const uno::Sequence< OUString > aElementNames( xNA->getElementNames() );
const OUString *pElementNames = aElementNames.getConstArray();
sal_Int32 nLen = aElementNames.getLength();
for (sal_Int32 i = 0; i < nLen; ++i)
{
uno::Sequence< OUString > aImplNames;
uno::Any aTmp( xNA->getByName( pElementNames[i] ) );
if (aTmp >>= aImplNames)
{
if (aImplNames.getLength() > 0)
{
// only the first entry is used, there should be only one grammar checker per language
const OUString aImplName( aImplNames[0] );
const LanguageType nLang = MsLangId::convertIsoStringToLanguage( pElementNames[i] );
aTmpGCImplNamesByLang[ nLang ] = aImplName;
}
}
else
{
DBG_ASSERT( 0, "failed to get aImplNames. Wrong type?" );
}
}
}
catch (uno::Exception &)
{
DBG_ASSERT( 0, "exception caught. Failed to get configured services" );
}
{
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aGCImplNamesByLang = aTmpGCImplNamesByLang;
// ---- THREAD SAFE END ----
}
}
/*
void GrammarCheckingIterator::GetMatchingGCSvcs_Impl()
{
GCImplNames_t aTmpGCImplNamesByLang;
try
{
// get node names (locale iso strings) for configured grammar checkers
uno::Reference< container::XNameAccess > xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW );
xNA.set( xNA->getByName( A2OU("GrammarCheckers") ), uno::UNO_QUERY_THROW );
const uno::Sequence< OUString > aGCImplNames( xNA->getElementNames() );
const OUString *pGCImplNames = aGCImplNames.getConstArray();
sal_Int32 nLen = aGCImplNames.getLength();
for (sal_Int32 i = 0; i < nLen; ++i)
{
uno::Reference< container::XNameAccess > xTmpNA( xNA->getByName( pGCImplNames[i] ), uno::UNO_QUERY_THROW );
uno::Any aTmp( xTmpNA->getByName( A2OU("Locales") ) );
uno::Sequence< OUString > aIsoLocaleNames;
if (aTmp >>= aIsoLocaleNames)
{
const OUString *pIsoLocaleNames = aIsoLocaleNames.getConstArray();
for (sal_Int32 k = 0; k < aIsoLocaleNames.getLength(); ++k)
{
// if there are more grammar checkers for one language, for the time being,
// the last one found here will win...
const LanguageType nLang = MsLangId::convertIsoStringToLanguage( pIsoLocaleNames[k] );
aTmpGCImplNamesByLang[ nLang ] = pGCImplNames[i];
}
}
else
{
DBG_ASSERT( 0, "failed to get aImplNames. Wrong type?" );
}
}
}
catch (uno::Exception &)
{
DBG_ASSERT( 0, "exception caught. Failed to get matching grammar checker services" );
}
{
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aGCImplNamesByLang = aTmpGCImplNamesByLang;
// ---- THREAD SAFE END ----
}
}
*/
/*
void GrammarCheckingIterator::GetAvailableGCSvcs_Impl()
{
// internal method; will always be called with locked mutex
if (m_xMSF.is())
{
uno::Reference< container::XContentEnumerationAccess > xEnumAccess( m_xMSF, uno::UNO_QUERY );
uno::Reference< container::XEnumeration > xEnum;
if (xEnumAccess.is())
xEnum = xEnumAccess->createContentEnumeration( A2OU( SN_GRAMMARCHECKER ) );
if (xEnum.is())
{
while (xEnum->hasMoreElements())
{
uno::Any aCurrent = xEnum->nextElement();
uno::Reference< lang::XSingleComponentFactory > xCompFactory;
uno::Reference< lang::XSingleServiceFactory > xFactory;
uno::Reference< uno::XComponentContext > xContext;
uno::Reference< beans::XPropertySet > xProps( m_xMSF, uno::UNO_QUERY );
xProps->getPropertyValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "DefaultContext" ))) >>= xContext;
if ( xContext.is() &&
(cppu::extractInterface( xCompFactory, aCurrent ) ||
cppu::extractInterface( xFactory, aCurrent )) )
{
try
{
uno::Reference< linguistic2::XProofreader > xSvc( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY );
if (xSvc.is())
{
OUString aImplName;
uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
if (xInfo.is())
aImplName = xInfo->getImplementationName();
DBG_ASSERT( aImplName.getLength(), "empty implementation name" );
uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xSvc, uno::UNO_QUERY );
DBG_ASSERT( xSuppLoc.is(), "interfaces not supported" );
if (xSuppLoc.is() && aImplName.getLength() > 0)
{
uno::Sequence< lang::Locale > aLocaleSequence( xSuppLoc->getLocales() );
// ---- THREAD SAFE START ----
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
m_aGCLocalesByService[ aImplName ] = aLocaleSequence;
m_aGCReferencesByService[ aImplName ] = xSvc;
// ---- THREAD SAFE END ----
}
}
}
catch (uno::Exception &)
{
DBG_ASSERT( 0, "instantiating grammar checker failed" );
}
}
}
}
}
}
*/
sal_Bool SAL_CALL GrammarCheckingIterator::supportsService(
const OUString & rServiceName )
throw(uno::RuntimeException)
{
uno::Sequence< OUString > aSNL = getSupportedServiceNames();
const OUString * pArray = aSNL.getConstArray();
for( sal_Int32 i = 0; i < aSNL.getLength(); ++i )
if( pArray[i] == rServiceName )
return sal_True;
return sal_False;
}
OUString SAL_CALL GrammarCheckingIterator::getImplementationName( ) throw (uno::RuntimeException)
{
return GrammarCheckingIterator_getImplementationName();
}
uno::Sequence< OUString > SAL_CALL GrammarCheckingIterator::getSupportedServiceNames( ) throw (uno::RuntimeException)
{
return GrammarCheckingIterator_getSupportedServiceNames();
}
void GrammarCheckingIterator::SetServiceList(
const lang::Locale &rLocale,
const uno::Sequence< OUString > &rSvcImplNames )
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
LanguageType nLanguage = LocaleToLanguage( rLocale );
OUString aImplName;
if (rSvcImplNames.getLength() > 0)
aImplName = rSvcImplNames[0]; // there is only one grammar checker per language
if (nLanguage != LANGUAGE_NONE && nLanguage != LANGUAGE_DONTKNOW)
{
if (aImplName.getLength() > 0)
m_aGCImplNamesByLang[ nLanguage ] = aImplName;
else
m_aGCImplNamesByLang.erase( nLanguage );
}
}
uno::Sequence< OUString > GrammarCheckingIterator::GetServiceList(
const lang::Locale &rLocale ) const
{
::osl::Guard< ::osl::Mutex > aGuard( MyMutex::get() );
uno::Sequence< OUString > aRes(1);
OUString aImplName; // there is only one grammar checker per language
LanguageType nLang = LocaleToLanguage( rLocale );
GCImplNames_t::const_iterator aIt( m_aGCImplNamesByLang.find( nLang ) );
if (aIt != m_aGCImplNamesByLang.end())
aImplName = aIt->second;
if (aImplName.getLength() > 0)
aRes[0] = aImplName;
else
aRes.realloc(0);
return aRes;
}
LinguDispatcher::DspType GrammarCheckingIterator::GetDspType() const
{
return DSP_GRAMMAR;
}
///////////////////////////////////////////////////////////////////////////
static OUString GrammarCheckingIterator_getImplementationName() throw()
{
return A2OU( "com.sun.star.lingu2.ProofreadingIterator" );
}
static uno::Sequence< OUString > GrammarCheckingIterator_getSupportedServiceNames() throw()
{
uno::Sequence< OUString > aSNS( 1 );
aSNS.getArray()[0] = A2OU( SN_GRAMMARCHECKINGITERATOR );
return aSNS;
}
static uno::Reference< uno::XInterface > SAL_CALL GrammarCheckingIterator_createInstance(
const uno::Reference< lang::XMultiServiceFactory > & rxSMgr )
throw(uno::Exception)
{
return static_cast< ::cppu::OWeakObject * >(new GrammarCheckingIterator( rxSMgr ));
}
void * SAL_CALL GrammarCheckingIterator_getFactory(
const sal_Char *pImplName,
lang::XMultiServiceFactory *pServiceManager,
void * /*pRegistryKey*/ )
{
void * pRet = 0;
if ( !GrammarCheckingIterator_getImplementationName().compareToAscii( pImplName ) )
{
uno::Reference< lang::XSingleServiceFactory > xFactory =
cppu::createOneInstanceFactory(
pServiceManager,
GrammarCheckingIterator_getImplementationName(),
GrammarCheckingIterator_createInstance,
GrammarCheckingIterator_getSupportedServiceNames());
// acquire, because we return an interface pointer instead of a reference
xFactory->acquire();
pRet = xFactory.get();
}
return pRet;
}