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