1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: sspellimp.cxx,v $
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>
47 #include <sspellimp.hxx>
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>
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() )
93 SpellChecker::~SpellChecker()
96 for (int i
= 0; i
< numdict
; i
++) {
97 if (aDicts
[i
]) delete aDicts
[i
];
104 if (aDEncs
) delete[] aDEncs
;
106 if (aDLocs
) delete[] aDLocs
;
108 if (aDNames
) delete[] aDNames
;
111 pPropHelper
->RemoveAsPropListener();
115 PropertyHelper_Spell
& SpellChecker::GetPropHelper_Impl()
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
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
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();
170 // get supported locales from the dictionaries-to-use...
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
;
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
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
];
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
)
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
;
238 DBG_ASSERT( k
== numdict
, "index mismatch?" );
242 /* no dictionary found so register no dictionaries */
248 aSuppLocales
.realloc(0);
256 sal_Bool SAL_CALL
SpellChecker::hasLocale(const Locale
& rLocale
)
257 throw(RuntimeException
)
259 MutexGuard
aGuard( GetLinguMutex() );
262 if (!aSuppLocales
.getLength())
265 INT32 nLen
= aSuppLocales
.getLength();
266 for (INT32 i
= 0; i
< nLen
; ++i
)
268 const Locale
*pLocale
= aSuppLocales
.getConstArray();
269 if (rLocale
== pLocale
[i
])
278 INT16
SpellChecker::GetSpellFailure( const OUString
&rWord
, const Locale
&rLocale
)
281 rtl_TextEncoding aEnc
;
283 // initialize a myspell object for each dictionary once
284 // (note: mutex is held higher up in isValid)
289 // first handle smart quotes both single and double
290 OUStringBuffer
rBuf(rWord
);
291 sal_Int32 n
= rBuf
.getLength();
293 for (sal_Int32 ix
=0; ix
< n
; 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());
302 for (sal_Int32 i
= 0; i
< numdict
; ++i
) {
306 if (rLocale
== aDLocs
[i
])
310 OUString dicpath
= aDNames
[i
] + A2OU(".dic");
311 OUString affpath
= aDNames
[i
] + A2OU(".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()));
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
);
328 aDicts
[i
] = new Hunspell(aTmpaff
.getStr(),aTmpdict
.getStr());
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
;
347 OString
aWrd(OU2ENC(nWord
,aEnc
));
348 int rVal
= pMS
->spell((char*)aWrd
.getStr());
351 nRes
= SpellFailure::SPELLING_ERROR
;
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())
374 if (!hasLocale( rLocale
))
375 #ifdef LINGU_EXCEPTIONS
376 throw( IllegalArgumentException() );
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
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
)
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
419 rtl_TextEncoding aEnc
;
423 // first handle smart quotes (single and double)
424 OUStringBuffer
rBuf(rWord
);
425 sal_Int32 n
= rBuf
.getLength();
427 for (sal_Int32 ix
=0; ix
< n
; 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());
436 INT16 nLang
= LocaleToLanguage( rLocale
);
438 Sequence
< OUString
> aStr( 0 );
440 for (int i
=0; i
< numdict
; i
++) {
445 if (rLocale
== aDLocs
[i
])
453 char ** suglst
= NULL
;
454 OString
aWrd(OU2ENC(nWord
,aEnc
));
455 count
= pMS
->suggest(&suglst
, (const char *) aWrd
.getStr());
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
;
474 // now return an empty alternative for no suggestions or the list of alternatives if some found
475 SpellAlternatives
*pAlt
= new SpellAlternatives
;
477 pAlt
->SetWordLanguage( aTmp
, nLang
);
478 pAlt
->SetFailureType( SpellFailure::SPELLING_ERROR
);
479 pAlt
->SetAlternatives( aStr
);
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())
500 if (!hasLocale( rLocale
))
501 #ifdef LINGU_EXCEPTIONS
502 throw( IllegalArgumentException() );
507 Reference
< XSpellAlternatives
> xAlt
;
508 if (!isValid( rWord
, rLocale
, rProperties
))
510 xAlt
= GetProposals( rWord
, rLocale
);
516 Reference
< XInterface
> SAL_CALL
SpellChecker_CreateInstance(
517 const Reference
< XMultiServiceFactory
> & /*rSMgr*/ )
521 Reference
< XInterface
> xService
= (cppu::OWeakObject
*) new SpellChecker
;
527 SpellChecker::addLinguServiceEventListener(
528 const Reference
< XLinguServiceEventListener
>& rxLstnr
)
529 throw(RuntimeException
)
531 MutexGuard
aGuard( GetLinguMutex() );
534 if (!bDisposing
&& rxLstnr
.is())
536 bRes
= GetPropHelper().addLinguServiceEventListener( rxLstnr
);
543 SpellChecker::removeLinguServiceEventListener(
544 const Reference
< XLinguServiceEventListener
>& rxLstnr
)
545 throw(RuntimeException
)
547 MutexGuard
aGuard( GetLinguMutex() );
550 if (!bDisposing
&& rxLstnr
.is())
552 DBG_ASSERT( xPropHelper
.is(), "xPropHelper non existent" );
553 bRes
= GetPropHelper().removeLinguServiceEventListener( rxLstnr
);
560 SpellChecker::getServiceDisplayName( const Locale
& /*rLocale*/ )
561 throw(RuntimeException
)
563 MutexGuard
aGuard( GetLinguMutex() );
564 return A2OU( "Hunspell SpellChecker" );
569 SpellChecker::initialize( const Sequence
< Any
>& rArguments
)
570 throw(Exception
, RuntimeException
)
572 MutexGuard
aGuard( GetLinguMutex() );
576 INT32 nLen
= rArguments
.getLength();
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
592 DBG_ERROR( "wrong number of arguments in sequence" );
600 SpellChecker::dispose()
601 throw(RuntimeException
)
603 MutexGuard
aGuard( GetLinguMutex() );
608 EventObject
aEvtObj( (XSpellChecker
*) this );
609 aEvtListeners
.disposeAndClear( aEvtObj
);
615 SpellChecker::addEventListener( const Reference
< XEventListener
>& rxListener
)
616 throw(RuntimeException
)
618 MutexGuard
aGuard( GetLinguMutex() );
620 if (!bDisposing
&& rxListener
.is())
621 aEvtListeners
.addInterface( rxListener
);
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
)
663 Sequence
< OUString
> SAL_CALL
SpellChecker::getSupportedServiceNames()
664 throw(RuntimeException
)
666 MutexGuard
aGuard( GetLinguMutex() );
668 return getSupportedServiceNames_Static();
672 Sequence
< OUString
> SpellChecker::getSupportedServiceNames_Static()
675 MutexGuard
aGuard( GetLinguMutex() );
677 Sequence
< OUString
> aSNS( 1 ); // auch mehr als 1 Service moeglich
678 aSNS
.getArray()[0] = A2OU( SN_SPELLCHECKER
);
683 sal_Bool SAL_CALL
SpellChecker_writeInfo(
684 void * /*pServiceManager*/, registry::XRegistryKey
* pRegistryKey
)
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
] );
708 void * SAL_CALL
SpellChecker_getFactory( const sal_Char
* pImplName
,
709 XMultiServiceFactory
* pServiceManager
, void * )
712 if ( !SpellChecker::getImplementationName_Static().compareToAscii( pImplName
) )
714 Reference
< XSingleServiceFactory
> xFactory
=
715 cppu::createOneInstanceFactory(
717 SpellChecker::getImplementationName_Static(),
718 SpellChecker_CreateInstance
,
719 SpellChecker::getSupportedServiceNames_Static());
720 // acquire, because we return an interface pointer instead of a reference
722 pRet
= xFactory
.get();
728 ///////////////////////////////////////////////////////////////////////////