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 .
21 #include <cppuhelper/factory.hxx>
23 #include "hyphdsp.hxx"
24 #include <i18nlangtag/lang.h>
25 #include <i18nlangtag/languagetag.hxx>
26 #include <osl/mutex.hxx>
27 #include <sal/log.hxx>
28 #include <tools/debug.hxx>
29 #include <tools/stream.hxx>
30 #include <tools/urlobj.hxx>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/string.hxx>
33 #include <comphelper/sequence.hxx>
34 #include <unotools/ucbstreamhelper.hxx>
36 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
37 #include <com/sun/star/linguistic2/DictionaryType.hpp>
38 #include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
39 #include <com/sun/star/registry/XRegistryKey.hpp>
40 #include <com/sun/star/io/TempFile.hpp>
41 #include <com/sun/star/io/XInputStream.hpp>
42 #include <com/sun/star/io/XOutputStream.hpp>
44 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
45 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
54 using namespace com::sun::star
;
55 using namespace com::sun::star::lang
;
56 using namespace com::sun::star::uno
;
57 using namespace com::sun::star::linguistic2
;
58 using namespace linguistic
;
62 #define VERS2_NOLANGUAGE 1024
64 #define MAX_HEADER_LENGTH 16
66 // XML-header to query SPELLML support
67 // to handle user words with "Grammar By" model words
68 #define SPELLML_SUPPORT "<?xml?>"
70 // User dictionaries can contain optional "title:" tags
71 // to support custom titles with space and other characters.
72 // (old mechanism stores the title of the user dictionary
73 // only in its file name, but special characters are
74 // problem for user dictionaries shipped with LibreOffice).
76 // The following fake file name extension will be
77 // added to the text of the title: field for correct
78 // text stripping and dictionary saving.
79 #define EXTENSION_FOR_TITLE_TEXT "."
81 static const sal_Char
* const pVerStr2
= "WBSWG2";
82 static const sal_Char
* const pVerStr5
= "WBSWG5";
83 static const sal_Char
* const pVerStr6
= "WBSWG6";
84 static const sal_Char
* const pVerOOo7
= "OOoUserDict1";
86 static const sal_Int16 DIC_VERSION_DONTKNOW
= -1;
87 static const sal_Int16 DIC_VERSION_2
= 2;
88 static const sal_Int16 DIC_VERSION_5
= 5;
89 static const sal_Int16 DIC_VERSION_6
= 6;
90 static const sal_Int16 DIC_VERSION_7
= 7;
92 static uno::Reference
< XLinguServiceManager2
> GetLngSvcMgr_Impl()
94 uno::Reference
< XComponentContext
> xContext( comphelper::getProcessComponentContext() );
95 uno::Reference
< XLinguServiceManager2
> xRes
= LinguServiceManager::create( xContext
) ;
99 static bool getTag(const OString
&rLine
, const sal_Char
*pTagName
,
102 sal_Int32 nPos
= rLine
.indexOf(pTagName
);
106 rTagValue
= comphelper::string::strip(rLine
.copy(nPos
+ rtl_str_getLength(pTagName
)),
112 sal_Int16
ReadDicVersion( SvStreamPtr
const &rpStream
, LanguageType
&nLng
, bool &bNeg
, OUString
&aDicName
)
115 sal_Int16 nDicVersion
= DIC_VERSION_DONTKNOW
;
116 sal_Char pMagicHeader
[MAX_HEADER_LENGTH
];
118 nLng
= LANGUAGE_NONE
;
121 if (!rpStream
.get() || rpStream
->GetError())
124 sal_uInt64
const nSniffPos
= rpStream
->Tell();
125 static std::size_t nVerOOo7Len
= sal::static_int_cast
< std::size_t >(strlen( pVerOOo7
));
126 pMagicHeader
[ nVerOOo7Len
] = '\0';
127 if ((rpStream
->ReadBytes(static_cast<void *>(pMagicHeader
), nVerOOo7Len
) == nVerOOo7Len
) &&
128 !strcmp(pMagicHeader
, pVerOOo7
))
133 nDicVersion
= DIC_VERSION_7
;
135 // 1st skip magic / header line
136 rpStream
->ReadLine(aLine
);
138 // 2nd line: language all | en-US | pt-BR ...
139 while ((bSuccess
= rpStream
->ReadLine(aLine
)))
143 if (aLine
[0] == '#') // skip comments
147 if (getTag(aLine
, "lang: ", aTagValue
))
149 if (aTagValue
== "<none>")
150 nLng
= LANGUAGE_NONE
;
152 nLng
= LanguageTag::convertToLanguageType(
153 OStringToOUString( aTagValue
, RTL_TEXTENCODING_ASCII_US
));
156 // type: negative / positive
157 if (getTag(aLine
, "type: ", aTagValue
))
159 bNeg
= aTagValue
== "negative";
163 if (getTag(aLine
, "title: ", aTagValue
))
165 aDicName
= OStringToOUString( aTagValue
, RTL_TEXTENCODING_UTF8
) +
166 // recent title text preparation in GetDicInfoStr() waits for an
167 // extension, so we add it to avoid bad stripping at final dot
169 EXTENSION_FOR_TITLE_TEXT
;
172 if (aLine
.indexOf("---") != -1) // end of header
182 rpStream
->Seek (nSniffPos
);
184 rpStream
->ReadUInt16( nLen
);
185 if (nLen
>= MAX_HEADER_LENGTH
)
188 rpStream
->ReadBytes(pMagicHeader
, nLen
);
189 pMagicHeader
[nLen
] = '\0';
191 // Check version magic
192 if (0 == strcmp( pMagicHeader
, pVerStr6
))
193 nDicVersion
= DIC_VERSION_6
;
194 else if (0 == strcmp( pMagicHeader
, pVerStr5
))
195 nDicVersion
= DIC_VERSION_5
;
196 else if (0 == strcmp( pMagicHeader
, pVerStr2
))
197 nDicVersion
= DIC_VERSION_2
;
199 nDicVersion
= DIC_VERSION_DONTKNOW
;
201 if (DIC_VERSION_2
== nDicVersion
||
202 DIC_VERSION_5
== nDicVersion
||
203 DIC_VERSION_6
== nDicVersion
)
205 // The language of the dictionary
207 rpStream
->ReadUInt16( nTmp
);
208 nLng
= LanguageType(nTmp
);
209 if (VERS2_NOLANGUAGE
== static_cast<sal_uInt16
>(nLng
))
210 nLng
= LANGUAGE_NONE
;
213 rpStream
->ReadCharAsBool( bNeg
);
220 DictionaryNeo::DictionaryNeo(const OUString
&rName
,
221 LanguageType nLang
, DictionaryType eType
,
222 const OUString
&rMainURL
,
224 aDicEvtListeners( GetLinguMutex() ),
230 nDicVersion
= DIC_VERSION_DONTKNOW
;
232 bIsModified
= bIsActive
= false;
233 bIsReadonly
= !bWriteable
;
235 if( !rMainURL
.isEmpty())
237 bool bExists
= FileExists( rMainURL
);
240 // save new dictionaries with in Format 7 (UTF8 plain text)
241 nDicVersion
= DIC_VERSION_7
;
243 //! create physical representation of an **empty** dictionary
244 //! that could be found by the dictionary-list implementation
245 // (Note: empty dictionaries are not just empty files!)
246 DBG_ASSERT( !bIsReadonly
,
247 "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
249 saveEntries( rMainURL
);
250 bNeedEntries
= false;
255 // non persistent dictionaries (like IgnoreAllList) should always be writable
257 bNeedEntries
= false;
261 DictionaryNeo::~DictionaryNeo()
265 ErrCode
DictionaryNeo::loadEntries(const OUString
&rMainURL
)
267 MutexGuard
aGuard( GetLinguMutex() );
269 // counter check that it is safe to set bIsModified to sal_False at
270 // the end of the function
271 DBG_ASSERT(!bIsModified
, "lng : dictionary already modified!");
273 // function should only be called once in order to load entries from file
274 bNeedEntries
= false;
276 if (rMainURL
.isEmpty())
279 uno::Reference
< uno::XComponentContext
> xContext( comphelper::getProcessComponentContext() );
281 // get XInputStream stream
282 uno::Reference
< io::XInputStream
> xStream
;
285 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess( ucb::SimpleFileAccess::create(xContext
) );
286 xStream
= xAccess
->openFileRead( rMainURL
);
288 catch (const uno::Exception
&)
290 SAL_WARN( "linguistic", "failed to get input stream" );
293 return ErrCode(sal_uInt32(-1));
295 SvStreamPtr pStream
= SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream
) );
300 nDicVersion
= ReadDicVersion(pStream
, nLang
, bNegativ
, aDicName
);
301 ErrCode nErr
= pStream
->GetError();
302 if (nErr
!= ERRCODE_NONE
)
307 eDicType
= bNegativ
? DictionaryType_NEGATIVE
: DictionaryType_POSITIVE
;
309 rtl_TextEncoding eEnc
= osl_getThreadTextEncoding();
310 if (nDicVersion
>= DIC_VERSION_6
)
311 eEnc
= RTL_TEXTENCODING_UTF8
;
314 if (DIC_VERSION_6
== nDicVersion
||
315 DIC_VERSION_5
== nDicVersion
||
316 DIC_VERSION_2
== nDicVersion
)
319 sal_Char aWordBuf
[ BUFSIZE
];
321 // Read the first word
324 pStream
->ReadUInt16( nLen
);
325 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
327 if ( nLen
< BUFSIZE
)
329 pStream
->ReadBytes(aWordBuf
, nLen
);
330 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
332 *(aWordBuf
+ nLen
) = 0;
335 return SVSTREAM_READ_ERROR
;
338 while(!pStream
->eof())
341 // Paste in dictionary without converting
344 OUString
aText(aWordBuf
, rtl_str_getLength(aWordBuf
), eEnc
);
345 uno::Reference
< XDictionaryEntry
> xEntry
=
346 new DicEntry( aText
, bNegativ
);
347 addEntry_Impl( xEntry
, true ); //! don't launch events here
350 pStream
->ReadUInt16( nLen
);
353 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
358 pStream
->ReadBytes(aWordBuf
, nLen
);
359 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
363 return SVSTREAM_READ_ERROR
;
364 *(aWordBuf
+ nLen
) = 0;
367 else if (DIC_VERSION_7
== nDicVersion
)
371 // remaining lines - stock strings (a [==] b)
372 while (pStream
->ReadLine(aLine
))
374 if (aLine
.isEmpty() || aLine
[0] == '#') // skip comments
376 OUString aText
= OStringToOUString(aLine
, RTL_TEXTENCODING_UTF8
);
377 uno::Reference
< XDictionaryEntry
> xEntry
=
378 new DicEntry( aText
, eDicType
== DictionaryType_NEGATIVE
);
379 addEntry_Impl( xEntry
, true ); //! don't launch events here
383 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
385 // since this routine should be called only initially (prior to any
386 // modification to be saved) we reset the bIsModified flag here that
387 // was implicitly set by addEntry_Impl
390 return pStream
->GetError();
393 static OString
formatForSave(const uno::Reference
< XDictionaryEntry
> &xEntry
,
394 rtl_TextEncoding eEnc
)
396 OStringBuffer
aStr(OUStringToOString(xEntry
->getDictionaryWord(), eEnc
));
398 if (xEntry
->isNegative() || !xEntry
->getReplacementText().isEmpty())
401 aStr
.append(OUStringToOString(xEntry
->getReplacementText(), eEnc
));
403 return aStr
.makeStringAndClear();
406 ErrCode
DictionaryNeo::saveEntries(const OUString
&rURL
)
408 MutexGuard
aGuard( GetLinguMutex() );
412 DBG_ASSERT(!INetURLObject( rURL
).HasError(), "lng : invalid URL");
414 uno::Reference
< uno::XComponentContext
> xContext( comphelper::getProcessComponentContext() );
416 // get XOutputStream stream
417 uno::Reference
<io::XStream
> xStream
;
420 xStream
= io::TempFile::create(xContext
);
422 catch (const uno::Exception
&)
424 DBG_ASSERT( false, "failed to get input stream" );
427 return ErrCode(sal_uInt32(-1));
429 SvStreamPtr pStream
= SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream
) );
431 // Always write as the latest version, i.e. DIC_VERSION_7
433 rtl_TextEncoding eEnc
= RTL_TEXTENCODING_UTF8
;
434 pStream
->WriteLine(pVerOOo7
);
435 ErrCode nErr
= pStream
->GetError();
436 if (nErr
!= ERRCODE_NONE
)
438 /* XXX: the <none> case could be differentiated, is it absence or
439 * undetermined or multiple? Earlier versions did not know about 'und' and
440 * 'mul' and 'zxx' codes. Sync with ReadDicVersion() */
441 if (LinguIsUnspecified(nLanguage
))
442 pStream
->WriteLine("lang: <none>");
445 OStringBuffer
aLine("lang: ");
446 aLine
.append(OUStringToOString(LanguageTag::convertToBcp47(nLanguage
), eEnc
));
447 pStream
->WriteLine(aLine
.makeStringAndClear());
449 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
451 if (eDicType
== DictionaryType_POSITIVE
)
452 pStream
->WriteLine("type: positive");
454 pStream
->WriteLine("type: negative");
455 if (aDicName
.endsWith(EXTENSION_FOR_TITLE_TEXT
))
457 pStream
->WriteLine(OUStringToOString("title: " +
458 // strip EXTENSION_FOR_TITLE_TEXT
459 aDicName
.copy(0, aDicName
.lastIndexOf(EXTENSION_FOR_TITLE_TEXT
)), eEnc
));
461 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
463 pStream
->WriteLine("---");
464 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
466 for (Reference
<XDictionaryEntry
> & aEntrie
: aEntries
)
468 OString aOutStr
= formatForSave(aEntrie
, eEnc
);
469 pStream
->WriteLine (aOutStr
);
470 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
477 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess(ucb::SimpleFileAccess::create(xContext
));
478 Reference
<io::XInputStream
> xInputStream(xStream
, UNO_QUERY_THROW
);
479 uno::Reference
<io::XSeekable
> xSeek(xInputStream
, UNO_QUERY_THROW
);
481 xAccess
->writeFile(rURL
, xInputStream
);
482 //If we are migrating from an older version, then on first successful
483 //write, we're now converted to the latest version, i.e. DIC_VERSION_7
484 nDicVersion
= DIC_VERSION_7
;
486 catch (const uno::Exception
&)
488 DBG_ASSERT( false, "failed to write stream" );
489 return ErrCode(sal_uInt32(-1));
495 void DictionaryNeo::launchEvent(sal_Int16 nEvent
,
496 const uno::Reference
< XDictionaryEntry
>& xEntry
)
498 MutexGuard
aGuard( GetLinguMutex() );
500 DictionaryEvent aEvt
;
501 aEvt
.Source
= uno::Reference
< XDictionary
>( this );
502 aEvt
.nEvent
= nEvent
;
503 aEvt
.xDictionaryEntry
= xEntry
;
505 aDicEvtListeners
.notifyEach( &XDictionaryEventListener::processDictionaryEvent
, aEvt
);
508 int DictionaryNeo::cmpDicEntry(const OUString
& rWord1
,
509 const OUString
&rWord2
,
512 MutexGuard
aGuard( GetLinguMutex() );
514 // returns 0 if rWord1 is equal to rWord2
515 // " a value < 0 if rWord1 is less than rWord2
516 // " a value > 0 if rWord1 is greater than rWord2
520 sal_Int32 nLen1
= rWord1
.getLength(),
521 nLen2
= rWord2
.getLength();
524 const sal_Unicode cChar
= '.';
525 if (nLen1
&& cChar
== rWord1
[ nLen1
- 1 ])
527 if (nLen2
&& cChar
== rWord2
[ nLen2
- 1 ])
531 const sal_Unicode cIgnChar
= '=';
532 const sal_Unicode cIgnBeg
= '['; // for alternative hyphenation, eg. Schif[f]fahrt, Zuc[1k]ker
533 const sal_Unicode cIgnEnd
= ']'; // planned: gee"[1-/e]rfde or ge[-/1e]e"rfde (gee"rfde -> ge=erfde)
541 sal_Unicode cChar1
= '\0';
542 sal_Unicode cChar2
= '\0';
545 // skip chars to be ignored
547 while (nIdx1
< nLen1
&& ((cChar1
= rWord1
[ nIdx1
]) == cIgnChar
|| cChar1
== cIgnBeg
|| IgnState
))
549 if ( cChar1
== cIgnBeg
)
551 else if (cChar1
== cIgnEnd
)
557 while (nIdx2
< nLen2
&& ((cChar2
= rWord2
[ nIdx2
]) == cIgnChar
|| cChar2
== cIgnBeg
|| IgnState
))
559 if ( cChar2
== cIgnBeg
)
561 else if (cChar2
== cIgnEnd
)
567 if (nIdx1
< nLen1
&& nIdx2
< nLen2
)
569 nDiff
= cChar1
- cChar2
;
575 } while (nIdx1
< nLen1
&& nIdx2
< nLen2
);
581 { // the string with the smallest count of not ignored chars is the
584 // count remaining IgnChars
586 while (nIdx1
< nLen1
)
588 if (rWord1
[ nIdx1
] == cIgnBeg
)
590 if (IgnState
|| rWord1
[ nIdx1
] == cIgnChar
)
592 if (rWord1
[ nIdx1
] == cIgnEnd
)
597 while (nIdx2
< nLen2
)
599 if (rWord2
[ nIdx2
] == cIgnBeg
)
601 if (IgnState
|| rWord2
[ nIdx2
] == cIgnChar
)
603 if (rWord2
[ nIdx2
] == cIgnEnd
)
608 nRes
= (nLen1
- nNumIgnChar1
) - (nLen2
- nNumIgnChar2
);
614 bool DictionaryNeo::seekEntry(const OUString
&rWord
,
615 sal_Int32
*pPos
, bool bSimilarOnly
)
617 // look for entry with binary search.
618 // return sal_True if found sal_False else.
619 // if pPos != NULL it will become the position of the found entry, or
620 // if that was not found the position where it has to be inserted
621 // to keep the entries sorted
623 MutexGuard
aGuard( GetLinguMutex() );
625 sal_Int32 nUpperIdx
= getCount(),
631 while( nLowerIdx
<= nUpperIdx
)
633 nMidIdx
= (nLowerIdx
+ nUpperIdx
) / 2;
634 DBG_ASSERT(aEntries
[nMidIdx
].is(), "lng : empty entry encountered");
636 int nCmp
= - cmpDicEntry( aEntries
[nMidIdx
]->getDictionaryWord(),
637 rWord
, bSimilarOnly
);
640 if( pPos
) *pPos
= nMidIdx
;
644 nLowerIdx
= nMidIdx
+ 1;
645 else if( nMidIdx
== 0 )
647 if( pPos
) *pPos
= nLowerIdx
;
651 nUpperIdx
= nMidIdx
- 1;
654 if( pPos
) *pPos
= nLowerIdx
;
658 bool DictionaryNeo::isSorted()
662 sal_Int32 nEntries
= getCount();
664 for (i
= 1; i
< nEntries
; i
++)
666 if (cmpDicEntry( aEntries
[i
-1]->getDictionaryWord(),
667 aEntries
[i
]->getDictionaryWord() ) > 0)
676 bool DictionaryNeo::addEntry_Impl(const uno::Reference
< XDictionaryEntry
>& xDicEntry
,
679 MutexGuard
aGuard( GetLinguMutex() );
683 if ( bIsLoadEntries
|| (!bIsReadonly
&& xDicEntry
.is()) )
685 bool bIsNegEntry
= xDicEntry
->isNegative();
686 bool bAddEntry
= !isFull() &&
687 ( ( eDicType
== DictionaryType_POSITIVE
&& !bIsNegEntry
)
688 || ( eDicType
== DictionaryType_NEGATIVE
&& bIsNegEntry
)
689 || ( eDicType
== DictionaryType_MIXED
) );
691 // look for position to insert entry at
692 // if there is already an entry do not insert the new one
696 const bool bFound
= seekEntry( xDicEntry
->getDictionaryWord(), &nPos
);
703 DBG_ASSERT(!bNeedEntries
, "lng : entries still not loaded");
705 // insert new entry at specified position
706 aEntries
.insert(aEntries
.begin() + nPos
, xDicEntry
);
707 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary entries unsorted");
713 launchEvent( DictionaryEventFlags::ADD_ENTRY
, xDicEntry
);
717 // add word to the Hunspell dictionary using a sample word for affixation/compounding
718 if (xDicEntry
.is() && !xDicEntry
->isNegative() && !xDicEntry
->getReplacementText().isEmpty()) {
719 uno::Reference
< XLinguServiceManager2
> xLngSvcMgr( GetLngSvcMgr_Impl() );
720 uno::Reference
< XSpellChecker1
> xSpell
;
721 Reference
< XSpellAlternatives
> xTmpRes
;
722 xSpell
.set( xLngSvcMgr
->getSpellChecker(), UNO_QUERY
);
723 Sequence
< css::beans::PropertyValue
> aEmptySeq
;
724 if (xSpell
.is() && (xSpell
->isValid( SPELLML_SUPPORT
, static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
)))
726 // "Grammar By" sample word is a Hunspell dictionary word?
727 if (xSpell
->isValid( xDicEntry
->getReplacementText(), static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
))
729 xTmpRes
= xSpell
->spell( "<?xml?><query type='add'><word>" +
730 xDicEntry
->getDictionaryWord() + "</word><word>" + xDicEntry
->getReplacementText() +
731 "</word></query>", static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
);
741 OUString SAL_CALL
DictionaryNeo::getName( )
743 MutexGuard
aGuard( GetLinguMutex() );
747 void SAL_CALL
DictionaryNeo::setName( const OUString
& aName
)
749 MutexGuard
aGuard( GetLinguMutex() );
751 if (aDicName
!= aName
)
754 launchEvent(DictionaryEventFlags::CHG_NAME
, nullptr);
758 DictionaryType SAL_CALL
DictionaryNeo::getDictionaryType( )
760 MutexGuard
aGuard( GetLinguMutex() );
765 void SAL_CALL
DictionaryNeo::setActive( sal_Bool bActivate
)
767 MutexGuard
aGuard( GetLinguMutex() );
769 if (bIsActive
!= bool(bActivate
))
771 bIsActive
= bActivate
;
772 sal_Int16 nEvent
= bIsActive
?
773 DictionaryEventFlags::ACTIVATE_DIC
: DictionaryEventFlags::DEACTIVATE_DIC
;
775 // remove entries from memory if dictionary is deactivated
778 bool bIsEmpty
= aEntries
.empty();
780 // save entries first if necessary
781 if (bIsModified
&& hasLocation() && !isReadonly())
786 bNeedEntries
= !bIsEmpty
;
788 DBG_ASSERT( !bIsModified
|| !hasLocation() || isReadonly(),
789 "lng : dictionary is still modified" );
792 launchEvent(nEvent
, nullptr);
796 sal_Bool SAL_CALL
DictionaryNeo::isActive( )
798 MutexGuard
aGuard( GetLinguMutex() );
802 sal_Int32 SAL_CALL
DictionaryNeo::getCount( )
804 MutexGuard
aGuard( GetLinguMutex() );
807 loadEntries( aMainURL
);
808 return static_cast<sal_Int32
>(aEntries
.size());
811 Locale SAL_CALL
DictionaryNeo::getLocale( )
813 MutexGuard
aGuard( GetLinguMutex() );
814 return LanguageTag::convertToLocale( nLanguage
);
817 void SAL_CALL
DictionaryNeo::setLocale( const Locale
& aLocale
)
819 MutexGuard
aGuard( GetLinguMutex() );
820 LanguageType nLanguageP
= LinguLocaleToLanguage( aLocale
);
821 if (!bIsReadonly
&& nLanguage
!= nLanguageP
)
823 nLanguage
= nLanguageP
;
824 bIsModified
= true; // new language needs to be saved with dictionary
826 launchEvent( DictionaryEventFlags::CHG_LANGUAGE
, nullptr );
830 uno::Reference
< XDictionaryEntry
> SAL_CALL
DictionaryNeo::getEntry(
831 const OUString
& aWord
)
833 MutexGuard
aGuard( GetLinguMutex() );
836 loadEntries( aMainURL
);
839 bool bFound
= seekEntry( aWord
, &nPos
, true );
840 DBG_ASSERT(!bFound
|| nPos
< static_cast<sal_Int32
>(aEntries
.size()), "lng : index out of range");
842 return bFound
? aEntries
[ nPos
]
843 : uno::Reference
< XDictionaryEntry
>();
846 sal_Bool SAL_CALL
DictionaryNeo::addEntry(
847 const uno::Reference
< XDictionaryEntry
>& xDicEntry
)
849 MutexGuard
aGuard( GetLinguMutex() );
856 loadEntries( aMainURL
);
857 bRes
= addEntry_Impl( xDicEntry
);
864 DictionaryNeo::add( const OUString
& rWord
, sal_Bool bIsNegative
,
865 const OUString
& rRplcText
)
867 MutexGuard
aGuard( GetLinguMutex() );
873 uno::Reference
< XDictionaryEntry
> xEntry
=
874 new DicEntry( rWord
, bIsNegative
, rRplcText
);
875 bRes
= addEntry_Impl( xEntry
);
881 sal_Bool SAL_CALL
DictionaryNeo::remove( const OUString
& aWord
)
883 MutexGuard
aGuard( GetLinguMutex() );
885 bool bRemoved
= false;
890 loadEntries( aMainURL
);
893 bool bFound
= seekEntry( aWord
, &nPos
);
894 DBG_ASSERT(!bFound
|| nPos
< static_cast<sal_Int32
>(aEntries
.size()), "lng : index out of range");
896 // remove element if found
899 // entry to be removed
900 uno::Reference
< XDictionaryEntry
>
901 xDicEntry( aEntries
[ nPos
] );
902 DBG_ASSERT(xDicEntry
.is(), "lng : dictionary entry is NULL");
904 aEntries
.erase(aEntries
.begin() + nPos
);
906 bRemoved
= bIsModified
= true;
908 launchEvent( DictionaryEventFlags::DEL_ENTRY
, xDicEntry
);
915 sal_Bool SAL_CALL
DictionaryNeo::isFull( )
917 MutexGuard
aGuard( GetLinguMutex() );
920 loadEntries( aMainURL
);
921 return aEntries
.size() >= DIC_MAX_ENTRIES
;
924 uno::Sequence
< uno::Reference
< XDictionaryEntry
> >
925 SAL_CALL
DictionaryNeo::getEntries( )
927 MutexGuard
aGuard( GetLinguMutex() );
930 loadEntries( aMainURL
);
931 return comphelper::containerToSequence(aEntries
);
935 void SAL_CALL
DictionaryNeo::clear( )
937 MutexGuard
aGuard( GetLinguMutex() );
939 if (!bIsReadonly
&& !aEntries
.empty())
941 // release all references to old entries
944 bNeedEntries
= false;
947 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED
, nullptr );
951 sal_Bool SAL_CALL
DictionaryNeo::addDictionaryEventListener(
952 const uno::Reference
< XDictionaryEventListener
>& xListener
)
954 MutexGuard
aGuard( GetLinguMutex() );
959 sal_Int32 nLen
= aDicEvtListeners
.getLength();
960 bRes
= aDicEvtListeners
.addInterface( xListener
) != nLen
;
965 sal_Bool SAL_CALL
DictionaryNeo::removeDictionaryEventListener(
966 const uno::Reference
< XDictionaryEventListener
>& xListener
)
968 MutexGuard
aGuard( GetLinguMutex() );
973 sal_Int32 nLen
= aDicEvtListeners
.getLength();
974 bRes
= aDicEvtListeners
.removeInterface( xListener
) != nLen
;
980 sal_Bool SAL_CALL
DictionaryNeo::hasLocation()
982 MutexGuard
aGuard( GetLinguMutex() );
983 return !aMainURL
.isEmpty();
986 OUString SAL_CALL
DictionaryNeo::getLocation()
988 MutexGuard
aGuard( GetLinguMutex() );
992 sal_Bool SAL_CALL
DictionaryNeo::isReadonly()
994 MutexGuard
aGuard( GetLinguMutex() );
999 void SAL_CALL
DictionaryNeo::store()
1001 MutexGuard
aGuard( GetLinguMutex() );
1003 if (bIsModified
&& hasLocation() && !isReadonly())
1005 if (!saveEntries( aMainURL
))
1006 bIsModified
= false;
1010 void SAL_CALL
DictionaryNeo::storeAsURL(
1011 const OUString
& aURL
,
1012 const uno::Sequence
< beans::PropertyValue
>& /*rArgs*/ )
1014 MutexGuard
aGuard( GetLinguMutex() );
1016 if (!saveEntries( aURL
))
1019 bIsModified
= false;
1020 bIsReadonly
= IsReadOnly( getLocation() );
1024 void SAL_CALL
DictionaryNeo::storeToURL(
1025 const OUString
& aURL
,
1026 const uno::Sequence
< beans::PropertyValue
>& /*rArgs*/ )
1028 MutexGuard
aGuard( GetLinguMutex() );
1033 DicEntry::DicEntry(const OUString
&rDicFileWord
,
1034 bool bIsNegativWord
)
1036 if (!rDicFileWord
.isEmpty())
1037 splitDicFileWord( rDicFileWord
, aDicWord
, aReplacement
);
1038 bIsNegativ
= bIsNegativWord
;
1041 DicEntry::DicEntry(const OUString
&rDicWord
, bool bNegativ
,
1042 const OUString
&rRplcText
) :
1043 aDicWord (rDicWord
),
1044 aReplacement (rRplcText
),
1045 bIsNegativ (bNegativ
)
1049 DicEntry::~DicEntry()
1053 void DicEntry::splitDicFileWord(const OUString
&rDicFileWord
,
1055 OUString
&rReplacement
)
1057 MutexGuard
aGuard( GetLinguMutex() );
1059 sal_Int32 nDelimPos
= rDicFileWord
.indexOf( "==" );
1060 if (-1 != nDelimPos
)
1062 sal_Int32 nTriplePos
= nDelimPos
+ 2;
1063 if ( nTriplePos
< rDicFileWord
.getLength()
1064 && rDicFileWord
[ nTriplePos
] == '=' )
1066 rDicWord
= rDicFileWord
.copy( 0, nDelimPos
);
1067 rReplacement
= rDicFileWord
.copy( nDelimPos
+ 2 );
1071 rDicWord
= rDicFileWord
;
1072 rReplacement
.clear();
1076 OUString SAL_CALL
DicEntry::getDictionaryWord( )
1078 MutexGuard
aGuard( GetLinguMutex() );
1082 sal_Bool SAL_CALL
DicEntry::isNegative( )
1084 MutexGuard
aGuard( GetLinguMutex() );
1088 OUString SAL_CALL
DicEntry::getReplacementText( )
1090 MutexGuard
aGuard( GetLinguMutex() );
1091 return aReplacement
;
1095 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */