Bump version to 6.4.7.2.M8
[LibreOffice.git] / linguistic / source / dicimp.cxx
blob328871f0bd7abd211afc2f898bd35f9cabff9c15
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 .
21 #include <cppuhelper/factory.hxx>
22 #include "dicimp.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>
45 #include "defs.hxx"
47 #include <algorithm>
50 using namespace utl;
51 using namespace osl;
52 using namespace com::sun::star;
53 using namespace com::sun::star::lang;
54 using namespace com::sun::star::uno;
55 using namespace com::sun::star::linguistic2;
56 using namespace linguistic;
59 #define BUFSIZE 4096
60 #define VERS2_NOLANGUAGE 1024
62 #define MAX_HEADER_LENGTH 16
64 // XML-header to query SPELLML support
65 // to handle user words with "Grammar By" model words
66 #define SPELLML_SUPPORT "<?xml?>"
68 // User dictionaries can contain optional "title:" tags
69 // to support custom titles with space and other characters.
70 // (old mechanism stores the title of the user dictionary
71 // only in its file name, but special characters are
72 // problem for user dictionaries shipped with LibreOffice).
74 // The following fake file name extension will be
75 // added to the text of the title: field for correct
76 // text stripping and dictionary saving.
77 #define EXTENSION_FOR_TITLE_TEXT "."
79 static const sal_Char* const pVerStr2 = "WBSWG2";
80 static const sal_Char* const pVerStr5 = "WBSWG5";
81 static const sal_Char* const pVerStr6 = "WBSWG6";
82 static const sal_Char* const pVerOOo7 = "OOoUserDict1";
84 static const sal_Int16 DIC_VERSION_DONTKNOW = -1;
85 static const sal_Int16 DIC_VERSION_2 = 2;
86 static const sal_Int16 DIC_VERSION_5 = 5;
87 static const sal_Int16 DIC_VERSION_6 = 6;
88 static const sal_Int16 DIC_VERSION_7 = 7;
90 static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl()
92 uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
93 uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create( xContext ) ;
94 return xRes;
97 static bool getTag(const OString &rLine, const sal_Char *pTagName,
98 OString &rTagValue)
100 sal_Int32 nPos = rLine.indexOf(pTagName);
101 if (nPos == -1)
102 return false;
104 rTagValue = comphelper::string::strip(rLine.copy(nPos + rtl_str_getLength(pTagName)),
105 ' ');
106 return true;
110 sal_Int16 ReadDicVersion( SvStreamPtr const &rpStream, LanguageType &nLng, bool &bNeg, OUString &aDicName )
112 // Sniff the header
113 sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
114 sal_Char pMagicHeader[MAX_HEADER_LENGTH];
116 nLng = LANGUAGE_NONE;
117 bNeg = false;
119 if (!rpStream.get() || rpStream->GetError())
120 return -1;
122 sal_uInt64 const nSniffPos = rpStream->Tell();
123 static std::size_t nVerOOo7Len = sal::static_int_cast< std::size_t >(strlen( pVerOOo7 ));
124 pMagicHeader[ nVerOOo7Len ] = '\0';
125 if ((rpStream->ReadBytes(static_cast<void *>(pMagicHeader), nVerOOo7Len) == nVerOOo7Len) &&
126 !strcmp(pMagicHeader, pVerOOo7))
128 bool bSuccess;
129 OString aLine;
131 nDicVersion = DIC_VERSION_7;
133 // 1st skip magic / header line
134 rpStream->ReadLine(aLine);
136 // 2nd line: language all | en-US | pt-BR ...
137 while ((bSuccess = rpStream->ReadLine(aLine)))
139 OString aTagValue;
141 if (aLine[0] == '#') // skip comments
142 continue;
144 // lang: field
145 if (getTag(aLine, "lang: ", aTagValue))
147 if (aTagValue == "<none>")
148 nLng = LANGUAGE_NONE;
149 else
150 nLng = LanguageTag::convertToLanguageType(
151 OStringToOUString( aTagValue, RTL_TEXTENCODING_ASCII_US));
154 // type: negative / positive
155 if (getTag(aLine, "type: ", aTagValue))
157 bNeg = aTagValue == "negative";
160 // lang: title
161 if (getTag(aLine, "title: ", aTagValue))
163 aDicName = OStringToOUString( aTagValue, RTL_TEXTENCODING_UTF8) +
164 // recent title text preparation in GetDicInfoStr() waits for an
165 // extension, so we add it to avoid bad stripping at final dot
166 // of the title text
167 EXTENSION_FOR_TITLE_TEXT;
170 if (aLine.indexOf("---") != -1) // end of header
171 break;
173 if (!bSuccess)
174 return -2;
176 else
178 sal_uInt16 nLen;
180 rpStream->Seek (nSniffPos );
182 rpStream->ReadUInt16( nLen );
183 if (nLen >= MAX_HEADER_LENGTH)
184 return -1;
186 rpStream->ReadBytes(pMagicHeader, nLen);
187 pMagicHeader[nLen] = '\0';
189 // Check version magic
190 if (0 == strcmp( pMagicHeader, pVerStr6 ))
191 nDicVersion = DIC_VERSION_6;
192 else if (0 == strcmp( pMagicHeader, pVerStr5 ))
193 nDicVersion = DIC_VERSION_5;
194 else if (0 == strcmp( pMagicHeader, pVerStr2 ))
195 nDicVersion = DIC_VERSION_2;
196 else
197 nDicVersion = DIC_VERSION_DONTKNOW;
199 if (DIC_VERSION_2 == nDicVersion ||
200 DIC_VERSION_5 == nDicVersion ||
201 DIC_VERSION_6 == nDicVersion)
203 // The language of the dictionary
204 sal_uInt16 nTmp = 0;
205 rpStream->ReadUInt16( nTmp );
206 nLng = LanguageType(nTmp);
207 if (VERS2_NOLANGUAGE == static_cast<sal_uInt16>(nLng))
208 nLng = LANGUAGE_NONE;
210 // Negative Flag
211 rpStream->ReadCharAsBool( bNeg );
215 return nDicVersion;
218 DictionaryNeo::DictionaryNeo(const OUString &rName,
219 LanguageType nLang, DictionaryType eType,
220 const OUString &rMainURL,
221 bool bWriteable) :
222 aDicEvtListeners( GetLinguMutex() ),
223 aDicName (rName),
224 aMainURL (rMainURL),
225 eDicType (eType),
226 nLanguage (nLang)
228 nDicVersion = DIC_VERSION_DONTKNOW;
229 bNeedEntries = true;
230 bIsModified = bIsActive = false;
231 bIsReadonly = !bWriteable;
233 if( !rMainURL.isEmpty())
235 bool bExists = FileExists( rMainURL );
236 if( !bExists )
238 // save new dictionaries with in Format 7 (UTF8 plain text)
239 nDicVersion = DIC_VERSION_7;
241 //! create physical representation of an **empty** dictionary
242 //! that could be found by the dictionary-list implementation
243 // (Note: empty dictionaries are not just empty files!)
244 DBG_ASSERT( !bIsReadonly,
245 "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
246 if (!bIsReadonly)
247 saveEntries( rMainURL );
248 bNeedEntries = false;
251 else
253 // non persistent dictionaries (like IgnoreAllList) should always be writable
254 bIsReadonly = false;
255 bNeedEntries = false;
259 DictionaryNeo::~DictionaryNeo()
263 ErrCode DictionaryNeo::loadEntries(const OUString &rMainURL)
265 MutexGuard aGuard( GetLinguMutex() );
267 // counter check that it is safe to set bIsModified to sal_False at
268 // the end of the function
269 DBG_ASSERT(!bIsModified, "lng : dictionary already modified!");
271 // function should only be called once in order to load entries from file
272 bNeedEntries = false;
274 if (rMainURL.isEmpty())
275 return ERRCODE_NONE;
277 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
279 // get XInputStream stream
280 uno::Reference< io::XInputStream > xStream;
283 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
284 xStream = xAccess->openFileRead( rMainURL );
286 catch (const uno::Exception &)
288 SAL_WARN( "linguistic", "failed to get input stream" );
290 if (!xStream.is())
291 return ErrCode(sal_uInt32(-1));
293 SvStreamPtr pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
295 // read header
296 bool bNegativ;
297 LanguageType nLang;
298 nDicVersion = ReadDicVersion(pStream, nLang, bNegativ, aDicName);
299 ErrCode nErr = pStream->GetError();
300 if (nErr != ERRCODE_NONE)
301 return nErr;
303 nLanguage = nLang;
305 eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
307 rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
308 if (nDicVersion >= DIC_VERSION_6)
309 eEnc = RTL_TEXTENCODING_UTF8;
310 aEntries.clear();
312 if (DIC_VERSION_6 == nDicVersion ||
313 DIC_VERSION_5 == nDicVersion ||
314 DIC_VERSION_2 == nDicVersion)
316 sal_uInt16 nLen = 0;
317 sal_Char aWordBuf[ BUFSIZE ];
319 // Read the first word
320 if (!pStream->eof())
322 pStream->ReadUInt16( nLen );
323 if (ERRCODE_NONE != (nErr = pStream->GetError()))
324 return nErr;
325 if ( nLen < BUFSIZE )
327 pStream->ReadBytes(aWordBuf, nLen);
328 if (ERRCODE_NONE != (nErr = pStream->GetError()))
329 return nErr;
330 *(aWordBuf + nLen) = 0;
332 else
333 return SVSTREAM_READ_ERROR;
336 while(!pStream->eof())
338 // Read from file
339 // Paste in dictionary without converting
340 if(*aWordBuf)
342 OUString aText(aWordBuf, rtl_str_getLength(aWordBuf), eEnc);
343 uno::Reference< XDictionaryEntry > xEntry =
344 new DicEntry( aText, bNegativ );
345 addEntry_Impl( xEntry, true ); //! don't launch events here
348 pStream->ReadUInt16( nLen );
349 if (pStream->eof())
350 break;
351 if (ERRCODE_NONE != (nErr = pStream->GetError()))
352 return nErr;
354 if (nLen < BUFSIZE)
356 pStream->ReadBytes(aWordBuf, nLen);
357 if (ERRCODE_NONE != (nErr = pStream->GetError()))
358 return nErr;
360 else
361 return SVSTREAM_READ_ERROR;
362 *(aWordBuf + nLen) = 0;
365 else if (DIC_VERSION_7 == nDicVersion)
367 OString aLine;
369 // remaining lines - stock strings (a [==] b)
370 while (pStream->ReadLine(aLine))
372 if (aLine.isEmpty() || aLine[0] == '#') // skip comments
373 continue;
374 OUString aText = OStringToOUString(aLine, RTL_TEXTENCODING_UTF8);
375 uno::Reference< XDictionaryEntry > xEntry =
376 new DicEntry( aText, eDicType == DictionaryType_NEGATIVE );
377 addEntry_Impl( xEntry, true ); //! don't launch events here
381 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
383 // since this routine should be called only initially (prior to any
384 // modification to be saved) we reset the bIsModified flag here that
385 // was implicitly set by addEntry_Impl
386 bIsModified = false;
388 return pStream->GetError();
391 static OString formatForSave(const uno::Reference< XDictionaryEntry > &xEntry,
392 rtl_TextEncoding eEnc )
394 OStringBuffer aStr(OUStringToOString(xEntry->getDictionaryWord(), eEnc));
396 if (xEntry->isNegative() || !xEntry->getReplacementText().isEmpty())
398 aStr.append("==");
399 aStr.append(OUStringToOString(xEntry->getReplacementText(), eEnc));
401 return aStr.makeStringAndClear();
404 ErrCode DictionaryNeo::saveEntries(const OUString &rURL)
406 MutexGuard aGuard( GetLinguMutex() );
408 if (rURL.isEmpty())
409 return ERRCODE_NONE;
410 DBG_ASSERT(!INetURLObject( rURL ).HasError(), "lng : invalid URL");
412 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
414 // get XOutputStream stream
415 uno::Reference<io::XStream> xStream;
418 xStream = io::TempFile::create(xContext);
420 catch (const uno::Exception &)
422 DBG_ASSERT( false, "failed to get input stream" );
424 if (!xStream.is())
425 return ErrCode(sal_uInt32(-1));
427 SvStreamPtr pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
429 // Always write as the latest version, i.e. DIC_VERSION_7
431 rtl_TextEncoding eEnc = RTL_TEXTENCODING_UTF8;
432 pStream->WriteLine(pVerOOo7);
433 ErrCode nErr = pStream->GetError();
434 if (nErr != ERRCODE_NONE)
435 return nErr;
436 /* XXX: the <none> case could be differentiated, is it absence or
437 * undetermined or multiple? Earlier versions did not know about 'und' and
438 * 'mul' and 'zxx' codes. Sync with ReadDicVersion() */
439 if (LinguIsUnspecified(nLanguage))
440 pStream->WriteLine("lang: <none>");
441 else
443 OStringBuffer aLine("lang: ");
444 aLine.append(OUStringToOString(LanguageTag::convertToBcp47(nLanguage), eEnc));
445 pStream->WriteLine(aLine.makeStringAndClear());
447 if (ERRCODE_NONE != (nErr = pStream->GetError()))
448 return nErr;
449 if (eDicType == DictionaryType_POSITIVE)
450 pStream->WriteLine("type: positive");
451 else
452 pStream->WriteLine("type: negative");
453 if (aDicName.endsWith(EXTENSION_FOR_TITLE_TEXT))
455 pStream->WriteLine(OUStringToOString("title: " +
456 // strip EXTENSION_FOR_TITLE_TEXT
457 aDicName.copy(0, aDicName.lastIndexOf(EXTENSION_FOR_TITLE_TEXT)), eEnc));
459 if (ERRCODE_NONE != (nErr = pStream->GetError()))
460 return nErr;
461 pStream->WriteLine("---");
462 if (ERRCODE_NONE != (nErr = pStream->GetError()))
463 return nErr;
464 for (const Reference<XDictionaryEntry> & aEntrie : aEntries)
466 OString aOutStr = formatForSave(aEntrie, eEnc);
467 pStream->WriteLine (aOutStr);
468 if (ERRCODE_NONE != (nErr = pStream->GetError()))
469 return nErr;
474 pStream.reset();
475 uno::Reference< ucb::XSimpleFileAccess3 > xAccess(ucb::SimpleFileAccess::create(xContext));
476 Reference<io::XInputStream> xInputStream(xStream, UNO_QUERY_THROW);
477 uno::Reference<io::XSeekable> xSeek(xInputStream, UNO_QUERY_THROW);
478 xSeek->seek(0);
479 xAccess->writeFile(rURL, xInputStream);
480 //If we are migrating from an older version, then on first successful
481 //write, we're now converted to the latest version, i.e. DIC_VERSION_7
482 nDicVersion = DIC_VERSION_7;
484 catch (const uno::Exception &)
486 DBG_ASSERT( false, "failed to write stream" );
487 return ErrCode(sal_uInt32(-1));
490 return nErr;
493 void DictionaryNeo::launchEvent(sal_Int16 nEvent,
494 const uno::Reference< XDictionaryEntry >& xEntry)
496 MutexGuard aGuard( GetLinguMutex() );
498 DictionaryEvent aEvt;
499 aEvt.Source = uno::Reference< XDictionary >( this );
500 aEvt.nEvent = nEvent;
501 aEvt.xDictionaryEntry = xEntry;
503 aDicEvtListeners.notifyEach( &XDictionaryEventListener::processDictionaryEvent, aEvt);
506 int DictionaryNeo::cmpDicEntry(const OUString& rWord1,
507 const OUString &rWord2,
508 bool bSimilarOnly)
510 MutexGuard aGuard( GetLinguMutex() );
512 // returns 0 if rWord1 is equal to rWord2
513 // " a value < 0 if rWord1 is less than rWord2
514 // " a value > 0 if rWord1 is greater than rWord2
516 int nRes = 0;
518 sal_Int32 nLen1 = rWord1.getLength(),
519 nLen2 = rWord2.getLength();
520 if (bSimilarOnly)
522 const sal_Unicode cChar = '.';
523 if (nLen1 && cChar == rWord1[ nLen1 - 1 ])
524 nLen1--;
525 if (nLen2 && cChar == rWord2[ nLen2 - 1 ])
526 nLen2--;
529 const sal_Unicode cIgnChar = '=';
530 const sal_Unicode cIgnBeg = '['; // for alternative hyphenation, eg. Schif[f]fahrt, Zuc[1k]ker
531 const sal_Unicode cIgnEnd = ']'; // planned: gee"[1-/e]rfde or ge[-/1e]e"rfde (gee"rfde -> ge=erfde)
532 sal_Int32 nIdx1 = 0,
533 nIdx2 = 0,
534 nNumIgnChar1 = 0,
535 nNumIgnChar2 = 0;
537 bool IgnState;
538 sal_Int32 nDiff = 0;
539 sal_Unicode cChar1 = '\0';
540 sal_Unicode cChar2 = '\0';
543 // skip chars to be ignored
544 IgnState = false;
545 while (nIdx1 < nLen1 && ((cChar1 = rWord1[ nIdx1 ]) == cIgnChar || cChar1 == cIgnBeg || IgnState ))
547 if ( cChar1 == cIgnBeg )
548 IgnState = true;
549 else if (cChar1 == cIgnEnd)
550 IgnState = false;
551 nIdx1++;
552 nNumIgnChar1++;
554 IgnState = false;
555 while (nIdx2 < nLen2 && ((cChar2 = rWord2[ nIdx2 ]) == cIgnChar || cChar2 == cIgnBeg || IgnState ))
557 if ( cChar2 == cIgnBeg )
558 IgnState = true;
559 else if (cChar2 == cIgnEnd)
560 IgnState = false;
561 nIdx2++;
562 nNumIgnChar2++;
565 if (nIdx1 < nLen1 && nIdx2 < nLen2)
567 nDiff = cChar1 - cChar2;
568 if (nDiff)
569 break;
570 nIdx1++;
571 nIdx2++;
573 } while (nIdx1 < nLen1 && nIdx2 < nLen2);
576 if (nDiff)
577 nRes = nDiff;
578 else
579 { // the string with the smallest count of not ignored chars is the
580 // shorter one
582 // count remaining IgnChars
583 IgnState = false;
584 while (nIdx1 < nLen1 )
586 if (rWord1[ nIdx1 ] == cIgnBeg)
587 IgnState = true;
588 if (IgnState || rWord1[ nIdx1 ] == cIgnChar)
589 nNumIgnChar1++;
590 if (rWord1[ nIdx1] == cIgnEnd)
591 IgnState = false;
592 nIdx1++;
594 IgnState = false;
595 while (nIdx2 < nLen2 )
597 if (rWord2[ nIdx2 ] == cIgnBeg)
598 IgnState = true;
599 if (IgnState || rWord2[ nIdx2 ] == cIgnChar)
600 nNumIgnChar2++;
601 if (rWord2[ nIdx2 ] == cIgnEnd)
602 IgnState = false;
603 nIdx2++;
606 nRes = (nLen1 - nNumIgnChar1) - (nLen2 - nNumIgnChar2);
609 return nRes;
612 bool DictionaryNeo::seekEntry(const OUString &rWord,
613 sal_Int32 *pPos, bool bSimilarOnly)
615 // look for entry with binary search.
616 // return sal_True if found sal_False else.
617 // if pPos != NULL it will become the position of the found entry, or
618 // if that was not found the position where it has to be inserted
619 // to keep the entries sorted
621 MutexGuard aGuard( GetLinguMutex() );
623 sal_Int32 nUpperIdx = getCount(),
624 nMidIdx,
625 nLowerIdx = 0;
626 if( nUpperIdx > 0 )
628 nUpperIdx--;
629 while( nLowerIdx <= nUpperIdx )
631 nMidIdx = (nLowerIdx + nUpperIdx) / 2;
632 DBG_ASSERT(aEntries[nMidIdx].is(), "lng : empty entry encountered");
634 int nCmp = - cmpDicEntry( aEntries[nMidIdx]->getDictionaryWord(),
635 rWord, bSimilarOnly );
636 if(nCmp == 0)
638 if( pPos ) *pPos = nMidIdx;
639 return true;
641 else if(nCmp > 0)
642 nLowerIdx = nMidIdx + 1;
643 else if( nMidIdx == 0 )
645 if( pPos ) *pPos = nLowerIdx;
646 return false;
648 else
649 nUpperIdx = nMidIdx - 1;
652 if( pPos ) *pPos = nLowerIdx;
653 return false;
656 bool DictionaryNeo::isSorted()
658 bool bRes = true;
660 sal_Int32 nEntries = getCount();
661 sal_Int32 i;
662 for (i = 1; i < nEntries; i++)
664 if (cmpDicEntry( aEntries[i-1]->getDictionaryWord(),
665 aEntries[i]->getDictionaryWord() ) > 0)
667 bRes = false;
668 break;
671 return bRes;
674 bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry >& xDicEntry,
675 bool bIsLoadEntries)
677 MutexGuard aGuard( GetLinguMutex() );
679 bool bRes = false;
681 if ( bIsLoadEntries || (!bIsReadonly && xDicEntry.is()) )
683 bool bIsNegEntry = xDicEntry->isNegative();
684 bool bAddEntry = !isFull() &&
685 ( ( eDicType == DictionaryType_POSITIVE && !bIsNegEntry )
686 || ( eDicType == DictionaryType_NEGATIVE && bIsNegEntry )
687 || ( eDicType == DictionaryType_MIXED ) );
689 // look for position to insert entry at
690 // if there is already an entry do not insert the new one
691 sal_Int32 nPos = 0;
692 if (bAddEntry)
694 const bool bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
695 if (bFound)
696 bAddEntry = false;
699 if (bAddEntry)
701 DBG_ASSERT(!bNeedEntries, "lng : entries still not loaded");
703 // insert new entry at specified position
704 aEntries.insert(aEntries.begin() + nPos, xDicEntry);
705 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary entries unsorted");
707 bIsModified = true;
708 bRes = true;
710 if (!bIsLoadEntries)
711 launchEvent( DictionaryEventFlags::ADD_ENTRY, xDicEntry );
715 // add word to the Hunspell dictionary using a sample word for affixation/compounding
716 if (xDicEntry.is() && !xDicEntry->isNegative() && !xDicEntry->getReplacementText().isEmpty()) {
717 uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() );
718 uno::Reference< XSpellChecker1 > xSpell;
719 Reference< XSpellAlternatives > xTmpRes;
720 xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY );
721 Sequence< css::beans::PropertyValue > aEmptySeq;
722 if (xSpell.is() && (xSpell->isValid( SPELLML_SUPPORT, static_cast<sal_uInt16>(nLanguage), aEmptySeq )))
724 // "Grammar By" sample word is a Hunspell dictionary word?
725 if (xSpell->isValid( xDicEntry->getReplacementText(), static_cast<sal_uInt16>(nLanguage), aEmptySeq ))
727 xTmpRes = xSpell->spell( "<?xml?><query type='add'><word>" +
728 xDicEntry->getDictionaryWord() + "</word><word>" + xDicEntry->getReplacementText() +
729 "</word></query>", static_cast<sal_uInt16>(nLanguage), aEmptySeq );
730 bRes = true;
731 } else
732 bRes = false;
736 return bRes;
739 OUString SAL_CALL DictionaryNeo::getName( )
741 MutexGuard aGuard( GetLinguMutex() );
742 return aDicName;
745 void SAL_CALL DictionaryNeo::setName( const OUString& aName )
747 MutexGuard aGuard( GetLinguMutex() );
749 if (aDicName != aName)
751 aDicName = aName;
752 launchEvent(DictionaryEventFlags::CHG_NAME, nullptr);
756 DictionaryType SAL_CALL DictionaryNeo::getDictionaryType( )
758 MutexGuard aGuard( GetLinguMutex() );
760 return eDicType;
763 void SAL_CALL DictionaryNeo::setActive( sal_Bool bActivate )
765 MutexGuard aGuard( GetLinguMutex() );
767 if (bIsActive != bool(bActivate))
769 bIsActive = bActivate;
770 sal_Int16 nEvent = bIsActive ?
771 DictionaryEventFlags::ACTIVATE_DIC : DictionaryEventFlags::DEACTIVATE_DIC;
773 // remove entries from memory if dictionary is deactivated
774 if (!bIsActive)
776 bool bIsEmpty = aEntries.empty();
778 // save entries first if necessary
779 if (bIsModified && hasLocation() && !isReadonly())
781 store();
783 aEntries.clear();
784 bNeedEntries = !bIsEmpty;
786 DBG_ASSERT( !bIsModified || !hasLocation() || isReadonly(),
787 "lng : dictionary is still modified" );
790 launchEvent(nEvent, nullptr);
794 sal_Bool SAL_CALL DictionaryNeo::isActive( )
796 MutexGuard aGuard( GetLinguMutex() );
797 return bIsActive;
800 sal_Int32 SAL_CALL DictionaryNeo::getCount( )
802 MutexGuard aGuard( GetLinguMutex() );
804 if (bNeedEntries)
805 loadEntries( aMainURL );
806 return static_cast<sal_Int32>(aEntries.size());
809 Locale SAL_CALL DictionaryNeo::getLocale( )
811 MutexGuard aGuard( GetLinguMutex() );
812 return LanguageTag::convertToLocale( nLanguage );
815 void SAL_CALL DictionaryNeo::setLocale( const Locale& aLocale )
817 MutexGuard aGuard( GetLinguMutex() );
818 LanguageType nLanguageP = LinguLocaleToLanguage( aLocale );
819 if (!bIsReadonly && nLanguage != nLanguageP)
821 nLanguage = nLanguageP;
822 bIsModified = true; // new language needs to be saved with dictionary
824 launchEvent( DictionaryEventFlags::CHG_LANGUAGE, nullptr );
828 uno::Reference< XDictionaryEntry > SAL_CALL DictionaryNeo::getEntry(
829 const OUString& aWord )
831 MutexGuard aGuard( GetLinguMutex() );
833 if (bNeedEntries)
834 loadEntries( aMainURL );
836 sal_Int32 nPos;
837 bool bFound = seekEntry( aWord, &nPos, true );
838 DBG_ASSERT(!bFound || nPos < static_cast<sal_Int32>(aEntries.size()), "lng : index out of range");
840 return bFound ? aEntries[ nPos ]
841 : uno::Reference< XDictionaryEntry >();
844 sal_Bool SAL_CALL DictionaryNeo::addEntry(
845 const uno::Reference< XDictionaryEntry >& xDicEntry )
847 MutexGuard aGuard( GetLinguMutex() );
849 bool bRes = false;
851 if (!bIsReadonly)
853 if (bNeedEntries)
854 loadEntries( aMainURL );
855 bRes = addEntry_Impl( xDicEntry );
858 return bRes;
861 sal_Bool SAL_CALL
862 DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
863 const OUString& rRplcText )
865 MutexGuard aGuard( GetLinguMutex() );
867 bool bRes = false;
869 if (!bIsReadonly)
871 uno::Reference< XDictionaryEntry > xEntry =
872 new DicEntry( rWord, bIsNegative, rRplcText );
873 bRes = addEntry_Impl( xEntry );
876 return bRes;
879 sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
881 MutexGuard aGuard( GetLinguMutex() );
883 bool bRemoved = false;
885 if (!bIsReadonly)
887 if (bNeedEntries)
888 loadEntries( aMainURL );
890 sal_Int32 nPos;
891 bool bFound = seekEntry( aWord, &nPos );
892 DBG_ASSERT(!bFound || nPos < static_cast<sal_Int32>(aEntries.size()), "lng : index out of range");
894 // remove element if found
895 if (bFound)
897 // entry to be removed
898 uno::Reference< XDictionaryEntry >
899 xDicEntry( aEntries[ nPos ] );
900 DBG_ASSERT(xDicEntry.is(), "lng : dictionary entry is NULL");
902 aEntries.erase(aEntries.begin() + nPos);
904 bRemoved = bIsModified = true;
906 launchEvent( DictionaryEventFlags::DEL_ENTRY, xDicEntry );
910 return bRemoved;
913 sal_Bool SAL_CALL DictionaryNeo::isFull( )
915 MutexGuard aGuard( GetLinguMutex() );
917 if (bNeedEntries)
918 loadEntries( aMainURL );
919 return aEntries.size() >= DIC_MAX_ENTRIES;
922 uno::Sequence< uno::Reference< XDictionaryEntry > >
923 SAL_CALL DictionaryNeo::getEntries( )
925 MutexGuard aGuard( GetLinguMutex() );
927 if (bNeedEntries)
928 loadEntries( aMainURL );
929 return comphelper::containerToSequence(aEntries);
933 void SAL_CALL DictionaryNeo::clear( )
935 MutexGuard aGuard( GetLinguMutex() );
937 if (!bIsReadonly && !aEntries.empty())
939 // release all references to old entries
940 aEntries.clear();
942 bNeedEntries = false;
943 bIsModified = true;
945 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , nullptr );
949 sal_Bool SAL_CALL DictionaryNeo::addDictionaryEventListener(
950 const uno::Reference< XDictionaryEventListener >& xListener )
952 MutexGuard aGuard( GetLinguMutex() );
954 bool bRes = false;
955 if (xListener.is())
957 sal_Int32 nLen = aDicEvtListeners.getLength();
958 bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
960 return bRes;
963 sal_Bool SAL_CALL DictionaryNeo::removeDictionaryEventListener(
964 const uno::Reference< XDictionaryEventListener >& xListener )
966 MutexGuard aGuard( GetLinguMutex() );
968 bool bRes = false;
969 if (xListener.is())
971 sal_Int32 nLen = aDicEvtListeners.getLength();
972 bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
974 return bRes;
978 sal_Bool SAL_CALL DictionaryNeo::hasLocation()
980 MutexGuard aGuard( GetLinguMutex() );
981 return !aMainURL.isEmpty();
984 OUString SAL_CALL DictionaryNeo::getLocation()
986 MutexGuard aGuard( GetLinguMutex() );
987 return aMainURL;
990 sal_Bool SAL_CALL DictionaryNeo::isReadonly()
992 MutexGuard aGuard( GetLinguMutex() );
994 return bIsReadonly;
997 void SAL_CALL DictionaryNeo::store()
999 MutexGuard aGuard( GetLinguMutex() );
1001 if (bIsModified && hasLocation() && !isReadonly())
1003 if (!saveEntries( aMainURL ))
1004 bIsModified = false;
1008 void SAL_CALL DictionaryNeo::storeAsURL(
1009 const OUString& aURL,
1010 const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1012 MutexGuard aGuard( GetLinguMutex() );
1014 if (!saveEntries( aURL ))
1016 aMainURL = aURL;
1017 bIsModified = false;
1018 bIsReadonly = IsReadOnly( getLocation() );
1022 void SAL_CALL DictionaryNeo::storeToURL(
1023 const OUString& aURL,
1024 const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1026 MutexGuard aGuard( GetLinguMutex() );
1027 saveEntries(aURL);
1031 DicEntry::DicEntry(const OUString &rDicFileWord,
1032 bool bIsNegativWord)
1034 if (!rDicFileWord.isEmpty())
1035 splitDicFileWord( rDicFileWord, aDicWord, aReplacement );
1036 bIsNegativ = bIsNegativWord;
1039 DicEntry::DicEntry(const OUString &rDicWord, bool bNegativ,
1040 const OUString &rRplcText) :
1041 aDicWord (rDicWord),
1042 aReplacement (rRplcText),
1043 bIsNegativ (bNegativ)
1047 DicEntry::~DicEntry()
1051 void DicEntry::splitDicFileWord(const OUString &rDicFileWord,
1052 OUString &rDicWord,
1053 OUString &rReplacement)
1055 MutexGuard aGuard( GetLinguMutex() );
1057 sal_Int32 nDelimPos = rDicFileWord.indexOf( "==" );
1058 if (-1 != nDelimPos)
1060 sal_Int32 nTriplePos = nDelimPos + 2;
1061 if ( nTriplePos < rDicFileWord.getLength()
1062 && rDicFileWord[ nTriplePos ] == '=' )
1063 ++nDelimPos;
1064 rDicWord = rDicFileWord.copy( 0, nDelimPos );
1065 rReplacement = rDicFileWord.copy( nDelimPos + 2 );
1067 else
1069 rDicWord = rDicFileWord;
1070 rReplacement.clear();
1074 OUString SAL_CALL DicEntry::getDictionaryWord( )
1076 MutexGuard aGuard( GetLinguMutex() );
1077 return aDicWord;
1080 sal_Bool SAL_CALL DicEntry::isNegative( )
1082 MutexGuard aGuard( GetLinguMutex() );
1083 return bIsNegativ;
1086 OUString SAL_CALL DicEntry::getReplacementText( )
1088 MutexGuard aGuard( GetLinguMutex() );
1089 return aReplacement;
1093 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */