1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_linguistic.hxx"
30 #include <com/sun/star/uno/Reference.h>
31 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
32 
33 #include <com/sun/star/linguistic2/SpellFailure.hpp>
34 #include <cppuhelper/factory.hxx>	// helper for factories
35 #include <com/sun/star/registry/XRegistryKey.hpp>
36 #include <tools/debug.hxx>
37 #include <unotools/processfactory.hxx>
38 #include <osl/mutex.hxx>
39 
40 #ifndef _SPELLIMP_HXX
41 #include <sspellimp.hxx>
42 #endif
43 
44 #include "linguistic/lngprops.hxx"
45 #include "linguistic/spelldta.hxx"
46 
47 using namespace utl;
48 using namespace osl;
49 using namespace rtl;
50 using namespace com::sun::star;
51 using namespace com::sun::star::beans;
52 using namespace com::sun::star::lang;
53 using namespace com::sun::star::uno;
54 using namespace com::sun::star::linguistic2;
55 using namespace linguistic;
56 
57 
58 ///////////////////////////////////////////////////////////////////////////
59 
60 BOOL operator == ( const Locale &rL1, const Locale &rL2 )
61 {
62 	return	rL1.Language ==  rL2.Language	&&
63 			rL1.Country  ==  rL2.Country	&&
64 			rL1.Variant  ==  rL2.Variant;
65 }
66 
67 ///////////////////////////////////////////////////////////////////////////
68 
69 
70 SpellChecker::SpellChecker() :
71 	aEvtListeners	( GetLinguMutex() )
72 {
73 	bDisposing = FALSE;
74 	pPropHelper = NULL;
75 }
76 
77 
78 SpellChecker::~SpellChecker()
79 {
80 	if (pPropHelper)
81 		pPropHelper->RemoveAsPropListener();
82 }
83 
84 
85 PropertyHelper_Spell & SpellChecker::GetPropHelper_Impl()
86 {
87 	if (!pPropHelper)
88 	{
89 		Reference< XPropertySet	>	xPropSet( GetLinguProperties(), UNO_QUERY );
90 
91 		pPropHelper	= new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
92 		xPropHelper = pPropHelper;
93 		pPropHelper->AddAsPropListener();	//! after a reference is established
94 	}
95 	return *pPropHelper;
96 }
97 
98 
99 Sequence< Locale > SAL_CALL SpellChecker::getLocales()
100 		throw(RuntimeException)
101 {
102 	MutexGuard	aGuard( GetLinguMutex() );
103 
104 	if (!aSuppLocales.getLength())
105 	{
106 		aSuppLocales.realloc( 3 );
107 		Locale *pLocale = aSuppLocales.getArray();
108 		pLocale[0] = Locale( A2OU("en"), A2OU("US"), OUString() );
109 		pLocale[1] = Locale( A2OU("de"), A2OU("DE"), OUString() );
110 		pLocale[2] = Locale( A2OU("de"), A2OU("CH"), OUString() );
111 	}
112 
113 	return aSuppLocales;
114 }
115 
116 
117 sal_Bool SAL_CALL SpellChecker::hasLocale(const Locale& rLocale)
118 		throw(RuntimeException)
119 {
120 	MutexGuard	aGuard( GetLinguMutex() );
121 
122 	BOOL bRes = FALSE;
123 	if (!aSuppLocales.getLength())
124 		getLocales();
125 	INT32 nLen = aSuppLocales.getLength();
126 	for (INT32 i = 0;  i < nLen;  ++i)
127 	{
128 		const Locale *pLocale = aSuppLocales.getConstArray();
129 		if (rLocale == pLocale[i])
130 		{
131 			bRes = TRUE;
132 			break;
133 		}
134 	}
135 	return bRes;
136 }
137 
138 
139 INT16 SpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
140 {
141 	// Checks wether a word is OK in a given language (Locale) or not, and
142 	// provides a failure type for the incorrect ones.
143 	// - words with "liss" (case sensitiv) as substring will be negative.
144 	// - words with 'x' or 'X' will have incorrect spelling.
145 	// - words with 's' or 'S' as first letter will have the wrong caption.
146 	// - all other words will be OK.
147 
148 	INT16 nRes = -1;
149 
150 	String aTmp( rWord );
151 	if (aTmp.Len())
152 	{
153 		if (STRING_NOTFOUND != aTmp.SearchAscii( "liss" ))
154 		{
155 			nRes = SpellFailure::IS_NEGATIVE_WORD;
156 		}
157 		else if (STRING_NOTFOUND != aTmp.Search( (sal_Unicode) 'x' )  ||
158 				 STRING_NOTFOUND != aTmp.Search( (sal_Unicode) 'X' ))
159 		{
160 			nRes = SpellFailure::SPELLING_ERROR;
161 		}
162 		else
163 		{
164 			sal_Unicode cChar = aTmp.GetChar( 0 );
165 			if (cChar == (sal_Unicode) 's'  ||  cChar == (sal_Unicode) 'S')
166 				nRes = SpellFailure::CAPTION_ERROR;
167 		}
168 	}
169 
170 	return nRes;
171 }
172 
173 
174 sal_Bool SAL_CALL
175 	SpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
176 			const PropertyValues& rProperties )
177 		throw(IllegalArgumentException, RuntimeException)
178 {
179 	MutexGuard	aGuard( GetLinguMutex() );
180 
181  	if (rLocale == Locale()  ||  !rWord.getLength())
182 		return TRUE;
183 
184 	if (!hasLocale( rLocale ))
185 #ifdef LINGU_EXCEPTIONS
186 		throw( IllegalArgumentException() );
187 #else
188 		return TRUE;
189 #endif
190 
191 	// Get property values to be used.
192 	// These are be the default values set in the SN_LINGU_PROPERTIES
193 	// PropertySet which are overridden by the supplied ones from the
194 	// last argument.
195 	// You'll probably like to use a simplier solution than the provided
196 	// one using the PropertyHelper_Spell.
197 	PropertyHelper_Spell &rHelper = GetPropHelper();
198 	rHelper.SetTmpPropVals( rProperties );
199 
200 	INT16 nFailure = GetSpellFailure( rWord, rLocale );
201 	if (nFailure != -1)
202 	{
203 		INT16 nLang = LocaleToLanguage( rLocale );
204 		// postprocess result for errors that should be ignored
205 		if (   (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang ))
206 			|| (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
207 			|| (!rHelper.IsSpellCapitalization()
208 				&&  nFailure == SpellFailure::CAPTION_ERROR)
209 		)
210 			nFailure = -1;
211 	}
212 	return nFailure == -1;
213 }
214 
215 
216 Reference< XSpellAlternatives >
217 	SpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
218 {
219 	// Retrieves the return values for the 'spell' function call in case
220 	// of a misspelled word.
221 	// Especially it may give a list of suggested (correct) words:
222 	// - a "liss" substring will be replaced by "liz".
223 	// - 'x' or 'X' will be replaced by 'u' or 'U' for the first proposal
224 	//   and they will be removed from the word for the second proposal.
225 	// - 's' or 'S' as first letter will be changed to the other caption.
226 
227 	Reference< XSpellAlternatives > xRes;
228 
229 	String aTmp( rWord );
230 	if (aTmp.Len())
231 	{
232 		INT16 nLang = LocaleToLanguage( rLocale );
233 
234 		if (STRING_NOTFOUND != aTmp.SearchAscii( "liss" ))
235 		{
236 			aTmp.SearchAndReplaceAllAscii( "liss", A2OU("liz") );
237 			xRes = new SpellAlternatives( aTmp, nLang,
238 						SpellFailure::IS_NEGATIVE_WORD, aTmp );
239 		}
240 		else if (STRING_NOTFOUND != aTmp.Search( (sal_Unicode) 'x' )  ||
241 				 STRING_NOTFOUND != aTmp.Search( (sal_Unicode) 'X' ))
242 		{
243 			Sequence< OUString > aStr( 2 );
244 			OUString *pStr = aStr.getArray();
245 			String  aAlt1( aTmp ),
246 					aAlt2( aTmp );
247 			aAlt1.SearchAndReplaceAll( (sal_Unicode) 'x', (sal_Unicode) 'u');
248 			aAlt1.SearchAndReplaceAll( (sal_Unicode) 'X', (sal_Unicode) 'U');
249 			aAlt2.EraseAllChars( (sal_Unicode) 'x' );
250 			aAlt2.EraseAllChars( (sal_Unicode) 'X' );
251 			pStr[0] = aAlt1;
252 			pStr[1] = aAlt2;
253 
254 			SpellAlternatives *pAlt = new SpellAlternatives;
255 			pAlt->SetWordLanguage( aTmp, nLang );
256 			pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
257 			pAlt->SetAlternatives( aStr );
258 
259 			xRes = pAlt;
260 		}
261 		else
262 		{
263 			sal_Unicode cChar = aTmp.GetChar( 0 );
264 			if (cChar == (sal_Unicode) 's'  ||  cChar == (sal_Unicode) 'S')
265 			{
266 				sal_Unicode cNewChar = cChar == (sal_Unicode) 's' ?
267 						(sal_Unicode) 'S': (sal_Unicode) 's';
268 				aTmp.GetBufferAccess()[0] = cNewChar;
269 				xRes = new SpellAlternatives( aTmp, nLang,
270 						SpellFailure::CAPTION_ERROR, aTmp );
271 			}
272 		}
273 	}
274 
275 	return xRes;
276 }
277 
278 
279 Reference< XSpellAlternatives > SAL_CALL
280 	SpellChecker::spell( const OUString& rWord, const Locale& rLocale,
281 			const PropertyValues& rProperties )
282 		throw(IllegalArgumentException, RuntimeException)
283 {
284 	MutexGuard	aGuard( GetLinguMutex() );
285 
286  	if (rLocale == Locale()  ||  !rWord.getLength())
287 		return NULL;
288 
289 	if (!hasLocale( rLocale ))
290 #ifdef LINGU_EXCEPTIONS
291 		throw( IllegalArgumentException() );
292 #else
293 		return NULL;
294 #endif
295 
296 	Reference< XSpellAlternatives > xAlt;
297 	if (!isValid( rWord, rLocale, rProperties ))
298 	{
299 		xAlt =  GetProposals( rWord, rLocale );
300 	}
301 	return xAlt;
302 }
303 
304 
305 Reference< XInterface > SAL_CALL SpellChecker_CreateInstance(
306 			const Reference< XMultiServiceFactory > & rSMgr )
307 		throw(Exception)
308 {
309 	Reference< XInterface > xService = (cppu::OWeakObject*) new SpellChecker;
310 	return xService;
311 }
312 
313 
314 sal_Bool SAL_CALL
315 	SpellChecker::addLinguServiceEventListener(
316 			const Reference< XLinguServiceEventListener >& rxLstnr )
317 		throw(RuntimeException)
318 {
319 	MutexGuard	aGuard( GetLinguMutex() );
320 
321 	BOOL bRes = FALSE;
322 	if (!bDisposing && rxLstnr.is())
323 	{
324 		bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
325 	}
326 	return bRes;
327 }
328 
329 
330 sal_Bool SAL_CALL
331 	SpellChecker::removeLinguServiceEventListener(
332 			const Reference< XLinguServiceEventListener >& rxLstnr )
333 		throw(RuntimeException)
334 {
335 	MutexGuard	aGuard( GetLinguMutex() );
336 
337 	BOOL bRes = FALSE;
338 	if (!bDisposing && rxLstnr.is())
339 	{
340 		DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
341 		bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
342 	}
343 	return bRes;
344 }
345 
346 
347 OUString SAL_CALL
348 	SpellChecker::getServiceDisplayName( const Locale& rLocale )
349 		throw(RuntimeException)
350 {
351 	MutexGuard	aGuard( GetLinguMutex() );
352 	return A2OU( "OpenOffice example spellchecker" );
353 }
354 
355 
356 void SAL_CALL
357 	SpellChecker::initialize( const Sequence< Any >& rArguments )
358 		throw(Exception, RuntimeException)
359 {
360 	MutexGuard	aGuard( GetLinguMutex() );
361 
362 	if (!pPropHelper)
363 	{
364 		INT32 nLen = rArguments.getLength();
365 		if (2 == nLen)
366 		{
367 			Reference< XPropertySet	>	xPropSet;
368 			rArguments.getConstArray()[0] >>= xPropSet;
369 			//rArguments.getConstArray()[1] >>= xDicList;
370 
371 			//! Pointer allows for access of the non-UNO functions.
372 			//! And the reference to the UNO-functions while increasing
373 			//! the ref-count and will implicitly free the memory
374 			//! when the object is not longer used.
375 			pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
376 			xPropHelper = pPropHelper;
377 			pPropHelper->AddAsPropListener();	//! after a reference is established
378 		}
379 		else
380 			DBG_ERROR( "wrong number of arguments in sequence" );
381 	}
382 }
383 
384 
385 void SAL_CALL
386 	SpellChecker::dispose()
387 		throw(RuntimeException)
388 {
389 	MutexGuard	aGuard( GetLinguMutex() );
390 
391 	if (!bDisposing)
392 	{
393 		bDisposing = TRUE;
394 		EventObject	aEvtObj( (XSpellChecker *) this );
395 		aEvtListeners.disposeAndClear( aEvtObj );
396 	}
397 }
398 
399 
400 void SAL_CALL
401 	SpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
402 		throw(RuntimeException)
403 {
404 	MutexGuard	aGuard( GetLinguMutex() );
405 
406 	if (!bDisposing && rxListener.is())
407 		aEvtListeners.addInterface( rxListener );
408 }
409 
410 
411 void SAL_CALL
412 	SpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
413 		throw(RuntimeException)
414 {
415 	MutexGuard	aGuard( GetLinguMutex() );
416 
417 	if (!bDisposing && rxListener.is())
418 		aEvtListeners.removeInterface( rxListener );
419 }
420 
421 
422 ///////////////////////////////////////////////////////////////////////////
423 // Service specific part
424 //
425 
426 OUString SAL_CALL SpellChecker::getImplementationName()
427 		throw(RuntimeException)
428 {
429 	MutexGuard	aGuard( GetLinguMutex() );
430 	return getImplementationName_Static();
431 }
432 
433 
434 sal_Bool SAL_CALL SpellChecker::supportsService( const OUString& ServiceName )
435 		throw(RuntimeException)
436 {
437 	MutexGuard	aGuard( GetLinguMutex() );
438 
439 	Sequence< OUString > aSNL = getSupportedServiceNames();
440 	const OUString * pArray = aSNL.getConstArray();
441 	for( INT32 i = 0; i < aSNL.getLength(); i++ )
442 		if( pArray[i] == ServiceName )
443 			return TRUE;
444 	return FALSE;
445 }
446 
447 
448 Sequence< OUString > SAL_CALL SpellChecker::getSupportedServiceNames()
449 		throw(RuntimeException)
450 {
451 	MutexGuard	aGuard( GetLinguMutex() );
452 	return getSupportedServiceNames_Static();
453 }
454 
455 
456 Sequence< OUString > SpellChecker::getSupportedServiceNames_Static()
457 		throw()
458 {
459 	MutexGuard	aGuard( GetLinguMutex() );
460 
461 	Sequence< OUString > aSNS( 1 );	// auch mehr als 1 Service moeglich
462 	aSNS.getArray()[0] = A2OU( SN_SPELLCHECKER );
463 	return aSNS;
464 }
465 
466 
467 sal_Bool SAL_CALL SpellChecker_writeInfo(
468 			void * /*pServiceManager*/, registry::XRegistryKey * pRegistryKey )
469 {
470 	try
471 	{
472 		String aImpl( '/' );
473 		aImpl += SpellChecker::getImplementationName_Static().getStr();
474 		aImpl.AppendAscii( "/UNO/SERVICES" );
475 		Reference< registry::XRegistryKey > xNewKey =
476 				pRegistryKey->createKey( aImpl );
477 		Sequence< OUString > aServices =
478 				SpellChecker::getSupportedServiceNames_Static();
479 		for( INT32 i = 0; i < aServices.getLength(); i++ )
480 			xNewKey->createKey( aServices.getConstArray()[i] );
481 
482 		return sal_True;
483 	}
484 	catch(Exception &)
485 	{
486 		return sal_False;
487 	}
488 }
489 
490 
491 void * SAL_CALL SpellChecker_getFactory( const sal_Char * pImplName,
492 			XMultiServiceFactory * pServiceManager, void *  )
493 {
494 	void * pRet = 0;
495 	if ( !SpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
496 	{
497 		Reference< XSingleServiceFactory > xFactory =
498 			cppu::createOneInstanceFactory(
499 				pServiceManager,
500 				SpellChecker::getImplementationName_Static(),
501 				SpellChecker_CreateInstance,
502 				SpellChecker::getSupportedServiceNames_Static());
503 		// acquire, because we return an interface pointer instead of a reference
504 		xFactory->acquire();
505 		pRet = xFactory.get();
506 	}
507 	return pRet;
508 }
509 
510 
511 ///////////////////////////////////////////////////////////////////////////
512 
513