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 <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"
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
) :
53 HyphenatorDispatcher::~HyphenatorDispatcher()
59 void HyphenatorDispatcher::ClearSvcList()
61 // release memory for each table entry
62 HyphSvcByLangMap_t 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
;
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
];
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())
129 aShorter
= rOrigWord
;
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!" );
142 //! take care of #i22591#
145 DBG_ASSERT( aText
== rOrigWord
, "failed to " );
146 xRes
= new HyphenatedWord( aText
, nLang
, nHyphenationPos
,
147 aText
, nHyphenationPos
);
156 Reference
< XPossibleHyphens
> HyphenatorDispatcher::buildPossHyphens(
157 const Reference
< XDictionaryEntry
> &xEntry
, sal_Int16 nLanguage
)
159 MutexGuard
aGuard( GetLinguMutex() );
161 Reference
<XPossibleHyphens
> xRes
;
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
];
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)
202 DBG_ASSERT( nHyphCount
>= 0, "lng : invalid hyphenation count");
206 aHyphPos
.realloc( nHyphCount
);
207 xRes
= new PossibleHyphens( aTmp
.makeStringAndClear(), nLanguage
,
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();
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
)
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
))
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
);
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
);
302 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
303 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
304 "lng : index out of range");
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
,
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
),
339 catch (uno::Exception
&)
341 DBG_ASSERT( 0, "createInstanceWithArguments failed" );
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
,
354 pEntry
->nLastTriedSvcIndex
= (sal_Int16
) 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() );
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
)
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))
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
);
429 //! alternative spellings not yet supported by dictionaries
433 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
434 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
435 "lng : index out of range");
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
);
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" );
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
;
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() );
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())
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
;
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
);
552 xRes
= buildPossHyphens( xEntry
, nLanguage
);
556 sal_Int32 nLen
= pEntry
->aSvcImplNames
.getLength() > 0 ? 1 : 0;
557 DBG_ASSERT( pEntry
->nLastTriedSvcIndex
< nLen
,
558 "lng : index out of range");
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
,
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
),
593 catch (uno::Exception
&)
595 DBG_ASSERT( 0, "createWithArguments failed" );
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
;
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() );
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();
639 aSvcMap
.erase( nLanguage
);
643 LangSvcEntries_Hyph
*pEntry
= aSvcMap
[ nLanguage
].get();
647 pEntry
->aSvcImplNames
= rSvcImplNames
;
648 pEntry
->aSvcImplNames
.realloc(1);
649 pEntry
->aSvcRefs
= Sequence
< Reference
< XHyphenator
> > ( 1 );
653 boost::shared_ptr
< LangSvcEntries_Hyph
> pTmpEntry( new LangSvcEntries_Hyph( rSvcImplNames
[0] ) );
654 pTmpEntry
->aSvcRefs
= Sequence
< Reference
< XHyphenator
> >( 1 );
655 aSvcMap
[ nLanguage
] = pTmpEntry
;
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
;
675 aRes
= pEntry
->aSvcImplNames
;
676 if (aRes
.getLength() > 0)
684 LinguDispatcher::DspType
HyphenatorDispatcher::GetDspType() const
691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */