bump product version to 4.1.6.2
[LibreOffice.git] / linguistic / source / hyphdsp.cxx
blob7b5c0a96a7149970f50847e0014fa67068b1b421
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 <i18nlangtag/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;
47 HyphenatorDispatcher::HyphenatorDispatcher( LngSvcMgr &rLngSvcMgr ) :
48 rMgr (rLngSvcMgr)
53 HyphenatorDispatcher::~HyphenatorDispatcher()
55 ClearSvcList();
59 void HyphenatorDispatcher::ClearSvcList()
61 // release memory for each table entry
62 HyphSvcByLangMap_t aTmp;
63 aSvcMap.swap( aTmp );
67 Reference<XHyphenatedWord> HyphenatorDispatcher::buildHyphWord(
68 const OUString rOrigWord,
69 const Reference<XDictionaryEntry> &xEntry,
70 sal_Int16 nLang, sal_Int16 nMaxLeading )
72 MutexGuard aGuard( GetLinguMutex() );
74 Reference< XHyphenatedWord > xRes;
76 if (xEntry.is())
78 OUString aText( xEntry->getDictionaryWord() );
79 sal_Int32 nTextLen = aText.getLength();
81 // trailing '=' means "hyphenation should not be possible"
82 if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=')
84 sal_Int16 nHyphenationPos = -1;
86 OUStringBuffer aTmp( nTextLen );
87 sal_Bool bSkip = sal_False;
88 sal_Int32 nHyphIdx = -1;
89 sal_Int32 nLeading = 0;
90 for (sal_Int32 i = 0; i < nTextLen; i++)
92 sal_Unicode cTmp = aText[i];
93 if (cTmp != '=')
95 aTmp.append( cTmp );
96 nLeading++;
97 bSkip = sal_False;
98 nHyphIdx++;
100 else
102 if (!bSkip && nHyphIdx >= 0)
104 if (nLeading <= nMaxLeading)
105 nHyphenationPos = (sal_Int16) nHyphIdx;
107 bSkip = sal_True; //! multiple '=' should count as one only
111 if (nHyphenationPos > 0)
113 aText = aTmp.makeStringAndClear();
115 #if OSL_DEBUG_LEVEL > 1
117 if (aText != rOrigWord)
119 // both words should only differ by a having a trailing '.'
120 // character or not...
121 OUString aShorter, aLonger;
122 if (aText.getLength() <= rOrigWord.getLength())
124 aShorter = aText;
125 aLonger = rOrigWord;
127 else
129 aShorter = rOrigWord;
130 aLonger = aText;
132 xub_StrLen nS = sal::static_int_cast< xub_StrLen >( aShorter.getLength() );
133 xub_StrLen nL = sal::static_int_cast< xub_StrLen >( aLonger.getLength() );
134 if (nS > 0 && nL > 0)
136 DBG_ASSERT( (nS + 1 == nL) && aLonger[nL-1] == (sal_Unicode) '.',
137 "HyphenatorDispatcher::buildHyphWord: unexpected difference between words!" );
141 #endif
142 //! take care of #i22591#
143 aText = rOrigWord;
145 DBG_ASSERT( aText == rOrigWord, "failed to " );
146 xRes = new HyphenatedWord( aText, nLang, nHyphenationPos,
147 aText, nHyphenationPos );
152 return xRes;
156 Reference< XPossibleHyphens > HyphenatorDispatcher::buildPossHyphens(
157 const Reference< XDictionaryEntry > &xEntry, sal_Int16 nLanguage )
159 MutexGuard aGuard( GetLinguMutex() );
161 Reference<XPossibleHyphens> xRes;
163 if (xEntry.is())
165 // text with hyphenation info
166 OUString aText( xEntry->getDictionaryWord() );
167 sal_Int32 nTextLen = aText.getLength();
169 // trailing '=' means "hyphenation should not be possible"
170 if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=')
172 // sequence to hold hyphenation positions
173 Sequence< sal_Int16 > aHyphPos( nTextLen );
174 sal_Int16 *pPos = aHyphPos.getArray();
175 sal_Int32 nHyphCount = 0;
177 OUStringBuffer aTmp( nTextLen );
178 sal_Bool bSkip = sal_False;
179 sal_Int32 nHyphIdx = -1;
180 for (sal_Int32 i = 0; i < nTextLen; i++)
182 sal_Unicode cTmp = aText[i];
183 if (cTmp != '=')
185 aTmp.append( cTmp );
186 bSkip = sal_False;
187 nHyphIdx++;
189 else
191 if (!bSkip && nHyphIdx >= 0)
192 pPos[ nHyphCount++ ] = (sal_Int16) nHyphIdx;
193 bSkip = sal_True; //! multiple '=' should count as one only
197 // ignore (multiple) trailing '='
198 if (bSkip && nHyphIdx >= 0)
200 nHyphCount--;
202 DBG_ASSERT( nHyphCount >= 0, "lng : invalid hyphenation count");
204 if (nHyphCount > 0)
206 aHyphPos.realloc( nHyphCount );
207 xRes = new PossibleHyphens( aTmp.makeStringAndClear(), nLanguage,
208 aText, aHyphPos );
213 return xRes;
217 Sequence< Locale > SAL_CALL HyphenatorDispatcher::getLocales()
218 throw(RuntimeException)
220 MutexGuard aGuard( GetLinguMutex() );
222 Sequence< Locale > aLocales( static_cast< sal_Int32 >(aSvcMap.size()) );
223 Locale *pLocales = aLocales.getArray();
224 HyphSvcByLangMap_t::const_iterator aIt;
225 for (aIt = aSvcMap.begin(); aIt != aSvcMap.end(); ++aIt)
227 *pLocales++ = LanguageTag( aIt->first ).getLocale();
229 return aLocales;
233 sal_Bool SAL_CALL HyphenatorDispatcher::hasLocale(const Locale& rLocale)
234 throw(RuntimeException)
236 MutexGuard aGuard( GetLinguMutex() );
237 HyphSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
238 return aIt != aSvcMap.end();
242 Reference< XHyphenatedWord > SAL_CALL
243 HyphenatorDispatcher::hyphenate(
244 const OUString& rWord, const Locale& rLocale, sal_Int16 nMaxLeading,
245 const PropertyValues& rProperties )
246 throw(IllegalArgumentException, RuntimeException)
248 MutexGuard aGuard( GetLinguMutex() );
250 Reference< XHyphenatedWord > xRes;
252 sal_Int32 nWordLen = rWord.getLength();
253 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
254 if (LinguIsUnspecified(nLanguage) || !nWordLen ||
255 nMaxLeading == 0 || nMaxLeading == nWordLen)
256 return xRes;
258 // search for entry with that language
259 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
260 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
262 bool bWordModified = false;
263 if (!pEntry || (nMaxLeading < 0 || nMaxLeading > nWordLen))
265 return NULL;
267 else
269 OUString aChkWord( rWord );
271 // replace typographical apostroph by ascii apostroph
272 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
273 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
274 if (aSingleQuote.Len())
275 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
277 bWordModified |= RemoveHyphens( aChkWord );
278 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
279 bWordModified |= RemoveControlChars( aChkWord );
280 sal_Int16 nChkMaxLeading = (sal_Int16) GetPosInWordToCheck( rWord, nMaxLeading );
282 // check for results from (positive) dictionaries which have precedence!
283 Reference< XDictionaryEntry > xEntry;
285 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
287 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
288 sal_True, sal_False );
291 if (xEntry.is())
293 //! because queryDictionaryEntry (in the end DictionaryNeo::getEntry)
294 //! does not distinguish betwee "XYZ" and "XYZ." in order to avoid
295 //! to require them as different entry we have to supply the
296 //! original word here as well so it can be used in th result
297 //! otherwise a strange effect may occur (see #i22591#)
298 xRes = buildHyphWord( rWord, xEntry, nLanguage, nChkMaxLeading );
300 else
302 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
303 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
304 "lng : index out of range");
306 sal_Int32 i = 0;
307 Reference< XHyphenator > xHyph;
308 if (pEntry->aSvcRefs.getLength() > 0)
309 xHyph = pEntry->aSvcRefs[0];
311 // try already instantiated service
312 if (i <= pEntry->nLastTriedSvcIndex)
314 if (xHyph.is() && xHyph->hasLocale( rLocale ))
315 xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
316 rProperties );
317 ++i;
319 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
320 // instantiate services and try it
322 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
324 Reference< XComponentContext > xContext(
325 comphelper::getProcessComponentContext() );
327 // build service initialization argument
328 Sequence< Any > aArgs(2);
329 aArgs.getArray()[0] <<= GetPropSet();
331 // create specific service via it's implementation name
334 xHyph = Reference< XHyphenator >(
335 xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
336 pEntry->aSvcImplNames[0], aArgs, xContext ),
337 UNO_QUERY );
339 catch (uno::Exception &)
341 DBG_ASSERT( 0, "createInstanceWithArguments failed" );
343 pRef [i] = xHyph;
345 Reference< XLinguServiceEventBroadcaster >
346 xBroadcaster( xHyph, UNO_QUERY );
347 if (xBroadcaster.is())
348 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
350 if (xHyph.is() && xHyph->hasLocale( rLocale ))
351 xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
352 rProperties );
354 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
355 ++i;
357 // if language is not supported by the services
358 // remove it from the list.
359 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
360 aSvcMap.erase( nLanguage );
362 } // if (xEntry.is())
365 if (bWordModified && xRes.is())
366 xRes = RebuildHyphensAndControlChars( rWord, xRes );
368 if (xRes.is() && xRes->getWord() != rWord)
370 xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
371 xRes->getHyphenatedWord(),
372 xRes->getHyphenPos() );
375 return xRes;
379 Reference< XHyphenatedWord > SAL_CALL
380 HyphenatorDispatcher::queryAlternativeSpelling(
381 const OUString& rWord, const Locale& rLocale, sal_Int16 nIndex,
382 const PropertyValues& rProperties )
383 throw(IllegalArgumentException, RuntimeException)
385 MutexGuard aGuard( GetLinguMutex() );
387 Reference< XHyphenatedWord > xRes;
389 sal_Int32 nWordLen = rWord.getLength();
390 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
391 if (LinguIsUnspecified(nLanguage) || !nWordLen)
392 return xRes;
394 // search for entry with that language
395 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
396 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
398 bool bWordModified = false;
399 if (!pEntry || !(0 <= nIndex && nIndex <= nWordLen - 2))
401 return NULL;
403 else
405 OUString aChkWord( rWord );
407 // replace typographical apostroph by ascii apostroph
408 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
409 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
410 if (aSingleQuote.Len())
411 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
413 bWordModified |= RemoveHyphens( aChkWord );
414 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
415 bWordModified |= RemoveControlChars( aChkWord );
416 sal_Int16 nChkIndex = (sal_Int16) GetPosInWordToCheck( rWord, nIndex );
418 // check for results from (positive) dictionaries which have precedence!
419 Reference< XDictionaryEntry > xEntry;
421 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
423 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
424 sal_True, sal_False );
427 if (xEntry.is())
429 //! alternative spellings not yet supported by dictionaries
431 else
433 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
434 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
435 "lng : index out of range");
437 sal_Int32 i = 0;
438 Reference< XHyphenator > xHyph;
439 if (pEntry->aSvcRefs.getLength() > 0)
440 xHyph = pEntry->aSvcRefs[0];
442 // try already instantiated service
443 if (i <= pEntry->nLastTriedSvcIndex)
445 if (xHyph.is() && xHyph->hasLocale( rLocale ))
446 xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
447 nChkIndex, rProperties );
448 ++i;
450 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
451 // instantiate services and try it
453 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
455 Reference< XComponentContext > xContext(
456 comphelper::getProcessComponentContext() );
458 // build service initialization argument
459 Sequence< Any > aArgs(2);
460 aArgs.getArray()[0] <<= GetPropSet();
462 // create specific service via it's implementation name
465 xHyph = Reference< XHyphenator >(
466 xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
467 pEntry->aSvcImplNames[0], aArgs, xContext ), UNO_QUERY );
469 catch (uno::Exception &)
471 DBG_ASSERT( 0, "createInstanceWithArguments failed" );
473 pRef [i] = xHyph;
475 Reference< XLinguServiceEventBroadcaster >
476 xBroadcaster( xHyph, UNO_QUERY );
477 if (xBroadcaster.is())
478 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
480 if (xHyph.is() && xHyph->hasLocale( rLocale ))
481 xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
482 nChkIndex, rProperties );
484 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
485 ++i;
487 // if language is not supported by the services
488 // remove it from the list.
489 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
490 aSvcMap.erase( nLanguage );
492 } // if (xEntry.is())
495 if (bWordModified && xRes.is())
496 xRes = RebuildHyphensAndControlChars( rWord, xRes );
498 if (xRes.is() && xRes->getWord() != rWord)
500 xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
501 xRes->getHyphenatedWord(),
502 xRes->getHyphenPos() );
505 return xRes;
509 Reference< XPossibleHyphens > SAL_CALL
510 HyphenatorDispatcher::createPossibleHyphens(
511 const OUString& rWord, const Locale& rLocale,
512 const PropertyValues& rProperties )
513 throw(IllegalArgumentException, RuntimeException)
515 MutexGuard aGuard( GetLinguMutex() );
517 Reference< XPossibleHyphens > xRes;
519 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
520 if (LinguIsUnspecified(nLanguage) || rWord.isEmpty())
521 return xRes;
523 // search for entry with that language
524 HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
525 LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
527 if (pEntry)
529 OUString aChkWord( rWord );
531 // replace typographical apostroph by ascii apostroph
532 String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
533 DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
534 if (aSingleQuote.Len())
535 aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
537 RemoveHyphens( aChkWord );
538 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
539 RemoveControlChars( aChkWord );
541 // check for results from (positive) dictionaries which have precedence!
542 Reference< XDictionaryEntry > xEntry;
544 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
546 xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
547 sal_True, sal_False );
550 if (xEntry.is())
552 xRes = buildPossHyphens( xEntry, nLanguage );
554 else
556 sal_Int32 nLen = pEntry->aSvcImplNames.getLength() > 0 ? 1 : 0;
557 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
558 "lng : index out of range");
560 sal_Int32 i = 0;
561 Reference< XHyphenator > xHyph;
562 if (pEntry->aSvcRefs.getLength() > 0)
563 xHyph = pEntry->aSvcRefs[0];
565 // try already instantiated service
566 if (i <= pEntry->nLastTriedSvcIndex)
568 if (xHyph.is() && xHyph->hasLocale( rLocale ))
569 xRes = xHyph->createPossibleHyphens( aChkWord, rLocale,
570 rProperties );
571 ++i;
573 else if (pEntry->nLastTriedSvcIndex < nLen - 1)
574 // instantiate services and try it
576 Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
578 Reference< XComponentContext > xContext(
579 comphelper::getProcessComponentContext() );
581 // build service initialization argument
582 Sequence< Any > aArgs(2);
583 aArgs.getArray()[0] <<= GetPropSet();
585 // create specific service via it's implementation name
588 xHyph = Reference< XHyphenator >(
589 xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
590 pEntry->aSvcImplNames[0], aArgs, xContext ),
591 UNO_QUERY );
593 catch (uno::Exception &)
595 DBG_ASSERT( 0, "createWithArguments failed" );
597 pRef [i] = xHyph;
599 Reference< XLinguServiceEventBroadcaster >
600 xBroadcaster( xHyph, UNO_QUERY );
601 if (xBroadcaster.is())
602 rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
604 if (xHyph.is() && xHyph->hasLocale( rLocale ))
605 xRes = xHyph->createPossibleHyphens( aChkWord, rLocale, rProperties );
607 pEntry->nLastTriedSvcIndex = (sal_Int16) i;
608 ++i;
610 // if language is not supported by the services
611 // remove it from the list.
612 if (xHyph.is() && !xHyph->hasLocale( rLocale ))
613 aSvcMap.erase( nLanguage );
615 } // if (xEntry.is())
618 if (xRes.is() && xRes->getWord() != rWord)
620 xRes = new PossibleHyphens( rWord, nLanguage,
621 xRes->getPossibleHyphens(),
622 xRes->getHyphenationPositions() );
625 return xRes;
629 void HyphenatorDispatcher::SetServiceList( const Locale &rLocale,
630 const Sequence< OUString > &rSvcImplNames )
632 MutexGuard aGuard( GetLinguMutex() );
634 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
636 sal_Int32 nLen = rSvcImplNames.getLength();
637 if (0 == nLen)
638 // remove entry
639 aSvcMap.erase( nLanguage );
640 else
642 // modify/add entry
643 LangSvcEntries_Hyph *pEntry = aSvcMap[ nLanguage ].get();
644 if (pEntry)
646 pEntry->Clear();
647 pEntry->aSvcImplNames = rSvcImplNames;
648 pEntry->aSvcImplNames.realloc(1);
649 pEntry->aSvcRefs = Sequence< Reference < XHyphenator > > ( 1 );
651 else
653 boost::shared_ptr< LangSvcEntries_Hyph > pTmpEntry( new LangSvcEntries_Hyph( rSvcImplNames[0] ) );
654 pTmpEntry->aSvcRefs = Sequence< Reference < XHyphenator > >( 1 );
655 aSvcMap[ nLanguage ] = pTmpEntry;
661 Sequence< OUString >
662 HyphenatorDispatcher::GetServiceList( const Locale &rLocale ) const
664 MutexGuard aGuard( GetLinguMutex() );
666 Sequence< OUString > aRes;
668 // search for entry with that language and use data from that
669 sal_Int16 nLanguage = LinguLocaleToLanguage( rLocale );
670 HyphenatorDispatcher *pThis = (HyphenatorDispatcher *) this;
671 const HyphSvcByLangMap_t::iterator aIt( pThis->aSvcMap.find( nLanguage ) );
672 const LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
673 if (pEntry)
675 aRes = pEntry->aSvcImplNames;
676 if (aRes.getLength() > 0)
677 aRes.realloc(1);
680 return aRes;
684 LinguDispatcher::DspType HyphenatorDispatcher::GetDspType() const
686 return DSP_HYPH;
691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */