Version 4.0.2.1, tag libreoffice-4.0.2.1
[LibreOffice.git] / linguistic / source / hyphdsp.cxx
blob757e836fc1266c8c3f2db867ebd773d283d1cf46
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <cppuhelper/factory.hxx> // helper for factories
21 #include <com/sun/star/registry/XRegistryKey.hpp>
22 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
23 #include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
24 #include <rtl/ustrbuf.hxx>
25 #include <i18npool/lang.h>
26 #include <unotools/localedatawrapper.hxx>
27 #include <tools/debug.hxx>
28 #include <svl/lngmisc.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <osl/mutex.hxx>
32 #include "hyphdsp.hxx"
33 #include "linguistic/hyphdta.hxx"
34 #include "linguistic/lngprops.hxx"
35 #include "lngsvcmgr.hxx"
37 using namespace osl;
38 using namespace com::sun::star;
39 using namespace com::sun::star::beans;
40 using namespace com::sun::star::lang;
41 using namespace com::sun::star::uno;
42 using namespace com::sun::star::linguistic2;
43 using namespace linguistic;
45 using ::rtl::OUString;
46 using ::rtl::OUStringBuffer;
49 HyphenatorDispatcher::HyphenatorDispatcher( LngSvcMgr &rLngSvcMgr ) :
50 rMgr (rLngSvcMgr)
55 HyphenatorDispatcher::~HyphenatorDispatcher()
57 ClearSvcList();
61 void HyphenatorDispatcher::ClearSvcList()
63 // release memory for each table entry
64 HyphSvcByLangMap_t aTmp;
65 aSvcMap.swap( aTmp );
69 Reference<XHyphenatedWord> HyphenatorDispatcher::buildHyphWord(
70 const OUString rOrigWord,
71 const Reference<XDictionaryEntry> &xEntry,
72 sal_Int16 nLang, sal_Int16 nMaxLeading )
74 MutexGuard aGuard( GetLinguMutex() );
76 Reference< XHyphenatedWord > xRes;
78 if (xEntry.is())
80 OUString aText( xEntry->getDictionaryWord() );
81 sal_Int32 nTextLen = aText.getLength();
83 // trailing '=' means "hyphenation should not be possible"
84 if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=')
86 sal_Int16 nHyphenationPos = -1;
88 OUStringBuffer aTmp( nTextLen );
89 sal_Bool bSkip = sal_False;
90 sal_Int32 nHyphIdx = -1;
91 sal_Int32 nLeading = 0;
92 for (sal_Int32 i = 0; i < nTextLen; i++)
94 sal_Unicode cTmp = aText[i];
95 if (cTmp != '=')
97 aTmp.append( cTmp );
98 nLeading++;
99 bSkip = sal_False;
100 nHyphIdx++;
102 else
104 if (!bSkip && nHyphIdx >= 0)
106 if (nLeading <= nMaxLeading)
107 nHyphenationPos = (sal_Int16) nHyphIdx;
109 bSkip = sal_True; //! multiple '=' should count as one only
113 if (nHyphenationPos > 0)
115 aText = aTmp.makeStringAndClear();
117 #if OSL_DEBUG_LEVEL > 1
119 if (aText != rOrigWord)
121 // both words should only differ by a having a trailing '.'
122 // character or not...
123 OUString aShorter, aLonger;
124 if (aText.getLength() <= rOrigWord.getLength())
126 aShorter = aText;
127 aLonger = rOrigWord;
129 else
131 aShorter = rOrigWord;
132 aLonger = aText;
134 xub_StrLen nS = sal::static_int_cast< xub_StrLen >( aShorter.getLength() );
135 xub_StrLen nL = sal::static_int_cast< xub_StrLen >( aLonger.getLength() );
136 if (nS > 0 && nL > 0)
138 DBG_ASSERT( (nS + 1 == nL) && aLonger[nL-1] == (sal_Unicode) '.',
139 "HyphenatorDispatcher::buildHyphWord: unexpected difference between words!" );
143 #endif
144 //! take care of #i22591#
145 aText = rOrigWord;
147 DBG_ASSERT( aText == rOrigWord, "failed to " );
148 xRes = new HyphenatedWord( aText, nLang, nHyphenationPos,
149 aText, nHyphenationPos );
154 return xRes;
158 Reference< XPossibleHyphens > HyphenatorDispatcher::buildPossHyphens(
159 const Reference< XDictionaryEntry > &xEntry, sal_Int16 nLanguage )
161 MutexGuard aGuard( GetLinguMutex() );
163 Reference<XPossibleHyphens> xRes;
165 if (xEntry.is())
167 // text with hyphenation info
168 OUString aText( xEntry->getDictionaryWord() );
169 sal_Int32 nTextLen = aText.getLength();
171 // trailing '=' means "hyphenation should not be possible"
172 if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=')
174 // sequence to hold hyphenation positions
175 Sequence< sal_Int16 > aHyphPos( nTextLen );
176 sal_Int16 *pPos = aHyphPos.getArray();
177 sal_Int32 nHyphCount = 0;
179 OUStringBuffer aTmp( nTextLen );
180 sal_Bool bSkip = sal_False;
181 sal_Int32 nHyphIdx = -1;
182 for (sal_Int32 i = 0; i < nTextLen; i++)
184 sal_Unicode cTmp = aText[i];
185 if (cTmp != '=')
187 aTmp.append( cTmp );
188 bSkip = sal_False;
189 nHyphIdx++;
191 else
193 if (!bSkip && nHyphIdx >= 0)
194 pPos[ nHyphCount++ ] = (sal_Int16) nHyphIdx;
195 bSkip = sal_True; //! multiple '=' should count as one only
199 // ignore (multiple) trailing '='
200 if (bSkip && nHyphIdx >= 0)
202 nHyphCount--;
204 DBG_ASSERT( nHyphCount >= 0, "lng : invalid hyphenation count");
206 if (nHyphCount > 0)
208 aHyphPos.realloc( nHyphCount );
209 xRes = new PossibleHyphens( aTmp.makeStringAndClear(), nLanguage,
210 aText, aHyphPos );
215 return xRes;
219 Sequence< Locale > SAL_CALL HyphenatorDispatcher::getLocales()
220 throw(RuntimeException)
222 MutexGuard aGuard( GetLinguMutex() );
224 Sequence< Locale > aLocales( static_cast< sal_Int32 >(aSvcMap.size()) );
225 Locale *pLocales = aLocales.getArray();
226 HyphSvcByLangMap_t::const_iterator aIt;
227 for (aIt = aSvcMap.begin(); aIt != aSvcMap.end(); ++aIt)
229 *pLocales++ = LanguageTag( aIt->first ).getLocale();
231 return aLocales;
235 sal_Bool SAL_CALL HyphenatorDispatcher::hasLocale(const Locale& rLocale)
236 throw(RuntimeException)
238 MutexGuard aGuard( GetLinguMutex() );
239 HyphSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
240 return aIt != aSvcMap.end();
244 Reference< XHyphenatedWord > SAL_CALL
245 HyphenatorDispatcher::hyphenate(
246 const OUString& rWord, const Locale& rLocale, sal_Int16 nMaxLeading,
247 const PropertyValues& rProperties )
248 throw(IllegalArgumentException, RuntimeException)
250 MutexGuard aGuard( GetLinguMutex() );
252 Reference< XHyphenatedWord > xRes;
254 sal_Int32 nWordLen = rWord.getLength();
255 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
256 if (LinguIsUnspecified(nLanguage) || !nWordLen ||
257 nMaxLeading == 0 || nMaxLeading == nWordLen)
258 return xRes;
260 // search for entry with that language
261 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
262 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
264 bool bWordModified = false;
265 if (!pEntry || (nMaxLeading < 0 || nMaxLeading > nWordLen))
267 return NULL;
269 else
271 OUString aChkWord( rWord );
273 // replace typographical apostroph by ascii apostroph
274 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
275 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
276 if (aSingleQuote.Len())
277 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
279 bWordModified |= RemoveHyphens( aChkWord );
280 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
281 bWordModified |= RemoveControlChars( aChkWord );
282 sal_Int16 nChkMaxLeading = (sal_Int16) GetPosInWordToCheck( rWord, nMaxLeading );
284 // check for results from (positive) dictionaries which have precedence!
285 Reference< XDictionaryEntry > xEntry;
287 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
289 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
290 sal_True, sal_False );
293 if (xEntry.is())
295 //! because queryDictionaryEntry (in the end DictionaryNeo::getEntry)
296 //! does not distinguish betwee "XYZ" and "XYZ." in order to avoid
297 //! to require them as different entry we have to supply the
298 //! original word here as well so it can be used in th result
299 //! otherwise a strange effect may occur (see #i22591#)
300 xRes = buildHyphWord( rWord, xEntry, nLanguage, nChkMaxLeading );
302 else
304 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
305 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
306 "lng : index out of range");
308 sal_Int32 i = 0;
309 Reference< XHyphenator > xHyph;
310 if (pEntry->aSvcRefs.getLength() > 0)
311 xHyph = pEntry->aSvcRefs[0];
313 // try already instantiated service
314 if (i <= pEntry->nLastTriedSvcIndex)
316 if (xHyph.is() && xHyph->hasLocale( rLocale ))
317 xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
318 rProperties );
319 ++i;
321 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
322 // instantiate services and try it
324 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
326 Reference< XMultiServiceFactory > xMgr(
327 comphelper::getProcessServiceFactory() );
328 if (xMgr.is())
330 // build service initialization argument
331 Sequence< Any > aArgs(2);
332 aArgs.getArray()[0] <<= GetPropSet();
334 // create specific service via it's implementation name
337 xHyph = Reference< XHyphenator >(
338 xMgr->createInstanceWithArguments(
339 pEntry->aSvcImplNames[0], aArgs ), UNO_QUERY );
341 catch (uno::Exception &)
343 DBG_ASSERT( 0, "createInstanceWithArguments failed" );
345 pRef [i] = xHyph;
347 Reference< XLinguServiceEventBroadcaster >
348 xBroadcaster( xHyph, UNO_QUERY );
349 if (xBroadcaster.is())
350 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
352 if (xHyph.is() && xHyph->hasLocale( rLocale ))
353 xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
354 rProperties );
356 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
357 ++i;
359 // if language is not supported by the services
360 // remove it from the list.
361 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
362 aSvcMap.erase( nLanguage );
365 } // if (xEntry.is())
368 if (bWordModified && xRes.is())
369 xRes = RebuildHyphensAndControlChars( rWord, xRes );
371 if (xRes.is() && xRes->getWord() != rWord)
373 xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
374 xRes->getHyphenatedWord(),
375 xRes->getHyphenPos() );
378 return xRes;
382 Reference< XHyphenatedWord > SAL_CALL
383 HyphenatorDispatcher::queryAlternativeSpelling(
384 const OUString& rWord, const Locale& rLocale, sal_Int16 nIndex,
385 const PropertyValues& rProperties )
386 throw(IllegalArgumentException, RuntimeException)
388 MutexGuard aGuard( GetLinguMutex() );
390 Reference< XHyphenatedWord > xRes;
392 sal_Int32 nWordLen = rWord.getLength();
393 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
394 if (LinguIsUnspecified(nLanguage) || !nWordLen)
395 return xRes;
397 // search for entry with that language
398 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
399 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
401 bool bWordModified = false;
402 if (!pEntry || !(0 <= nIndex && nIndex <= nWordLen - 2))
404 return NULL;
406 else
408 OUString aChkWord( rWord );
410 // replace typographical apostroph by ascii apostroph
411 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
412 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
413 if (aSingleQuote.Len())
414 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
416 bWordModified |= RemoveHyphens( aChkWord );
417 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
418 bWordModified |= RemoveControlChars( aChkWord );
419 sal_Int16 nChkIndex = (sal_Int16) GetPosInWordToCheck( rWord, nIndex );
421 // check for results from (positive) dictionaries which have precedence!
422 Reference< XDictionaryEntry > xEntry;
424 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
426 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
427 sal_True, sal_False );
430 if (xEntry.is())
432 //! alternative spellings not yet supported by dictionaries
434 else
436 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
437 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
438 "lng : index out of range");
440 sal_Int32 i = 0;
441 Reference< XHyphenator > xHyph;
442 if (pEntry->aSvcRefs.getLength() > 0)
443 xHyph = pEntry->aSvcRefs[0];
445 // try already instantiated service
446 if (i <= pEntry->nLastTriedSvcIndex)
448 if (xHyph.is() && xHyph->hasLocale( rLocale ))
449 xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
450 nChkIndex, rProperties );
451 ++i;
453 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
454 // instantiate services and try it
456 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
458 Reference< XMultiServiceFactory > xMgr(
459 comphelper::getProcessServiceFactory() );
460 if (xMgr.is())
462 // build service initialization argument
463 Sequence< Any > aArgs(2);
464 aArgs.getArray()[0] <<= GetPropSet();
466 // create specific service via it's implementation name
469 xHyph = Reference< XHyphenator >(
470 xMgr->createInstanceWithArguments(
471 pEntry->aSvcImplNames[0], aArgs ), UNO_QUERY );
473 catch (uno::Exception &)
475 DBG_ASSERT( 0, "createInstanceWithArguments failed" );
477 pRef [i] = xHyph;
479 Reference< XLinguServiceEventBroadcaster >
480 xBroadcaster( xHyph, UNO_QUERY );
481 if (xBroadcaster.is())
482 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
484 if (xHyph.is() && xHyph->hasLocale( rLocale ))
485 xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
486 nChkIndex, rProperties );
488 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
489 ++i;
491 // if language is not supported by the services
492 // remove it from the list.
493 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
494 aSvcMap.erase( nLanguage );
497 } // if (xEntry.is())
500 if (bWordModified && xRes.is())
501 xRes = RebuildHyphensAndControlChars( rWord, xRes );
503 if (xRes.is() && xRes->getWord() != rWord)
505 xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
506 xRes->getHyphenatedWord(),
507 xRes->getHyphenPos() );
510 return xRes;
514 Reference< XPossibleHyphens > SAL_CALL
515 HyphenatorDispatcher::createPossibleHyphens(
516 const OUString& rWord, const Locale& rLocale,
517 const PropertyValues& rProperties )
518 throw(IllegalArgumentException, RuntimeException)
520 MutexGuard aGuard( GetLinguMutex() );
522 Reference< XPossibleHyphens > xRes;
524 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
525 if (LinguIsUnspecified(nLanguage) || rWord.isEmpty())
526 return xRes;
528 // search for entry with that language
529 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
530 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
532 if (pEntry)
534 OUString aChkWord( rWord );
536 // replace typographical apostroph by ascii apostroph
537 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
538 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
539 if (aSingleQuote.Len())
540 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
542 RemoveHyphens( aChkWord );
543 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
544 RemoveControlChars( aChkWord );
546 // check for results from (positive) dictionaries which have precedence!
547 Reference< XDictionaryEntry > xEntry;
549 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
551 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
552 sal_True, sal_False );
555 if (xEntry.is())
557 xRes = buildPossHyphens( xEntry, nLanguage );
559 else
561 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
562 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
563 "lng : index out of range");
565 sal_Int32 i = 0;
566 Reference< XHyphenator > xHyph;
567 if (pEntry->aSvcRefs.getLength() > 0)
568 xHyph = pEntry->aSvcRefs[0];
570 // try already instantiated service
571 if (i <= pEntry->nLastTriedSvcIndex)
573 if (xHyph.is() && xHyph->hasLocale( rLocale ))
574 xRes = xHyph->createPossibleHyphens( aChkWord, rLocale,
575 rProperties );
576 ++i;
578 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
579 // instantiate services and try it
581 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
583 Reference< XMultiServiceFactory > xMgr(
584 comphelper::getProcessServiceFactory() );
585 if (xMgr.is())
587 // build service initialization argument
588 Sequence< Any > aArgs(2);
589 aArgs.getArray()[0] <<= GetPropSet();
591 // create specific service via it's implementation name
594 xHyph = Reference< XHyphenator >(
595 xMgr->createInstanceWithArguments(
596 pEntry->aSvcImplNames[0], aArgs ), UNO_QUERY );
598 catch (uno::Exception &)
600 DBG_ASSERT( 0, "createWithArguments failed" );
602 pRef [i] = xHyph;
604 Reference< XLinguServiceEventBroadcaster >
605 xBroadcaster( xHyph, UNO_QUERY );
606 if (xBroadcaster.is())
607 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
609 if (xHyph.is() && xHyph->hasLocale( rLocale ))
610 xRes = xHyph->createPossibleHyphens( aChkWord, rLocale,
611 rProperties );
613 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
614 ++i;
616 // if language is not supported by the services
617 // remove it from the list.
618 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
619 aSvcMap.erase( nLanguage );
622 } // if (xEntry.is())
625 if (xRes.is() && xRes->getWord() != rWord)
627 xRes = new PossibleHyphens( rWord, nLanguage,
628 xRes->getPossibleHyphens(),
629 xRes->getHyphenationPositions() );
632 return xRes;
636 void HyphenatorDispatcher::SetServiceList( const Locale &rLocale,
637 const Sequence< OUString > &rSvcImplNames )
639 MutexGuard aGuard( GetLinguMutex() );
641 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
643 sal_Int32 nLen = rSvcImplNames.getLength();
644 if (0 == nLen)
645 // remove entry
646 aSvcMap.erase( nLanguage );
647 else
649 // modify/add entry
650 LangSvcEntries_Hyph *pEntry = aSvcMap[ nLanguage ].get();
651 if (pEntry)
653 pEntry->Clear();
654 pEntry->aSvcImplNames = rSvcImplNames;
655 pEntry->aSvcImplNames.realloc(1);
656 pEntry->aSvcRefs = Sequence< Reference < XHyphenator > > ( 1 );
658 else
660 boost::shared_ptr< LangSvcEntries_Hyph > pTmpEntry( new LangSvcEntries_Hyph( rSvcImplNames[0] ) );
661 pTmpEntry->aSvcRefs = Sequence< Reference < XHyphenator > >( 1 );
662 aSvcMap[ nLanguage ] = pTmpEntry;
668 Sequence< OUString >
669 HyphenatorDispatcher::GetServiceList( const Locale &rLocale ) const
671 MutexGuard aGuard( GetLinguMutex() );
673 Sequence< OUString > aRes;
675 // search for entry with that language and use data from that
676 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
677 HyphenatorDispatcher *pThis = (HyphenatorDispatcher *) this;
678 const HyphSvcByLangMap_t::iterator aIt( pThis->aSvcMap.find( nLanguage ) );
679 const LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
680 if (pEntry)
682 aRes = pEntry->aSvcImplNames;
683 if (aRes.getLength() > 0)
684 aRes.realloc(1);
687 return aRes;
691 LinguDispatcher::DspType HyphenatorDispatcher::GetDspType() const
693 return DSP_HYPH;
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */