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