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 <i18nlangtag/lang.h>
24 #include <i18nlangtag/languagetag.hxx>
25 #include <linguistic/misc.hxx>
26 #include <osl/mutex.hxx>
27 #include <osl/thread.h>
28 #include <sal/log.hxx>
29 #include <tools/debug.hxx>
30 #include <tools/stream.hxx>
31 #include <tools/urlobj.hxx>
32 #include <comphelper/processfactory.hxx>
33 #include <comphelper/string.hxx>
34 #include <comphelper/sequence.hxx>
35 #include <unotools/ucbstreamhelper.hxx>
37 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
38 #include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
39 #include <com/sun/star/io/TempFile.hpp>
40 #include <com/sun/star/io/XInputStream.hpp>
42 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
43 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
51 using namespace com::sun::star
;
52 using namespace com::sun::star::lang
;
53 using namespace com::sun::star::uno
;
54 using namespace com::sun::star::linguistic2
;
55 using namespace linguistic
;
59 #define VERS2_NOLANGUAGE 1024
61 #define MAX_HEADER_LENGTH 16
63 // XML-header to query SPELLML support
64 // to handle user words with "Grammar By" model words
65 constexpr OUStringLiteral SPELLML_SUPPORT
= u
"<?xml?>";
67 // User dictionaries can contain optional "title:" tags
68 // to support custom titles with space and other characters.
69 // (old mechanism stores the title of the user dictionary
70 // only in its file name, but special characters are
71 // problem for user dictionaries shipped with LibreOffice).
73 // The following fake file name extension will be
74 // added to the text of the title: field for correct
75 // text stripping and dictionary saving.
76 constexpr OUString EXTENSION_FOR_TITLE_TEXT
= u
"."_ustr
;
78 const char* const pVerStr2
= "WBSWG2";
79 const char* const pVerStr5
= "WBSWG5";
80 const char* const pVerStr6
= "WBSWG6";
81 const char* const pVerOOo7
= "OOoUserDict1";
83 const sal_Int16 DIC_VERSION_DONTKNOW
= -1;
84 const sal_Int16 DIC_VERSION_2
= 2;
85 const sal_Int16 DIC_VERSION_5
= 5;
86 const sal_Int16 DIC_VERSION_6
= 6;
87 const sal_Int16 DIC_VERSION_7
= 7;
89 static uno::Reference
< XLinguServiceManager2
> GetLngSvcMgr_Impl()
91 const uno::Reference
< XComponentContext
>& xContext( comphelper::getProcessComponentContext() );
92 uno::Reference
< XLinguServiceManager2
> xRes
= LinguServiceManager::create( xContext
) ;
96 static bool getTag(std::string_view rLine
, std::string_view rTagName
,
99 size_t nPos
= rLine
.find(rTagName
);
100 if (nPos
== std::string_view::npos
)
103 rTagValue
= OString(comphelper::string::strip(rLine
.substr(nPos
+ rTagName
.size()),
109 sal_Int16
ReadDicVersion( SvStream
& rStream
, LanguageType
&nLng
, bool &bNeg
, OUString
&aDicName
)
112 sal_Int16 nDicVersion
= DIC_VERSION_DONTKNOW
;
113 char pMagicHeader
[MAX_HEADER_LENGTH
];
115 nLng
= LANGUAGE_NONE
;
118 if (rStream
.GetError())
121 sal_uInt64
const nSniffPos
= rStream
.Tell();
122 static std::size_t nVerOOo7Len
= sal::static_int_cast
< std::size_t >(strlen( pVerOOo7
));
123 pMagicHeader
[ nVerOOo7Len
] = '\0';
124 if ((rStream
.ReadBytes(static_cast<void *>(pMagicHeader
), nVerOOo7Len
) == nVerOOo7Len
) &&
125 !strcmp(pMagicHeader
, pVerOOo7
))
130 nDicVersion
= DIC_VERSION_7
;
132 // 1st skip magic / header line
133 (void)rStream
.ReadLine(aLine
);
135 // 2nd line: language all | en-US | pt-BR ...
136 while ((bSuccess
= rStream
.ReadLine(aLine
)))
140 if (aLine
[0] == '#') // skip comments
144 if (getTag(aLine
, "lang: ", aTagValue
))
146 if (aTagValue
== "<none>")
147 nLng
= LANGUAGE_NONE
;
149 nLng
= LanguageTag::convertToLanguageType(
150 OStringToOUString( aTagValue
, RTL_TEXTENCODING_ASCII_US
));
153 // type: negative / positive
154 if (getTag(aLine
, "type: ", aTagValue
))
156 bNeg
= aTagValue
== "negative";
160 if (getTag(aLine
, "title: ", aTagValue
))
162 aDicName
= OStringToOUString( aTagValue
, RTL_TEXTENCODING_UTF8
) +
163 // recent title text preparation in GetDicInfoStr() waits for an
164 // extension, so we add it to avoid bad stripping at final dot
166 EXTENSION_FOR_TITLE_TEXT
;
169 if (std::string_view(aLine
).find("---") != std::string_view::npos
) // end of header
179 rStream
.Seek (nSniffPos
);
181 rStream
.ReadUInt16( nLen
);
182 if (nLen
>= MAX_HEADER_LENGTH
)
185 rStream
.ReadBytes(pMagicHeader
, nLen
);
186 pMagicHeader
[nLen
] = '\0';
188 // Check version magic
189 if (0 == strcmp( pMagicHeader
, pVerStr6
))
190 nDicVersion
= DIC_VERSION_6
;
191 else if (0 == strcmp( pMagicHeader
, pVerStr5
))
192 nDicVersion
= DIC_VERSION_5
;
193 else if (0 == strcmp( pMagicHeader
, pVerStr2
))
194 nDicVersion
= DIC_VERSION_2
;
196 nDicVersion
= DIC_VERSION_DONTKNOW
;
198 if (DIC_VERSION_2
== nDicVersion
||
199 DIC_VERSION_5
== nDicVersion
||
200 DIC_VERSION_6
== nDicVersion
)
202 // The language of the dictionary
204 rStream
.ReadUInt16( nTmp
);
205 nLng
= LanguageType(nTmp
);
206 if (VERS2_NOLANGUAGE
== static_cast<sal_uInt16
>(nLng
))
207 nLng
= LANGUAGE_NONE
;
210 rStream
.ReadCharAsBool( bNeg
);
217 DictionaryNeo::DictionaryNeo(OUString aName
,
218 LanguageType nLang
, DictionaryType eType
,
219 const OUString
&rMainURL
,
221 aDicEvtListeners( GetLinguMutex() ),
222 aDicName (std::move(aName
)),
227 nDicVersion
= DIC_VERSION_DONTKNOW
;
229 bIsModified
= bIsActive
= false;
230 bIsReadonly
= !bWriteable
;
232 if( !rMainURL
.isEmpty())
234 bool bExists
= FileExists( rMainURL
);
237 // save new dictionaries with in Format 7 (UTF8 plain text)
238 nDicVersion
= DIC_VERSION_7
;
240 //! create physical representation of an **empty** dictionary
241 //! that could be found by the dictionary-list implementation
242 // (Note: empty dictionaries are not just empty files!)
243 DBG_ASSERT( !bIsReadonly
,
244 "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
246 saveEntries( rMainURL
);
247 bNeedEntries
= false;
252 // non persistent dictionaries (like IgnoreAllList) should always be writable
254 bNeedEntries
= false;
258 DictionaryNeo::~DictionaryNeo()
262 ErrCode
DictionaryNeo::loadEntries(const OUString
&rMainURL
)
264 MutexGuard
aGuard( GetLinguMutex() );
266 // counter check that it is safe to set bIsModified to sal_False at
267 // the end of the function
268 DBG_ASSERT(!bIsModified
, "lng : dictionary already modified!");
270 // function should only be called once in order to load entries from file
271 bNeedEntries
= false;
273 if (rMainURL
.isEmpty())
276 const uno::Reference
< uno::XComponentContext
>& xContext( comphelper::getProcessComponentContext() );
278 // get XInputStream stream
279 uno::Reference
< io::XInputStream
> xStream
;
282 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess( ucb::SimpleFileAccess::create(xContext
) );
283 xStream
= xAccess
->openFileRead( rMainURL
);
285 catch (const uno::Exception
&)
287 SAL_WARN( "linguistic", "failed to get input stream" );
290 return ErrCode(sal_uInt32(-1));
292 std::unique_ptr
<SvStream
> pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
297 nDicVersion
= ReadDicVersion(*pStream
, nLang
, bNegativ
, aDicName
);
298 ErrCode nErr
= pStream
->GetError();
299 if (nErr
!= ERRCODE_NONE
)
304 eDicType
= bNegativ
? DictionaryType_NEGATIVE
: DictionaryType_POSITIVE
;
306 rtl_TextEncoding eEnc
= osl_getThreadTextEncoding();
307 if (nDicVersion
>= DIC_VERSION_6
)
308 eEnc
= RTL_TEXTENCODING_UTF8
;
311 if (DIC_VERSION_6
== nDicVersion
||
312 DIC_VERSION_5
== nDicVersion
||
313 DIC_VERSION_2
== nDicVersion
)
316 char aWordBuf
[ BUFSIZE
];
318 // Read the first word
321 pStream
->ReadUInt16( nLen
);
322 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
324 if ( nLen
< BUFSIZE
)
326 pStream
->ReadBytes(aWordBuf
, nLen
);
327 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
329 *(aWordBuf
+ nLen
) = 0;
332 return SVSTREAM_READ_ERROR
;
335 while(!pStream
->eof())
338 // Paste in dictionary without converting
341 OUString
aText(aWordBuf
, rtl_str_getLength(aWordBuf
), eEnc
);
342 uno::Reference
< XDictionaryEntry
> xEntry
=
343 new DicEntry( aText
, bNegativ
);
344 addEntry_Impl( xEntry
, true ); //! don't launch events here
347 pStream
->ReadUInt16( nLen
);
350 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
355 pStream
->ReadBytes(aWordBuf
, nLen
);
356 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
360 return SVSTREAM_READ_ERROR
;
361 *(aWordBuf
+ nLen
) = 0;
364 else if (DIC_VERSION_7
== nDicVersion
)
368 // remaining lines - stock strings (a [==] b)
369 while (pStream
->ReadLine(aLine
))
371 if (aLine
.isEmpty() || aLine
[0] == '#') // skip comments
373 OUString aText
= OStringToOUString(aLine
, RTL_TEXTENCODING_UTF8
);
374 uno::Reference
< XDictionaryEntry
> xEntry
=
375 new DicEntry( aText
, eDicType
== DictionaryType_NEGATIVE
);
376 addEntry_Impl( xEntry
, true ); //! don't launch events here
380 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
382 // since this routine should be called only initially (prior to any
383 // modification to be saved) we reset the bIsModified flag here that
384 // was implicitly set by addEntry_Impl
387 return pStream
->GetError();
390 static OString
formatForSave(const uno::Reference
< XDictionaryEntry
> &xEntry
,
391 rtl_TextEncoding eEnc
)
393 OUStringBuffer
aStr(xEntry
->getDictionaryWord());
395 if (xEntry
->isNegative() || !xEntry
->getReplacementText().isEmpty())
397 aStr
.append("==" + xEntry
->getReplacementText());
399 return OUStringToOString(aStr
, eEnc
);
402 ErrCode
DictionaryNeo::saveEntries(const OUString
&rURL
)
404 MutexGuard
aGuard( GetLinguMutex() );
408 DBG_ASSERT(!INetURLObject( rURL
).HasError(), "lng : invalid URL");
410 const uno::Reference
< uno::XComponentContext
>& xContext( comphelper::getProcessComponentContext() );
412 // get XOutputStream stream
413 uno::Reference
<io::XStream
> xStream
;
416 xStream
= io::TempFile::create(xContext
);
418 catch (const uno::Exception
&)
420 DBG_ASSERT( false, "failed to get input stream" );
423 return ErrCode(sal_uInt32(-1));
425 std::unique_ptr
<SvStream
> pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
427 // Always write as the latest version, i.e. DIC_VERSION_7
429 rtl_TextEncoding eEnc
= RTL_TEXTENCODING_UTF8
;
430 pStream
->WriteLine(pVerOOo7
);
431 ErrCode nErr
= pStream
->GetError();
432 if (nErr
!= ERRCODE_NONE
)
434 /* XXX: the <none> case could be differentiated, is it absence or
435 * undetermined or multiple? Earlier versions did not know about 'und' and
436 * 'mul' and 'zxx' codes. Sync with ReadDicVersion() */
437 if (LinguIsUnspecified(nLanguage
))
438 pStream
->WriteLine("lang: <none>");
441 OString aLine
= "lang: " + OUStringToOString(LanguageTag::convertToBcp47(nLanguage
), eEnc
);
442 pStream
->WriteLine(aLine
);
444 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
446 if (eDicType
== DictionaryType_POSITIVE
)
447 pStream
->WriteLine("type: positive");
449 pStream
->WriteLine("type: negative");
450 if (aDicName
.endsWith(EXTENSION_FOR_TITLE_TEXT
))
452 pStream
->WriteLine(Concat2View("title: " + OUStringToOString(
453 // strip EXTENSION_FOR_TITLE_TEXT
454 aDicName
.subView(0, aDicName
.lastIndexOf(EXTENSION_FOR_TITLE_TEXT
)), eEnc
)));
456 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
458 pStream
->WriteLine("---");
459 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
461 for (const Reference
<XDictionaryEntry
> & aEntrie
: aEntries
)
463 OString aOutStr
= formatForSave(aEntrie
, eEnc
);
464 pStream
->WriteLine (aOutStr
);
465 if (ERRCODE_NONE
!= (nErr
= pStream
->GetError()))
472 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess(ucb::SimpleFileAccess::create(xContext
));
473 Reference
<io::XInputStream
> xInputStream(xStream
, UNO_QUERY_THROW
);
474 uno::Reference
<io::XSeekable
> xSeek(xInputStream
, UNO_QUERY_THROW
);
476 xAccess
->writeFile(rURL
, xInputStream
);
477 //If we are migrating from an older version, then on first successful
478 //write, we're now converted to the latest version, i.e. DIC_VERSION_7
479 nDicVersion
= DIC_VERSION_7
;
481 catch (const uno::Exception
&)
483 DBG_ASSERT( false, "failed to write stream" );
484 return ErrCode(sal_uInt32(-1));
490 void DictionaryNeo::launchEvent(sal_Int16 nEvent
,
491 const uno::Reference
< XDictionaryEntry
>& xEntry
)
493 MutexGuard
aGuard( GetLinguMutex() );
495 DictionaryEvent aEvt
;
496 aEvt
.Source
= uno::Reference
< XDictionary
>( this );
497 aEvt
.nEvent
= nEvent
;
498 aEvt
.xDictionaryEntry
= xEntry
;
500 aDicEvtListeners
.notifyEach( &XDictionaryEventListener::processDictionaryEvent
, aEvt
);
503 int DictionaryNeo::cmpDicEntry(std::u16string_view rWord1
,
504 std::u16string_view rWord2
,
507 // returns 0 if rWord1 is equal to rWord2
508 // " a value < 0 if rWord1 is less than rWord2
509 // " a value > 0 if rWord1 is greater than rWord2
513 sal_Int32 nLen1
= rWord1
.size(),
514 nLen2
= rWord2
.size();
517 const sal_Unicode cChar
= '.';
518 if (nLen1
&& cChar
== rWord1
[ nLen1
- 1 ])
520 if (nLen2
&& cChar
== rWord2
[ nLen2
- 1 ])
524 const sal_Unicode cIgnChar
= '=';
525 const sal_Unicode cIgnBeg
= '['; // for alternative hyphenation, eg. Schif[f]fahrt, Zuc[1k]ker
526 const sal_Unicode cIgnEnd
= ']'; // planned: gee"[1-/e]rfde or ge[-/1e]e"rfde (gee"rfde -> ge=erfde)
534 sal_Unicode cChar1
= '\0';
535 sal_Unicode cChar2
= '\0';
538 // skip chars to be ignored
540 while (nIdx1
< nLen1
)
542 cChar1
= rWord1
[ nIdx1
];
543 if (cChar1
!= cIgnChar
&& cChar1
!= cIgnBeg
&& !IgnState
)
545 if ( cChar1
== cIgnBeg
)
547 else if (cChar1
== cIgnEnd
)
553 while (nIdx2
< nLen2
)
555 cChar2
= rWord2
[ nIdx2
];
556 if (cChar2
!= cIgnChar
&& cChar2
!= cIgnBeg
&& !IgnState
)
558 if ( cChar2
== cIgnBeg
)
560 else if (cChar2
== cIgnEnd
)
566 if (nIdx1
< nLen1
&& nIdx2
< nLen2
)
568 nDiff
= cChar1
- cChar2
;
574 } while (nIdx1
< nLen1
&& nIdx2
< nLen2
);
580 { // the string with the smallest count of not ignored chars is the
583 // count remaining IgnChars
585 while (nIdx1
< nLen1
)
587 if (rWord1
[ nIdx1
] == cIgnBeg
)
589 if (IgnState
|| rWord1
[ nIdx1
] == cIgnChar
)
591 if (rWord1
[ nIdx1
] == cIgnEnd
)
596 while (nIdx2
< nLen2
)
598 if (rWord2
[ nIdx2
] == cIgnBeg
)
600 if (IgnState
|| rWord2
[ nIdx2
] == cIgnChar
)
602 if (rWord2
[ nIdx2
] == cIgnEnd
)
607 nRes
= (nLen1
- nNumIgnChar1
) - (nLen2
- nNumIgnChar2
);
613 bool DictionaryNeo::seekEntry(std::u16string_view rWord
,
614 sal_Int32
*pPos
, bool bSimilarOnly
)
616 // look for entry with binary search.
617 // return sal_True if found sal_False else.
618 // if pPos != NULL it will become the position of the found entry, or
619 // if that was not found the position where it has to be inserted
620 // to keep the entries sorted
622 MutexGuard
aGuard( GetLinguMutex() );
624 sal_Int32 nUpperIdx
= getCount(),
630 while( nLowerIdx
<= nUpperIdx
)
632 nMidIdx
= (nLowerIdx
+ nUpperIdx
) / 2;
633 DBG_ASSERT(aEntries
[nMidIdx
].is(), "lng : empty entry encountered");
635 int nCmp
= - cmpDicEntry( aEntries
[nMidIdx
]->getDictionaryWord(),
636 rWord
, bSimilarOnly
);
639 if( pPos
) *pPos
= nMidIdx
;
643 nLowerIdx
= nMidIdx
+ 1;
644 else if( nMidIdx
== 0 )
646 if( pPos
) *pPos
= nLowerIdx
;
650 nUpperIdx
= nMidIdx
- 1;
653 if( pPos
) *pPos
= nLowerIdx
;
657 bool DictionaryNeo::isSorted()
661 sal_Int32 nEntries
= getCount();
663 for (i
= 1; i
< nEntries
; i
++)
665 if (cmpDicEntry( aEntries
[i
-1]->getDictionaryWord(),
666 aEntries
[i
]->getDictionaryWord() ) > 0)
675 bool DictionaryNeo::addEntry_Impl(const uno::Reference
< XDictionaryEntry
>& xDicEntry
,
678 MutexGuard
aGuard( GetLinguMutex() );
682 if ( bIsLoadEntries
|| (!bIsReadonly
&& xDicEntry
.is()) )
684 bool bIsNegEntry
= xDicEntry
->isNegative();
685 bool bAddEntry
= !isFull() &&
686 ( ( eDicType
== DictionaryType_POSITIVE
&& !bIsNegEntry
)
687 || ( eDicType
== DictionaryType_NEGATIVE
&& bIsNegEntry
)
688 || ( eDicType
== DictionaryType_MIXED
) );
690 // look for position to insert entry at
691 // if there is already an entry do not insert the new one
695 const bool bFound
= seekEntry( xDicEntry
->getDictionaryWord(), &nPos
);
702 DBG_ASSERT(!bNeedEntries
, "lng : entries still not loaded");
704 // insert new entry at specified position
705 aEntries
.insert(aEntries
.begin() + nPos
, xDicEntry
);
706 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary entries unsorted");
712 launchEvent( DictionaryEventFlags::ADD_ENTRY
, xDicEntry
);
716 // add word to the Hunspell dictionary using a sample word for affixation/compounding
717 if (xDicEntry
.is() && !xDicEntry
->isNegative() && !xDicEntry
->getReplacementText().isEmpty()) {
718 uno::Reference
< XLinguServiceManager2
> xLngSvcMgr( GetLngSvcMgr_Impl() );
719 uno::Reference
< XSpellChecker1
> xSpell
;
720 Reference
< XSpellAlternatives
> xTmpRes
;
721 xSpell
.set( xLngSvcMgr
->getSpellChecker(), UNO_QUERY
);
722 Sequence
< css::beans::PropertyValue
> aEmptySeq
;
723 if (xSpell
.is() && (xSpell
->isValid( SPELLML_SUPPORT
, static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
)))
725 // "Grammar By" sample word is a Hunspell dictionary word?
726 if (xSpell
->isValid( xDicEntry
->getReplacementText(), static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
))
728 xTmpRes
= xSpell
->spell( "<?xml?><query type='add'><word>" +
729 xDicEntry
->getDictionaryWord() + "</word><word>" + xDicEntry
->getReplacementText() +
730 "</word></query>", static_cast<sal_uInt16
>(nLanguage
), aEmptySeq
);
740 OUString SAL_CALL
DictionaryNeo::getName( )
742 MutexGuard
aGuard( GetLinguMutex() );
746 void SAL_CALL
DictionaryNeo::setName( const OUString
& aName
)
748 MutexGuard
aGuard( GetLinguMutex() );
750 if (aDicName
!= aName
)
753 launchEvent(DictionaryEventFlags::CHG_NAME
, nullptr);
757 DictionaryType SAL_CALL
DictionaryNeo::getDictionaryType( )
759 MutexGuard
aGuard( GetLinguMutex() );
764 void SAL_CALL
DictionaryNeo::setActive( sal_Bool bActivate
)
766 MutexGuard
aGuard( GetLinguMutex() );
768 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);
795 sal_Bool SAL_CALL
DictionaryNeo::isActive( )
797 MutexGuard
aGuard( GetLinguMutex() );
801 sal_Int32 SAL_CALL
DictionaryNeo::getCount( )
803 MutexGuard
aGuard( GetLinguMutex() );
806 loadEntries( aMainURL
);
807 return static_cast<sal_Int32
>(aEntries
.size());
810 Locale SAL_CALL
DictionaryNeo::getLocale( )
812 MutexGuard
aGuard( GetLinguMutex() );
813 return LanguageTag::convertToLocale( nLanguage
);
816 void SAL_CALL
DictionaryNeo::setLocale( const Locale
& aLocale
)
818 MutexGuard
aGuard( GetLinguMutex() );
819 LanguageType nLanguageP
= LinguLocaleToLanguage( aLocale
);
820 if (!bIsReadonly
&& nLanguage
!= nLanguageP
)
822 nLanguage
= nLanguageP
;
823 bIsModified
= true; // new language needs to be saved with dictionary
825 launchEvent( DictionaryEventFlags::CHG_LANGUAGE
, nullptr );
829 uno::Reference
< XDictionaryEntry
> SAL_CALL
DictionaryNeo::getEntry(
830 const OUString
& aWord
)
832 MutexGuard
aGuard( GetLinguMutex() );
835 loadEntries( aMainURL
);
838 bool bFound
= seekEntry( aWord
, &nPos
, true );
839 DBG_ASSERT(!bFound
|| nPos
< static_cast<sal_Int32
>(aEntries
.size()), "lng : index out of range");
841 return bFound
? aEntries
[ nPos
]
842 : uno::Reference
< XDictionaryEntry
>();
845 sal_Bool SAL_CALL
DictionaryNeo::addEntry(
846 const uno::Reference
< XDictionaryEntry
>& xDicEntry
)
848 MutexGuard
aGuard( GetLinguMutex() );
855 loadEntries( aMainURL
);
856 bRes
= addEntry_Impl( xDicEntry
);
863 DictionaryNeo::add( const OUString
& rWord
, sal_Bool bIsNegative
,
864 const OUString
& rRplcText
)
866 MutexGuard
aGuard( GetLinguMutex() );
872 uno::Reference
< XDictionaryEntry
> xEntry
=
873 new DicEntry( rWord
, bIsNegative
, rRplcText
);
874 bRes
= addEntry_Impl( xEntry
);
880 sal_Bool SAL_CALL
DictionaryNeo::remove( const OUString
& aWord
)
882 MutexGuard
aGuard( GetLinguMutex() );
884 bool bRemoved
= false;
889 loadEntries( aMainURL
);
892 bool bFound
= seekEntry( aWord
, &nPos
);
893 DBG_ASSERT(!bFound
|| nPos
< static_cast<sal_Int32
>(aEntries
.size()), "lng : index out of range");
895 // remove element if found
898 // entry to be removed
899 uno::Reference
< XDictionaryEntry
>
900 xDicEntry( aEntries
[ nPos
] );
901 DBG_ASSERT(xDicEntry
.is(), "lng : dictionary entry is NULL");
903 aEntries
.erase(aEntries
.begin() + nPos
);
905 bRemoved
= bIsModified
= true;
907 launchEvent( DictionaryEventFlags::DEL_ENTRY
, xDicEntry
);
914 sal_Bool SAL_CALL
DictionaryNeo::isFull( )
916 MutexGuard
aGuard( GetLinguMutex() );
919 loadEntries( aMainURL
);
920 return aEntries
.size() >= DIC_MAX_ENTRIES
;
923 uno::Sequence
< uno::Reference
< XDictionaryEntry
> >
924 SAL_CALL
DictionaryNeo::getEntries( )
926 MutexGuard
aGuard( GetLinguMutex() );
929 loadEntries( aMainURL
);
930 return comphelper::containerToSequence(aEntries
);
934 void SAL_CALL
DictionaryNeo::clear( )
936 MutexGuard
aGuard( GetLinguMutex() );
938 if (!bIsReadonly
&& !aEntries
.empty())
940 // release all references to old entries
943 bNeedEntries
= false;
946 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED
, nullptr );
950 sal_Bool SAL_CALL
DictionaryNeo::addDictionaryEventListener(
951 const uno::Reference
< XDictionaryEventListener
>& xListener
)
953 MutexGuard
aGuard( GetLinguMutex() );
958 sal_Int32 nLen
= aDicEvtListeners
.getLength();
959 bRes
= aDicEvtListeners
.addInterface( xListener
) != nLen
;
964 sal_Bool SAL_CALL
DictionaryNeo::removeDictionaryEventListener(
965 const uno::Reference
< XDictionaryEventListener
>& xListener
)
967 MutexGuard
aGuard( GetLinguMutex() );
972 sal_Int32 nLen
= aDicEvtListeners
.getLength();
973 bRes
= aDicEvtListeners
.removeInterface( xListener
) != nLen
;
979 sal_Bool SAL_CALL
DictionaryNeo::hasLocation()
981 MutexGuard
aGuard( GetLinguMutex() );
982 return !aMainURL
.isEmpty();
985 OUString SAL_CALL
DictionaryNeo::getLocation()
987 MutexGuard
aGuard( GetLinguMutex() );
991 sal_Bool SAL_CALL
DictionaryNeo::isReadonly()
993 MutexGuard
aGuard( GetLinguMutex() );
998 void SAL_CALL
DictionaryNeo::store()
1000 MutexGuard
aGuard( GetLinguMutex() );
1002 if (bIsModified
&& hasLocation() && !isReadonly())
1004 if (!saveEntries( aMainURL
))
1005 bIsModified
= false;
1009 void SAL_CALL
DictionaryNeo::storeAsURL(
1010 const OUString
& aURL
,
1011 const uno::Sequence
< beans::PropertyValue
>& /*rArgs*/ )
1013 MutexGuard
aGuard( GetLinguMutex() );
1015 if (!saveEntries( aURL
))
1018 bIsModified
= false;
1019 bIsReadonly
= IsReadOnly( getLocation() );
1023 void SAL_CALL
DictionaryNeo::storeToURL(
1024 const OUString
& aURL
,
1025 const uno::Sequence
< beans::PropertyValue
>& /*rArgs*/ )
1027 MutexGuard
aGuard( GetLinguMutex() );
1032 DicEntry::DicEntry(const OUString
&rDicFileWord
,
1033 bool bIsNegativWord
)
1035 if (!rDicFileWord
.isEmpty())
1036 splitDicFileWord( rDicFileWord
, aDicWord
, aReplacement
);
1037 bIsNegativ
= bIsNegativWord
;
1040 DicEntry::DicEntry(OUString aDicWord_
, bool bNegativ
,
1041 OUString aRplcText_
) :
1042 aDicWord (std::move(aDicWord_
)),
1043 aReplacement (std::move(aRplcText_
)),
1044 bIsNegativ (bNegativ
)
1048 DicEntry::~DicEntry()
1052 void DicEntry::splitDicFileWord(const OUString
&rDicFileWord
,
1054 OUString
&rReplacement
)
1056 sal_Int32 nDelimPos
= rDicFileWord
.indexOf( "==" );
1057 if (-1 != nDelimPos
)
1059 sal_Int32 nTriplePos
= nDelimPos
+ 2;
1060 if ( nTriplePos
< rDicFileWord
.getLength()
1061 && rDicFileWord
[ nTriplePos
] == '=' )
1063 rDicWord
= rDicFileWord
.copy( 0, nDelimPos
);
1064 rReplacement
= rDicFileWord
.copy( nDelimPos
+ 2 );
1068 rDicWord
= rDicFileWord
;
1069 rReplacement
.clear();
1073 OUString SAL_CALL
DicEntry::getDictionaryWord( )
1078 sal_Bool SAL_CALL
DicEntry::isNegative( )
1083 OUString SAL_CALL
DicEntry::getReplacementText( )
1085 return aReplacement
;
1089 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */