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