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