1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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"
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
) :
55 HyphenatorDispatcher::~HyphenatorDispatcher()
61 void HyphenatorDispatcher::ClearSvcList()
63 // release memory for each table entry
64 HyphSvcByLangMap_t 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
;
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
];
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())
131 aShorter
= rOrigWord
;
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!" );
144 //! take care of #i22591#
147 DBG_ASSERT( aText
== rOrigWord
, "failed to " );
148 xRes
= new HyphenatedWord( aText
, nLang
, nHyphenationPos
,
149 aText
, nHyphenationPos
);
158 Reference
< XPossibleHyphens
> HyphenatorDispatcher::buildPossHyphens(
159 const Reference
< XDictionaryEntry
> &xEntry
, sal_Int16 nLanguage
)
161 MutexGuard
aGuard( GetLinguMutex() );
163 Reference
<XPossibleHyphens
> xRes
;
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
];
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)
204 DBG_ASSERT( nHyphCount
>= 0, "lng : invalid hyphenation count");
208 aHyphPos
.realloc( nHyphCount
);
209 xRes
= new PossibleHyphens( aTmp
.makeStringAndClear(), nLanguage
,
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();
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
)
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
))
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
);
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
);
304 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
305 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
306 "lng : index out of range");
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
,
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() );
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" );
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
,
356 pEntry
->nLastTriedSvcIndex
= (sal_Int16
) 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() );
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
)
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))
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
);
432 //! alternative spellings not yet supported by dictionaries
436 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
437 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
438 "lng : index out of range");
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
);
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() );
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" );
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
;
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() );
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())
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
;
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
);
557 xRes
= buildPossHyphens( xEntry
, nLanguage
);
561 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
562 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
563 "lng : index out of range");
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
,
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() );
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" );
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
,
613 pEntry
->nLastTriedSvcIndex
= (sal_Int16
) 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() );
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();
646 aSvcMap
.erase( nLanguage
);
650 LangSvcEntries_Hyph
*pEntry
= aSvcMap
[ nLanguage
].get();
654 pEntry
->aSvcImplNames
= rSvcImplNames
;
655 pEntry
->aSvcImplNames
.realloc(1);
656 pEntry
->aSvcRefs
= Sequence
< Reference
< XHyphenator
> > ( 1 );
660 boost::shared_ptr
< LangSvcEntries_Hyph
> pTmpEntry( new LangSvcEntries_Hyph( rSvcImplNames
[0] ) );
661 pTmpEntry
->aSvcRefs
= Sequence
< Reference
< XHyphenator
> >( 1 );
662 aSvcMap
[ nLanguage
] = pTmpEntry
;
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
;
682 aRes
= pEntry
->aSvcImplNames
;
683 if (aRes
.getLength() > 0)
691 LinguDispatcher::DspType
HyphenatorDispatcher::GetDspType() const
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */