xref: /trunk/main/lingucomponent/source/spellcheck/macosxspell/macspellimp.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
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_lingucomponent.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 //#include <hunspell.hxx>
41 #include <dictmgr.hxx>
42 #include <macspellimp.hxx>
43 
44 //#include <linguistic/lngprops.hxx>
45 #include <linguistic/spelldta.hxx>
46 #include <unotools/pathoptions.hxx>
47 #include <unotools/useroptions.hxx>
48 #include <osl/file.hxx>
49 #include <rtl/ustrbuf.hxx>
50 
51 
52 using namespace utl;
53 using namespace osl;
54 using namespace rtl;
55 using namespace com::sun::star;
56 using namespace com::sun::star::beans;
57 using namespace com::sun::star::lang;
58 using namespace com::sun::star::uno;
59 using namespace com::sun::star::linguistic2;
60 using namespace linguistic;
61 ///////////////////////////////////////////////////////////////////////////
62 // dbg_dump for development
63 #if OSL_DEBUG_LEVEL > 1
64 #include <rtl/strbuf.hxx>
65 #include <rtl/ustring.hxx>
66 
67 const sal_Char *dbg_dump(const rtl::OString &rStr)
68 {
69     static rtl::OStringBuffer aStr;
70 
71     aStr = rtl::OStringBuffer(rStr);
72     aStr.append(static_cast<char>(0));
73     return aStr.getStr();
74 }
75 
76 const sal_Char *dbg_dump(const rtl::OUString &rStr)
77 {
78     return dbg_dump(rtl::OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
79 }
80 
81 const sal_Char *dbg_dump(rtl_String *pStr)
82 {
83     return dbg_dump(rtl::OString(pStr));
84 }
85 
86 const sal_Char *dbg_dump(rtl_uString *pStr)
87 {
88     return dbg_dump(rtl::OUString(pStr));
89 }
90 
91 #endif
92 ///////////////////////////////////////////////////////////////////////////
93 
94 MacSpellChecker::MacSpellChecker() :
95     aEvtListeners   ( GetLinguMutex() )
96 {
97 //    aDicts = NULL;
98     aDEncs = NULL;
99     aDLocs = NULL;
100     aDNames = NULL;
101     bDisposing = sal_False;
102     pPropHelper = NULL;
103     numdict = 0;
104     NSApplicationLoad();
105     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
106     macSpell = [NSSpellChecker sharedSpellChecker];
107     macTag = [NSSpellChecker uniqueSpellDocumentTag];
108     [pool release];
109 }
110 
111 
112 MacSpellChecker::~MacSpellChecker()
113 {
114   // if (aDicts) {
115   //    for (int i = 0; i < numdict; i++) {
116   //           if (aDicts[i]) delete aDicts[i];
117   //           aDicts[i] = NULL;
118   //    }
119   //    delete[] aDicts;
120   // }
121   // aDicts = NULL;
122   numdict = 0;
123   if (aDEncs) delete[] aDEncs;
124   aDEncs = NULL;
125   if (aDLocs) delete[] aDLocs;
126   aDLocs = NULL;
127   if (aDNames) delete[] aDNames;
128   aDNames = NULL;
129   if (pPropHelper)
130      pPropHelper->RemoveAsPropListener();
131 }
132 
133 
134 PropertyHelper_Spell & MacSpellChecker::GetPropHelper_Impl()
135 {
136     if (!pPropHelper)
137     {
138         Reference< XPropertySet >   xPropSet( GetLinguProperties(), UNO_QUERY );
139 
140         pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
141         xPropHelper = pPropHelper;
142         pPropHelper->AddAsPropListener();   //! after a reference is established
143     }
144     return *pPropHelper;
145 }
146 
147 
148 Sequence< Locale > SAL_CALL MacSpellChecker::getLocales()
149         throw(RuntimeException)
150 {
151     MutexGuard  aGuard( GetLinguMutex() );
152 
153         // this routine should return the locales supported by the installed
154         // dictionaries.  So here we need to parse both the user edited
155         // dictionary list and the shared dictionary list
156         // to see what dictionaries the admin/user has installed
157 
158         int numusr;          // number of user dictionary entries
159         int numshr;          // number of shared dictionary entries
160         dictentry * spdict;  // shared dict entry pointer
161         dictentry * updict;  // user dict entry pointer
162         SvtPathOptions aPathOpt;
163         rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
164 
165         std::vector<objc_object *> postspdict;
166         //std::vector<dictentry *> postspdict;
167         std::vector<dictentry *> postupdict;
168 
169 
170     if (!numdict) {
171 
172         // invoke a dictionary manager to get the user dictionary list
173         // TODO How on Mac OS X?
174 
175         // invoke a second  dictionary manager to get the shared dictionary list
176         NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
177 
178         //Test for existence of the dictionaries
179         for (unsigned int i = 0; i < [aLocales count]; i++)
180         {
181             if( [macSpell setLanguage:[aLocales objectAtIndex:i] ] )
182             {
183                 postspdict.push_back( [ aLocales objectAtIndex:i ] );
184             }
185         }
186 
187         numusr = postupdict.size();
188         numshr = postspdict.size();
189 
190         // we really should merge these and remove duplicates but since
191         // users can name their dictionaries anything they want it would
192         // be impossible to know if a real duplication exists unless we
193         // add some unique key to each myspell dictionary
194         numdict = numshr + numusr;
195 
196         if (numdict) {
197             aDLocs = new Locale [numdict];
198             aDEncs  = new rtl_TextEncoding [numdict];
199             aDNames = new OUString [numdict];
200             aSuppLocales.realloc(numdict);
201             Locale * pLocale = aSuppLocales.getArray();
202             int numlocs = 0;
203             int newloc;
204             int i,j;
205             int k = 0;
206 
207             //first add the user dictionaries
208             //TODO for MAC?
209 
210             // now add the shared dictionaries
211             for (i = 0; i < numshr; i++) {
212                 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
213                 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
214                 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
215                 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
216                 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
217                 Locale nLoc( lang, country, OUString() );
218                 newloc = 1;
219                 //eliminate duplicates (is this needed for MacOS?)
220                 for (j = 0; j < numlocs; j++) {
221                     if (nLoc == pLocale[j]) newloc = 0;
222                 }
223                 if (newloc) {
224                     pLocale[numlocs] = nLoc;
225                     numlocs++;
226                 }
227                 aDLocs[k] = nLoc;
228                 //pointer to Hunspell dictionary - not needed for MAC
229                 //aDicts[k] = NULL;
230                 aDEncs[k] = 0;
231                 // Dictionary file names not valid for Mac Spell
232                 //aDNames[k] = aPathOpt.GetLinguisticPath() + A2OU("/ooo/") + A2OU(postspdict[i]->filename);
233                 k++;
234             }
235 
236             aSuppLocales.realloc(numlocs);
237 
238         } else {
239             /* no dictionary.lst found so register no dictionaries */
240             numdict = 0;
241             //aDicts = NULL;
242                 aDEncs  = NULL;
243                 aDLocs = NULL;
244                 aDNames = NULL;
245                 aSuppLocales.realloc(0);
246             }
247 
248             /* de-allocation of memory is handled inside the DictMgr */
249             updict = NULL;
250             spdict = NULL;
251 
252         }
253 
254     return aSuppLocales;
255 }
256 
257 
258 
259 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
260         throw(RuntimeException)
261 {
262     MutexGuard  aGuard( GetLinguMutex() );
263 
264     sal_Bool bRes = sal_False;
265     if (!aSuppLocales.getLength())
266         getLocales();
267 
268     sal_Int32 nLen = aSuppLocales.getLength();
269     for (sal_Int32 i = 0;  i < nLen;  ++i)
270     {
271         const Locale *pLocale = aSuppLocales.getConstArray();
272         if (rLocale == pLocale[i])
273         {
274             bRes = sal_True;
275             break;
276         }
277     }
278     return bRes;
279 }
280 
281 
282 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
283 {
284     rtl_TextEncoding aEnc;
285 
286     // initialize a myspell object for each dictionary once
287         // (note: mutex is held higher up in isValid)
288 
289 
290     sal_Int16 nRes = -1;
291 
292         // first handle smart quotes both single and double
293     OUStringBuffer rBuf(rWord);
294         sal_Int32 n = rBuf.getLength();
295         sal_Unicode c;
296     for (sal_Int32 ix=0; ix < n; ix++) {
297         c = rBuf.charAt(ix);
298             if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
299             if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
300         }
301         OUString nWord(rBuf.makeStringAndClear());
302 
303     if (n)
304     {
305         aEnc = 0;
306         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
307         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
308         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
309         if(rLocale.Country.getLength()>0)
310         {
311             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
312             NSString* aTag = @"_";
313             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
314             [aLang autorelease];
315             aLang = [aLang  stringByAppendingString:aTaggedCountry];
316         }
317 
318         int aCount;
319         NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
320         int rVal = 0;
321         if(range.length>0)
322         {
323             rVal = -1;
324         }
325         else
326         {
327             rVal = 1;
328         }
329         [pool release];
330         if (rVal != 1)
331         {
332             nRes = SpellFailure::SPELLING_ERROR;
333         } else {
334             return -1;
335         }
336     }
337     return nRes;
338 }
339 
340 
341 
342 sal_Bool SAL_CALL
343     MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
344             const PropertyValues& rProperties )
345         throw(IllegalArgumentException, RuntimeException)
346 {
347     MutexGuard  aGuard( GetLinguMutex() );
348 
349     if (rLocale == Locale()  ||  !rWord.getLength())
350         return sal_True;
351 
352     if (!hasLocale( rLocale ))
353 #ifdef LINGU_EXCEPTIONS
354         throw( IllegalArgumentException() );
355 #else
356         return sal_True;
357 #endif
358 
359     // Get property values to be used.
360     // These are be the default values set in the SN_LINGU_PROPERTIES
361     // PropertySet which are overridden by the supplied ones from the
362     // last argument.
363     // You'll probably like to use a simplier solution than the provided
364     // one using the PropertyHelper_Spell.
365 
366     PropertyHelper_Spell &rHelper = GetPropHelper();
367     rHelper.SetTmpPropVals( rProperties );
368 
369     sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
370     if (nFailure != -1)
371     {
372         sal_Int16 nLang = LocaleToLanguage( rLocale );
373         // postprocess result for errors that should be ignored
374         if (   (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang ))
375             || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
376             || (!rHelper.IsSpellCapitalization()
377                 &&  nFailure == SpellFailure::CAPTION_ERROR)
378         )
379             nFailure = -1;
380     }
381 
382     return (nFailure == -1);
383 }
384 
385 
386 Reference< XSpellAlternatives >
387     MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
388 {
389     // Retrieves the return values for the 'spell' function call in case
390     // of a misspelled word.
391     // Especially it may give a list of suggested (correct) words:
392 
393     Reference< XSpellAlternatives > xRes;
394         // note: mutex is held by higher up by spell which covers both
395 
396     sal_Int16 nLang = LocaleToLanguage( rLocale );
397     int count;
398     Sequence< OUString > aStr( 0 );
399 
400         // first handle smart quotes (single and double)
401     OUStringBuffer rBuf(rWord);
402         sal_Int32 n = rBuf.getLength();
403         sal_Unicode c;
404     for (sal_Int32 ix=0; ix < n; ix++) {
405          c = rBuf.charAt(ix);
406              if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
407              if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
408         }
409         OUString nWord(rBuf.makeStringAndClear());
410 
411     if (n)
412     {
413         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
414         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
415         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
416         if(rLocale.Country.getLength()>0)
417         {
418             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
419             NSString* aTag = @"_";
420             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
421             [aLang autorelease];
422             aLang = [aLang  stringByAppendingString:aTaggedCountry];
423         }
424         [macSpell setLanguage:aLang];
425         NSArray *guesses = [macSpell guessesForWord:aNSStr];
426         count = [guesses count];
427         if (count)
428         {
429            aStr.realloc( count );
430            OUString *pStr = aStr.getArray();
431                for (int ii=0; ii < count; ii++)
432                {
433                   // if needed add: if (suglst[ii] == NULL) continue;
434                   NSString* guess = [guesses objectAtIndex:ii];
435                   OUString cvtwrd((const sal_Unicode*)[guess cStringUsingEncoding:NSUnicodeStringEncoding], (sal_Int32)[guess length]);
436                   pStr[ii] = cvtwrd;
437                }
438         }
439        [pool release];
440     }
441 
442             // now return an empty alternative for no suggestions or the list of alternatives if some found
443         SpellAlternatives *pAlt = new SpellAlternatives;
444             String aTmp(rWord);
445         pAlt->SetWordLanguage( aTmp, nLang );
446         pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
447         pAlt->SetAlternatives( aStr );
448         xRes = pAlt;
449         return xRes;
450 
451 }
452 
453 
454 
455 
456 Reference< XSpellAlternatives > SAL_CALL
457     MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
458             const PropertyValues& rProperties )
459         throw(IllegalArgumentException, RuntimeException)
460 {
461     MutexGuard  aGuard( GetLinguMutex() );
462 
463     if (rLocale == Locale()  ||  !rWord.getLength())
464         return NULL;
465 
466     if (!hasLocale( rLocale ))
467 #ifdef LINGU_EXCEPTIONS
468         throw( IllegalArgumentException() );
469 #else
470         return NULL;
471 #endif
472 
473     Reference< XSpellAlternatives > xAlt;
474     if (!isValid( rWord, rLocale, rProperties ))
475     {
476         xAlt =  GetProposals( rWord, rLocale );
477     }
478     return xAlt;
479 }
480 
481 
482 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
483             const Reference< XMultiServiceFactory > & /*rSMgr*/ )
484         throw(Exception)
485 {
486 
487     Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
488     return xService;
489 }
490 
491 
492 sal_Bool SAL_CALL
493     MacSpellChecker::addLinguServiceEventListener(
494             const Reference< XLinguServiceEventListener >& rxLstnr )
495         throw(RuntimeException)
496 {
497     MutexGuard  aGuard( GetLinguMutex() );
498 
499     sal_Bool bRes = sal_False;
500     if (!bDisposing && rxLstnr.is())
501     {
502         bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
503     }
504     return bRes;
505 }
506 
507 
508 sal_Bool SAL_CALL
509     MacSpellChecker::removeLinguServiceEventListener(
510             const Reference< XLinguServiceEventListener >& rxLstnr )
511         throw(RuntimeException)
512 {
513     MutexGuard  aGuard( GetLinguMutex() );
514 
515     sal_Bool bRes = sal_False;
516     if (!bDisposing && rxLstnr.is())
517     {
518         DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
519         bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
520     }
521     return bRes;
522 }
523 
524 
525 OUString SAL_CALL
526     MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
527         throw(RuntimeException)
528 {
529     MutexGuard  aGuard( GetLinguMutex() );
530     return A2OU( "Mac OS X Spell Checker" );
531 }
532 
533 
534 void SAL_CALL
535     MacSpellChecker::initialize( const Sequence< Any >& rArguments )
536         throw(Exception, RuntimeException)
537 {
538     MutexGuard  aGuard( GetLinguMutex() );
539 
540     if (!pPropHelper)
541     {
542         sal_Int32 nLen = rArguments.getLength();
543         if (2 == nLen)
544         {
545             Reference< XPropertySet >   xPropSet;
546             rArguments.getConstArray()[0] >>= xPropSet;
547             //rArguments.getConstArray()[1] >>= xDicList;
548 
549             //! Pointer allows for access of the non-UNO functions.
550             //! And the reference to the UNO-functions while increasing
551             //! the ref-count and will implicitly free the memory
552             //! when the object is not longer used.
553             pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
554             xPropHelper = pPropHelper;
555             pPropHelper->AddAsPropListener();   //! after a reference is established
556         }
557         else
558             DBG_ERROR( "wrong number of arguments in sequence" );
559 
560     }
561 }
562 
563 
564 void SAL_CALL
565     MacSpellChecker::dispose()
566         throw(RuntimeException)
567 {
568     MutexGuard  aGuard( GetLinguMutex() );
569 
570     if (!bDisposing)
571     {
572         bDisposing = sal_True;
573         EventObject aEvtObj( (XSpellChecker *) this );
574         aEvtListeners.disposeAndClear( aEvtObj );
575     }
576 }
577 
578 
579 void SAL_CALL
580     MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
581         throw(RuntimeException)
582 {
583     MutexGuard  aGuard( GetLinguMutex() );
584 
585     if (!bDisposing && rxListener.is())
586         aEvtListeners.addInterface( rxListener );
587 }
588 
589 
590 void SAL_CALL
591     MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
592         throw(RuntimeException)
593 {
594     MutexGuard  aGuard( GetLinguMutex() );
595 
596     if (!bDisposing && rxListener.is())
597         aEvtListeners.removeInterface( rxListener );
598 }
599 
600 
601 ///////////////////////////////////////////////////////////////////////////
602 // Service specific part
603 //
604 
605 OUString SAL_CALL MacSpellChecker::getImplementationName()
606         throw(RuntimeException)
607 {
608     MutexGuard  aGuard( GetLinguMutex() );
609 
610     return getImplementationName_Static();
611 }
612 
613 
614 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
615         throw(RuntimeException)
616 {
617     MutexGuard  aGuard( GetLinguMutex() );
618 
619     Sequence< OUString > aSNL = getSupportedServiceNames();
620     const OUString * pArray = aSNL.getConstArray();
621     for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
622         if( pArray[i] == ServiceName )
623             return sal_True;
624     return sal_False;
625 }
626 
627 
628 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
629         throw(RuntimeException)
630 {
631     MutexGuard  aGuard( GetLinguMutex() );
632 
633     return getSupportedServiceNames_Static();
634 }
635 
636 
637 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
638         throw()
639 {
640     MutexGuard  aGuard( GetLinguMutex() );
641 
642     Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
643     aSNS.getArray()[0] = A2OU( SN_SPELLCHECKER );
644     return aSNS;
645 }
646 
647 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
648             XMultiServiceFactory * pServiceManager, void *  )
649 {
650     void * pRet = 0;
651     if ( !MacSpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
652     {
653         Reference< XSingleServiceFactory > xFactory =
654             cppu::createOneInstanceFactory(
655                 pServiceManager,
656                 MacSpellChecker::getImplementationName_Static(),
657                 MacSpellChecker_CreateInstance,
658                 MacSpellChecker::getSupportedServiceNames_Static());
659         // acquire, because we return an interface pointer instead of a reference
660         xFactory->acquire();
661         pRet = xFactory.get();
662     }
663     return pRet;
664 }
665 
666 
667 ///////////////////////////////////////////////////////////////////////////
668