Update ooo320-m1
[ooovba.git] / lingucomponent / source / spellcheck / spell / sspellimp.cxx
blob80815b67135c75fc14b24320d2c6f110030f489f
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: sspellimp.cxx,v $
10 * $Revision: 1.24 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_lingucomponent.hxx"
33 #include <com/sun/star/uno/Reference.h>
34 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
36 #include <com/sun/star/linguistic2/SpellFailure.hpp>
37 #include <cppuhelper/factory.hxx> // helper for factories
38 #include <com/sun/star/registry/XRegistryKey.hpp>
39 #include <tools/debug.hxx>
40 #include <unotools/processfactory.hxx>
41 #include <osl/mutex.hxx>
43 #include <hunspell.hxx>
44 #include <dictmgr.hxx>
46 #ifndef _SPELLIMP_HXX
47 #include <sspellimp.hxx>
48 #endif
50 #include <linguistic/lngprops.hxx>
51 #include <linguistic/spelldta.hxx>
52 #include <i18npool/mslangid.hxx>
53 #include <svtools/pathoptions.hxx>
54 #include <svtools/lingucfg.hxx>
55 #include <svtools/useroptions.hxx>
56 #include <osl/file.hxx>
57 #include <rtl/ustrbuf.hxx>
59 #include <lingutil.hxx>
61 #include <list>
62 #include <set>
65 using namespace utl;
66 using namespace osl;
67 using namespace rtl;
68 using namespace com::sun::star;
69 using namespace com::sun::star::beans;
70 using namespace com::sun::star::lang;
71 using namespace com::sun::star::uno;
72 using namespace com::sun::star::linguistic2;
73 using namespace linguistic;
75 // XML-header of SPELLML queries
76 #define SPELLML_HEADER "<?xml?>"
78 ///////////////////////////////////////////////////////////////////////////
80 SpellChecker::SpellChecker() :
81 aEvtListeners ( GetLinguMutex() )
83 aDicts = NULL;
84 aDEncs = NULL;
85 aDLocs = NULL;
86 aDNames = NULL;
87 bDisposing = FALSE;
88 pPropHelper = NULL;
89 numdict = 0;
93 SpellChecker::~SpellChecker()
95 if (aDicts) {
96 for (int i = 0; i < numdict; i++) {
97 if (aDicts[i]) delete aDicts[i];
98 aDicts[i] = NULL;
100 delete[] aDicts;
102 aDicts = NULL;
103 numdict = 0;
104 if (aDEncs) delete[] aDEncs;
105 aDEncs = NULL;
106 if (aDLocs) delete[] aDLocs;
107 aDLocs = NULL;
108 if (aDNames) delete[] aDNames;
109 aDNames = NULL;
110 if (pPropHelper)
111 pPropHelper->RemoveAsPropListener();
115 PropertyHelper_Spell & SpellChecker::GetPropHelper_Impl()
117 if (!pPropHelper)
119 Reference< XPropertySet > xPropSet( GetLinguProperties(), UNO_QUERY );
121 pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
122 xPropHelper = pPropHelper;
123 pPropHelper->AddAsPropListener(); //! after a reference is established
125 return *pPropHelper;
129 Sequence< Locale > SAL_CALL SpellChecker::getLocales()
130 throw(RuntimeException)
132 MutexGuard aGuard( GetLinguMutex() );
134 // this routine should return the locales supported by the installed
135 // dictionaries.
137 if (!numdict)
139 SvtLinguConfig aLinguCfg;
141 // get list of extension dictionaries-to-use
142 // (or better speaking: the list of dictionaries using the
143 // new configuration entries).
144 std::list< SvtLinguConfigDictionaryEntry > aDics;
145 uno::Sequence< rtl::OUString > aFormatList;
146 aLinguCfg.GetSupportedDictionaryFormatsFor( A2OU("SpellCheckers"),
147 A2OU("org.openoffice.lingu.MySpellSpellChecker"), aFormatList );
148 sal_Int32 nLen = aFormatList.getLength();
149 for (sal_Int32 i = 0; i < nLen; ++i)
151 std::vector< SvtLinguConfigDictionaryEntry > aTmpDic(
152 aLinguCfg.GetActiveDictionariesByFormat( aFormatList[i] ) );
153 aDics.insert( aDics.end(), aTmpDic.begin(), aTmpDic.end() );
156 //!! for compatibility with old dictionaries (the ones not using extensions
157 //!! or new configuration entries, but still using the dictionary.lst file)
158 //!! Get the list of old style spell checking dictionaries to use...
159 std::vector< SvtLinguConfigDictionaryEntry > aOldStyleDics(
160 GetOldStyleDics( "DICT" ) );
162 // to prefer dictionaries with configuration entries we will only
163 // use those old style dictionaries that add a language that
164 // is not yet supported by the list od new style dictionaries
165 MergeNewStyleDicsAndOldStyleDics( aDics, aOldStyleDics );
167 numdict = aDics.size();
168 if (numdict)
170 // get supported locales from the dictionaries-to-use...
171 sal_Int32 k = 0;
172 std::set< rtl::OUString, lt_rtl_OUString > aLocaleNamesSet;
173 std::list< SvtLinguConfigDictionaryEntry >::const_iterator aDictIt;
174 for (aDictIt = aDics.begin(); aDictIt != aDics.end(); ++aDictIt)
176 uno::Sequence< rtl::OUString > aLocaleNames( aDictIt->aLocaleNames );
177 sal_Int32 nLen2 = aLocaleNames.getLength();
178 for (k = 0; k < nLen2; ++k)
180 aLocaleNamesSet.insert( aLocaleNames[k] );
183 // ... and add them to the resulting sequence
184 aSuppLocales.realloc( aLocaleNamesSet.size() );
185 std::set< rtl::OUString, lt_rtl_OUString >::const_iterator aItB;
186 k = 0;
187 for (aItB = aLocaleNamesSet.begin(); aItB != aLocaleNamesSet.end(); ++aItB)
189 Locale aTmp( MsLangId::convertLanguageToLocale(
190 MsLangId::convertIsoStringToLanguage( *aItB )));
191 aSuppLocales[k++] = aTmp;
194 //! For each dictionary and each locale we need a seperate entry.
195 //! If this results in more than one dictionary per locale than (for now)
196 //! it is undefined which dictionary gets used.
197 //! In the future the implementation should support using several dictionaries
198 //! for one locale.
199 numdict = 0;
200 for (aDictIt = aDics.begin(); aDictIt != aDics.end(); ++aDictIt)
201 numdict = numdict + aDictIt->aLocaleNames.getLength();
203 // add dictionary information
204 aDicts = new Hunspell* [numdict];
205 aDEncs = new rtl_TextEncoding [numdict];
206 aDLocs = new Locale [numdict];
207 aDNames = new OUString [numdict];
208 k = 0;
209 for (aDictIt = aDics.begin(); aDictIt != aDics.end(); ++aDictIt)
211 if (aDictIt->aLocaleNames.getLength() > 0 &&
212 aDictIt->aLocations.getLength() > 0)
214 uno::Sequence< rtl::OUString > aLocaleNames( aDictIt->aLocaleNames );
215 sal_Int32 nLocales = aLocaleNames.getLength();
217 // currently only one language per dictionary is supported in the actual implementation...
218 // Thus here we work-around this by adding the same dictionary several times.
219 // Once for each of it's supported locales.
220 for (sal_Int32 i = 0; i < nLocales; ++i)
222 aDicts[k] = NULL;
223 aDEncs[k] = 0;
224 aDLocs[k] = MsLangId::convertLanguageToLocale(
225 MsLangId::convertIsoStringToLanguage( aLocaleNames[i] ));
226 // also both files have to be in the same directory and the
227 // file names must only differ in the extension (.aff/.dic).
228 // Thus we use the first location only and strip the extension part.
229 rtl::OUString aLocation = aDictIt->aLocations[0];
230 sal_Int32 nPos = aLocation.lastIndexOf( '.' );
231 aLocation = aLocation.copy( 0, nPos );
232 aDNames[k] = aLocation;
234 ++k;
238 DBG_ASSERT( k == numdict, "index mismatch?" );
240 else
242 /* no dictionary found so register no dictionaries */
243 numdict = 0;
244 aDicts = NULL;
245 aDEncs = NULL;
246 aDLocs = NULL;
247 aDNames = NULL;
248 aSuppLocales.realloc(0);
252 return aSuppLocales;
256 sal_Bool SAL_CALL SpellChecker::hasLocale(const Locale& rLocale)
257 throw(RuntimeException)
259 MutexGuard aGuard( GetLinguMutex() );
261 BOOL bRes = FALSE;
262 if (!aSuppLocales.getLength())
263 getLocales();
265 INT32 nLen = aSuppLocales.getLength();
266 for (INT32 i = 0; i < nLen; ++i)
268 const Locale *pLocale = aSuppLocales.getConstArray();
269 if (rLocale == pLocale[i])
271 bRes = TRUE;
272 break;
275 return bRes;
278 INT16 SpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
280 Hunspell * pMS;
281 rtl_TextEncoding aEnc;
283 // initialize a myspell object for each dictionary once
284 // (note: mutex is held higher up in isValid)
287 INT16 nRes = -1;
289 // first handle smart quotes both single and double
290 OUStringBuffer rBuf(rWord);
291 sal_Int32 n = rBuf.getLength();
292 sal_Unicode c;
293 for (sal_Int32 ix=0; ix < n; ix++) {
294 c = rBuf.charAt(ix);
295 if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
296 if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
298 OUString nWord(rBuf.makeStringAndClear());
300 if (n)
302 for (sal_Int32 i = 0; i < numdict; ++i) {
303 pMS = NULL;
304 aEnc = 0;
306 if (rLocale == aDLocs[i])
308 if (!aDicts[i])
310 OUString dicpath = aDNames[i] + A2OU(".dic");
311 OUString affpath = aDNames[i] + A2OU(".aff");
312 OUString dict;
313 OUString aff;
314 osl::FileBase::getSystemPathFromFileURL(dicpath,dict);
315 osl::FileBase::getSystemPathFromFileURL(affpath,aff);
316 OString aTmpaff(OU2ENC(aff,osl_getThreadTextEncoding()));
317 OString aTmpdict(OU2ENC(dict,osl_getThreadTextEncoding()));
319 #if defined(WNT)
320 // workaround for Windows specifc problem that the
321 // path length in calls to 'fopen' is limted to somewhat
322 // about 120+ characters which will usually be exceed when
323 // using dictionaries as extensions.
324 aTmpaff = Win_GetShortPathName( aff );
325 aTmpdict = Win_GetShortPathName( dict );
326 #endif
328 aDicts[i] = new Hunspell(aTmpaff.getStr(),aTmpdict.getStr());
329 aDEncs[i] = 0;
330 if (aDicts[i]) {
331 char * dic_encoding = aDicts[i]->get_dic_encoding();
332 aDEncs[i] = rtl_getTextEncodingFromUnixCharset(aDicts[i]->get_dic_encoding());
333 if (aDEncs[i] == RTL_TEXTENCODING_DONTKNOW) {
334 if (strcmp("ISCII-DEVANAGARI", dic_encoding) == 0) {
335 aDEncs[i] = RTL_TEXTENCODING_ISCII_DEVANAGARI;
336 } else if (strcmp("UTF-8", dic_encoding) == 0) {
337 aDEncs[i] = RTL_TEXTENCODING_UTF8;
342 pMS = aDicts[i];
343 aEnc = aDEncs[i];
345 if (pMS)
347 OString aWrd(OU2ENC(nWord,aEnc));
348 int rVal = pMS->spell((char*)aWrd.getStr());
349 if (rVal != 1)
351 nRes = SpellFailure::SPELLING_ERROR;
352 } else {
353 return -1;
355 pMS = NULL;
360 return nRes;
364 sal_Bool SAL_CALL
365 SpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
366 const PropertyValues& rProperties )
367 throw(IllegalArgumentException, RuntimeException)
369 MutexGuard aGuard( GetLinguMutex() );
371 if (rLocale == Locale() || !rWord.getLength())
372 return TRUE;
374 if (!hasLocale( rLocale ))
375 #ifdef LINGU_EXCEPTIONS
376 throw( IllegalArgumentException() );
377 #else
378 return TRUE;
379 #endif
381 // Get property values to be used.
382 // These are be the default values set in the SN_LINGU_PROPERTIES
383 // PropertySet which are overridden by the supplied ones from the
384 // last argument.
385 // You'll probably like to use a simplier solution than the provided
386 // one using the PropertyHelper_Spell.
388 PropertyHelper_Spell &rHelper = GetPropHelper();
389 rHelper.SetTmpPropVals( rProperties );
391 INT16 nFailure = GetSpellFailure( rWord, rLocale );
392 if (nFailure != -1 && !rWord.match(A2OU(SPELLML_HEADER), 0))
394 INT16 nLang = LocaleToLanguage( rLocale );
395 // postprocess result for errors that should be ignored
396 if ( (!rHelper.IsSpellUpperCase() && IsUpper( rWord, nLang ))
397 || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
398 || (!rHelper.IsSpellCapitalization()
399 && nFailure == SpellFailure::CAPTION_ERROR)
401 nFailure = -1;
404 return (nFailure == -1);
408 Reference< XSpellAlternatives >
409 SpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
411 // Retrieves the return values for the 'spell' function call in case
412 // of a misspelled word.
413 // Especially it may give a list of suggested (correct) words:
415 Reference< XSpellAlternatives > xRes;
416 // note: mutex is held by higher up by spell which covers both
418 Hunspell* pMS;
419 rtl_TextEncoding aEnc;
420 int count;
421 int numsug = 0;
423 // first handle smart quotes (single and double)
424 OUStringBuffer rBuf(rWord);
425 sal_Int32 n = rBuf.getLength();
426 sal_Unicode c;
427 for (sal_Int32 ix=0; ix < n; ix++) {
428 c = rBuf.charAt(ix);
429 if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
430 if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
432 OUString nWord(rBuf.makeStringAndClear());
434 if (n)
436 INT16 nLang = LocaleToLanguage( rLocale );
438 Sequence< OUString > aStr( 0 );
440 for (int i =0; i < numdict; i++) {
441 pMS = NULL;
442 aEnc = 0;
443 count = 0;
445 if (rLocale == aDLocs[i])
447 pMS = aDicts[i];
448 aEnc = aDEncs[i];
451 if (pMS)
453 char ** suglst = NULL;
454 OString aWrd(OU2ENC(nWord,aEnc));
455 count = pMS->suggest(&suglst, (const char *) aWrd.getStr());
457 if (count) {
459 aStr.realloc( numsug + count );
460 OUString *pStr = aStr.getArray();
461 for (int ii=0; ii < count; ii++)
463 // if needed add: if (suglst[ii] == NULL) continue;
464 OUString cvtwrd(suglst[ii],strlen(suglst[ii]),aEnc);
465 pStr[numsug + ii] = cvtwrd;
466 free(suglst[ii]);
468 free(suglst);
469 numsug += count;
474 // now return an empty alternative for no suggestions or the list of alternatives if some found
475 SpellAlternatives *pAlt = new SpellAlternatives;
476 String aTmp(rWord);
477 pAlt->SetWordLanguage( aTmp, nLang );
478 pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
479 pAlt->SetAlternatives( aStr );
480 xRes = pAlt;
481 return xRes;
484 return xRes;
490 Reference< XSpellAlternatives > SAL_CALL
491 SpellChecker::spell( const OUString& rWord, const Locale& rLocale,
492 const PropertyValues& rProperties )
493 throw(IllegalArgumentException, RuntimeException)
495 MutexGuard aGuard( GetLinguMutex() );
497 if (rLocale == Locale() || !rWord.getLength())
498 return NULL;
500 if (!hasLocale( rLocale ))
501 #ifdef LINGU_EXCEPTIONS
502 throw( IllegalArgumentException() );
503 #else
504 return NULL;
505 #endif
507 Reference< XSpellAlternatives > xAlt;
508 if (!isValid( rWord, rLocale, rProperties ))
510 xAlt = GetProposals( rWord, rLocale );
512 return xAlt;
516 Reference< XInterface > SAL_CALL SpellChecker_CreateInstance(
517 const Reference< XMultiServiceFactory > & /*rSMgr*/ )
518 throw(Exception)
521 Reference< XInterface > xService = (cppu::OWeakObject*) new SpellChecker;
522 return xService;
526 sal_Bool SAL_CALL
527 SpellChecker::addLinguServiceEventListener(
528 const Reference< XLinguServiceEventListener >& rxLstnr )
529 throw(RuntimeException)
531 MutexGuard aGuard( GetLinguMutex() );
533 BOOL bRes = FALSE;
534 if (!bDisposing && rxLstnr.is())
536 bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
538 return bRes;
542 sal_Bool SAL_CALL
543 SpellChecker::removeLinguServiceEventListener(
544 const Reference< XLinguServiceEventListener >& rxLstnr )
545 throw(RuntimeException)
547 MutexGuard aGuard( GetLinguMutex() );
549 BOOL bRes = FALSE;
550 if (!bDisposing && rxLstnr.is())
552 DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
553 bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
555 return bRes;
559 OUString SAL_CALL
560 SpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
561 throw(RuntimeException)
563 MutexGuard aGuard( GetLinguMutex() );
564 return A2OU( "Hunspell SpellChecker" );
568 void SAL_CALL
569 SpellChecker::initialize( const Sequence< Any >& rArguments )
570 throw(Exception, RuntimeException)
572 MutexGuard aGuard( GetLinguMutex() );
574 if (!pPropHelper)
576 INT32 nLen = rArguments.getLength();
577 if (2 == nLen)
579 Reference< XPropertySet > xPropSet;
580 rArguments.getConstArray()[0] >>= xPropSet;
581 //rArguments.getConstArray()[1] >>= xDicList;
583 //! Pointer allows for access of the non-UNO functions.
584 //! And the reference to the UNO-functions while increasing
585 //! the ref-count and will implicitly free the memory
586 //! when the object is not longer used.
587 pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
588 xPropHelper = pPropHelper;
589 pPropHelper->AddAsPropListener(); //! after a reference is established
591 else {
592 DBG_ERROR( "wrong number of arguments in sequence" );
599 void SAL_CALL
600 SpellChecker::dispose()
601 throw(RuntimeException)
603 MutexGuard aGuard( GetLinguMutex() );
605 if (!bDisposing)
607 bDisposing = TRUE;
608 EventObject aEvtObj( (XSpellChecker *) this );
609 aEvtListeners.disposeAndClear( aEvtObj );
614 void SAL_CALL
615 SpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
616 throw(RuntimeException)
618 MutexGuard aGuard( GetLinguMutex() );
620 if (!bDisposing && rxListener.is())
621 aEvtListeners.addInterface( rxListener );
625 void SAL_CALL
626 SpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
627 throw(RuntimeException)
629 MutexGuard aGuard( GetLinguMutex() );
631 if (!bDisposing && rxListener.is())
632 aEvtListeners.removeInterface( rxListener );
636 ///////////////////////////////////////////////////////////////////////////
637 // Service specific part
640 OUString SAL_CALL SpellChecker::getImplementationName()
641 throw(RuntimeException)
643 MutexGuard aGuard( GetLinguMutex() );
645 return getImplementationName_Static();
649 sal_Bool SAL_CALL SpellChecker::supportsService( const OUString& ServiceName )
650 throw(RuntimeException)
652 MutexGuard aGuard( GetLinguMutex() );
654 Sequence< OUString > aSNL = getSupportedServiceNames();
655 const OUString * pArray = aSNL.getConstArray();
656 for( INT32 i = 0; i < aSNL.getLength(); i++ )
657 if( pArray[i] == ServiceName )
658 return TRUE;
659 return FALSE;
663 Sequence< OUString > SAL_CALL SpellChecker::getSupportedServiceNames()
664 throw(RuntimeException)
666 MutexGuard aGuard( GetLinguMutex() );
668 return getSupportedServiceNames_Static();
672 Sequence< OUString > SpellChecker::getSupportedServiceNames_Static()
673 throw()
675 MutexGuard aGuard( GetLinguMutex() );
677 Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
678 aSNS.getArray()[0] = A2OU( SN_SPELLCHECKER );
679 return aSNS;
683 sal_Bool SAL_CALL SpellChecker_writeInfo(
684 void * /*pServiceManager*/, registry::XRegistryKey * pRegistryKey )
689 String aImpl( '/' );
690 aImpl += SpellChecker::getImplementationName_Static().getStr();
691 aImpl.AppendAscii( "/UNO/SERVICES" );
692 Reference< registry::XRegistryKey > xNewKey =
693 pRegistryKey->createKey( aImpl );
694 Sequence< OUString > aServices =
695 SpellChecker::getSupportedServiceNames_Static();
696 for( INT32 i = 0; i < aServices.getLength(); i++ )
697 xNewKey->createKey( aServices.getConstArray()[i] );
699 return sal_True;
701 catch(Exception &)
703 return sal_False;
708 void * SAL_CALL SpellChecker_getFactory( const sal_Char * pImplName,
709 XMultiServiceFactory * pServiceManager, void * )
711 void * pRet = 0;
712 if ( !SpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
714 Reference< XSingleServiceFactory > xFactory =
715 cppu::createOneInstanceFactory(
716 pServiceManager,
717 SpellChecker::getImplementationName_Static(),
718 SpellChecker_CreateInstance,
719 SpellChecker::getSupportedServiceNames_Static());
720 // acquire, because we return an interface pointer instead of a reference
721 xFactory->acquire();
722 pRet = xFactory.get();
724 return pRet;
728 ///////////////////////////////////////////////////////////////////////////