Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / linguistic / source / dicimp.cxx
blobddc5074f62c7666fba5e618ac4c60090f74551d1
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 "hyphdsp.hxx"
24 #include <i18nlangtag/lang.h>
25 #include <i18nlangtag/languagetag.hxx>
26 #include <osl/mutex.hxx>
27 #include <tools/debug.hxx>
28 #include <tools/stream.hxx>
29 #include <tools/urlobj.hxx>
30 #include <comphelper/processfactory.hxx>
31 #include <comphelper/string.hxx>
32 #include <comphelper/sequence.hxx>
33 #include <unotools/ucbstreamhelper.hxx>
35 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
36 #include <com/sun/star/linguistic2/DictionaryType.hpp>
37 #include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
38 #include <com/sun/star/registry/XRegistryKey.hpp>
39 #include <com/sun/star/io/TempFile.hpp>
40 #include <com/sun/star/io/XInputStream.hpp>
41 #include <com/sun/star/io/XOutputStream.hpp>
43 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
44 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
46 #include "defs.hxx"
48 #include <algorithm>
51 using namespace utl;
52 using namespace osl;
53 using namespace com::sun::star;
54 using namespace com::sun::star::lang;
55 using namespace com::sun::star::uno;
56 using namespace com::sun::star::linguistic2;
57 using namespace linguistic;
60 #define BUFSIZE 4096
61 #define VERS2_NOLANGUAGE 1024
63 #define MAX_HEADER_LENGTH 16
65 // XML-header to query SPELLML support
66 // to handle user words with "Grammar By" model words
67 #define SPELLML_SUPPORT "<?xml?>"
69 // User dictionaries can contain optional "title:" tags
70 // to support custom titles with space and other characters.
71 // (old mechanism stores the title of the user dictionary
72 // only in its file name, but special characters are
73 // problem for user dictionaries shipped with LibreOffice).
75 // The following fake file name extension will be
76 // added to the text of the title: field for correct
77 // text stripping and dictionary saving.
78 #define EXTENSION_FOR_TITLE_TEXT "."
80 static const sal_Char* const pVerStr2 = "WBSWG2";
81 static const sal_Char* const pVerStr5 = "WBSWG5";
82 static const sal_Char* const pVerStr6 = "WBSWG6";
83 static const sal_Char* const pVerOOo7 = "OOoUserDict1";
85 static const sal_Int16 DIC_VERSION_DONTKNOW = -1;
86 static const sal_Int16 DIC_VERSION_2 = 2;
87 static const sal_Int16 DIC_VERSION_5 = 5;
88 static const sal_Int16 DIC_VERSION_6 = 6;
89 static const sal_Int16 DIC_VERSION_7 = 7;
91 static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl()
93 uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
94 uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create( xContext ) ;
95 return xRes;
98 static bool getTag(const OString &rLine, const sal_Char *pTagName,
99 OString &rTagValue)
101 sal_Int32 nPos = rLine.indexOf(pTagName);
102 if (nPos == -1)
103 return false;
105 rTagValue = comphelper::string::strip(rLine.copy(nPos + rtl_str_getLength(pTagName)),
106 ' ');
107 return true;
111 sal_Int16 ReadDicVersion( SvStreamPtr const &rpStream, LanguageType &nLng, bool &bNeg, OUString &aDicName )
113 // Sniff the header
114 sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
115 sal_Char pMagicHeader[MAX_HEADER_LENGTH];
117 nLng = LANGUAGE_NONE;
118 bNeg = false;
120 if (!rpStream.get() || rpStream->GetError())
121 return -1;
123 sal_uInt64 const nSniffPos = rpStream->Tell();
124 static std::size_t nVerOOo7Len = sal::static_int_cast< std::size_t >(strlen( pVerOOo7 ));
125 pMagicHeader[ nVerOOo7Len ] = '\0';
126 if ((rpStream->ReadBytes(static_cast<void *>(pMagicHeader), nVerOOo7Len) == nVerOOo7Len) &&
127 !strcmp(pMagicHeader, pVerOOo7))
129 bool bSuccess;
130 OString aLine;
132 nDicVersion = DIC_VERSION_7;
134 // 1st skip magic / header line
135 rpStream->ReadLine(aLine);
137 // 2nd line: language all | en-US | pt-BR ...
138 while ((bSuccess = rpStream->ReadLine(aLine)))
140 OString aTagValue;
142 if (aLine[0] == '#') // skip comments
143 continue;
145 // lang: field
146 if (getTag(aLine, "lang: ", aTagValue))
148 if (aTagValue == "<none>")
149 nLng = LANGUAGE_NONE;
150 else
151 nLng = LanguageTag::convertToLanguageType(
152 OStringToOUString( aTagValue, RTL_TEXTENCODING_ASCII_US));
155 // type: negative / positive
156 if (getTag(aLine, "type: ", aTagValue))
158 bNeg = aTagValue == "negative";
161 // lang: title
162 if (getTag(aLine, "title: ", aTagValue))
164 aDicName = OStringToOUString( aTagValue, RTL_TEXTENCODING_UTF8) +
165 // recent title text preparation in GetDicInfoStr() waits for an
166 // extension, so we add it to avoid bad stripping at final dot
167 // of the title text
168 EXTENSION_FOR_TITLE_TEXT;
171 if (aLine.indexOf("---") != -1) // end of header
172 break;
174 if (!bSuccess)
175 return -2;
177 else
179 sal_uInt16 nLen;
181 rpStream->Seek (nSniffPos );
183 rpStream->ReadUInt16( nLen );
184 if (nLen >= MAX_HEADER_LENGTH)
185 return -1;
187 rpStream->ReadBytes(pMagicHeader, nLen);
188 pMagicHeader[nLen] = '\0';
190 // Check version magic
191 if (0 == strcmp( pMagicHeader, pVerStr6 ))
192 nDicVersion = DIC_VERSION_6;
193 else if (0 == strcmp( pMagicHeader, pVerStr5 ))
194 nDicVersion = DIC_VERSION_5;
195 else if (0 == strcmp( pMagicHeader, pVerStr2 ))
196 nDicVersion = DIC_VERSION_2;
197 else
198 nDicVersion = DIC_VERSION_DONTKNOW;
200 if (DIC_VERSION_2 == nDicVersion ||
201 DIC_VERSION_5 == nDicVersion ||
202 DIC_VERSION_6 == nDicVersion)
204 // The language of the dictionary
205 sal_uInt16 nTmp = 0;
206 rpStream->ReadUInt16( nTmp );
207 nLng = LanguageType(nTmp);
208 if (VERS2_NOLANGUAGE == static_cast<sal_uInt16>(nLng))
209 nLng = LANGUAGE_NONE;
211 // Negative Flag
212 rpStream->ReadCharAsBool( bNeg );
216 return nDicVersion;
219 DictionaryNeo::DictionaryNeo(const OUString &rName,
220 LanguageType nLang, DictionaryType eType,
221 const OUString &rMainURL,
222 bool bWriteable) :
223 aDicEvtListeners( GetLinguMutex() ),
224 aDicName (rName),
225 aMainURL (rMainURL),
226 eDicType (eType),
227 nLanguage (nLang)
229 nDicVersion = DIC_VERSION_DONTKNOW;
230 bNeedEntries = true;
231 bIsModified = bIsActive = false;
232 bIsReadonly = !bWriteable;
234 if( !rMainURL.isEmpty())
236 bool bExists = FileExists( rMainURL );
237 if( !bExists )
239 // save new dictionaries with in Format 7 (UTF8 plain text)
240 nDicVersion = DIC_VERSION_7;
242 //! create physical representation of an **empty** dictionary
243 //! that could be found by the dictionary-list implementation
244 // (Note: empty dictionaries are not just empty files!)
245 DBG_ASSERT( !bIsReadonly,
246 "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
247 if (!bIsReadonly)
248 saveEntries( rMainURL );
249 bNeedEntries = false;
252 else
254 // non persistent dictionaries (like IgnoreAllList) should always be writable
255 bIsReadonly = false;
256 bNeedEntries = false;
260 DictionaryNeo::~DictionaryNeo()
264 ErrCode DictionaryNeo::loadEntries(const OUString &rMainURL)
266 MutexGuard aGuard( GetLinguMutex() );
268 // counter check that it is safe to set bIsModified to sal_False at
269 // the end of the function
270 DBG_ASSERT(!bIsModified, "lng : dictionary already modified!");
272 // function should only be called once in order to load entries from file
273 bNeedEntries = false;
275 if (rMainURL.isEmpty())
276 return ERRCODE_NONE;
278 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
280 // get XInputStream stream
281 uno::Reference< io::XInputStream > xStream;
284 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
285 xStream = xAccess->openFileRead( rMainURL );
287 catch (const uno::Exception &)
289 SAL_WARN( "linguistic", "failed to get input stream" );
291 if (!xStream.is())
292 return ErrCode(sal_uInt32(-1));
294 SvStreamPtr pStream = SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream ) );
296 // read header
297 bool bNegativ;
298 LanguageType nLang;
299 nDicVersion = ReadDicVersion(pStream, nLang, bNegativ, aDicName);
300 ErrCode nErr = pStream->GetError();
301 if (nErr != ERRCODE_NONE)
302 return nErr;
304 nLanguage = nLang;
306 eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
308 rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
309 if (nDicVersion >= DIC_VERSION_6)
310 eEnc = RTL_TEXTENCODING_UTF8;
311 aEntries.clear();
313 if (DIC_VERSION_6 == nDicVersion ||
314 DIC_VERSION_5 == nDicVersion ||
315 DIC_VERSION_2 == nDicVersion)
317 sal_uInt16 nLen = 0;
318 sal_Char aWordBuf[ BUFSIZE ];
320 // Read the first word
321 if (!pStream->eof())
323 pStream->ReadUInt16( nLen );
324 if (ERRCODE_NONE != (nErr = pStream->GetError()))
325 return nErr;
326 if ( nLen < BUFSIZE )
328 pStream->ReadBytes(aWordBuf, nLen);
329 if (ERRCODE_NONE != (nErr = pStream->GetError()))
330 return nErr;
331 *(aWordBuf + nLen) = 0;
333 else
334 return SVSTREAM_READ_ERROR;
337 while(!pStream->eof())
339 // Read from file
340 // Paste in dictionary without converting
341 if(*aWordBuf)
343 OUString aText(aWordBuf, rtl_str_getLength(aWordBuf), eEnc);
344 uno::Reference< XDictionaryEntry > xEntry =
345 new DicEntry( aText, bNegativ );
346 addEntry_Impl( xEntry, true ); //! don't launch events here
349 pStream->ReadUInt16( nLen );
350 if (pStream->eof())
351 break;
352 if (ERRCODE_NONE != (nErr = pStream->GetError()))
353 return nErr;
355 if (nLen < BUFSIZE)
357 pStream->ReadBytes(aWordBuf, nLen);
358 if (ERRCODE_NONE != (nErr = pStream->GetError()))
359 return nErr;
361 else
362 return SVSTREAM_READ_ERROR;
363 *(aWordBuf + nLen) = 0;
366 else if (DIC_VERSION_7 == nDicVersion)
368 OString aLine;
370 // remaining lines - stock strings (a [==] b)
371 while (pStream->ReadLine(aLine))
373 if (aLine.isEmpty() || aLine[0] == '#') // skip comments
374 continue;
375 OUString aText = OStringToOUString(aLine, RTL_TEXTENCODING_UTF8);
376 uno::Reference< XDictionaryEntry > xEntry =
377 new DicEntry( aText, eDicType == DictionaryType_NEGATIVE );
378 addEntry_Impl( xEntry, true ); //! don't launch events here
382 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
384 // since this routine should be called only initially (prior to any
385 // modification to be saved) we reset the bIsModified flag here that
386 // was implicitly set by addEntry_Impl
387 bIsModified = false;
389 return pStream->GetError();
392 static OString formatForSave(const uno::Reference< XDictionaryEntry > &xEntry,
393 rtl_TextEncoding eEnc )
395 OStringBuffer aStr(OUStringToOString(xEntry->getDictionaryWord(), eEnc));
397 if (xEntry->isNegative() || !xEntry->getReplacementText().isEmpty())
399 aStr.append("==");
400 aStr.append(OUStringToOString(xEntry->getReplacementText(), eEnc));
402 return aStr.makeStringAndClear();
405 ErrCode DictionaryNeo::saveEntries(const OUString &rURL)
407 MutexGuard aGuard( GetLinguMutex() );
409 if (rURL.isEmpty())
410 return ERRCODE_NONE;
411 DBG_ASSERT(!INetURLObject( rURL ).HasError(), "lng : invalid URL");
413 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
415 // get XOutputStream stream
416 uno::Reference<io::XStream> xStream;
419 xStream = io::TempFile::create(xContext);
421 catch (const uno::Exception &)
423 DBG_ASSERT( false, "failed to get input stream" );
425 if (!xStream.is())
426 return ErrCode(sal_uInt32(-1));
428 SvStreamPtr pStream = SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream ) );
430 // Always write as the latest version, i.e. DIC_VERSION_7
432 rtl_TextEncoding eEnc = RTL_TEXTENCODING_UTF8;
433 pStream->WriteLine(OString(pVerOOo7));
434 ErrCode nErr = pStream->GetError();
435 if (nErr != ERRCODE_NONE)
436 return nErr;
437 /* XXX: the <none> case could be differentiated, is it absence or
438 * undetermined or multiple? Earlier versions did not know about 'und' and
439 * 'mul' and 'zxx' codes. Sync with ReadDicVersion() */
440 if (LinguIsUnspecified(nLanguage))
441 pStream->WriteLine(OString("lang: <none>"));
442 else
444 OStringBuffer aLine("lang: ");
445 aLine.append(OUStringToOString(LanguageTag::convertToBcp47(nLanguage), eEnc));
446 pStream->WriteLine(aLine.makeStringAndClear());
448 if (ERRCODE_NONE != (nErr = pStream->GetError()))
449 return nErr;
450 if (eDicType == DictionaryType_POSITIVE)
451 pStream->WriteLine(OString("type: positive"));
452 else
453 pStream->WriteLine(OString("type: negative"));
454 if (aDicName.endsWith(EXTENSION_FOR_TITLE_TEXT))
456 pStream->WriteLine(OUStringToOString("title: " +
457 // strip EXTENSION_FOR_TITLE_TEXT
458 aDicName.copy(0, aDicName.lastIndexOf(EXTENSION_FOR_TITLE_TEXT)), eEnc));
460 if (ERRCODE_NONE != (nErr = pStream->GetError()))
461 return nErr;
462 pStream->WriteLine(OString("---"));
463 if (ERRCODE_NONE != (nErr = pStream->GetError()))
464 return nErr;
465 for (Reference<XDictionaryEntry> & aEntrie : aEntries)
467 OString aOutStr = formatForSave(aEntrie, eEnc);
468 pStream->WriteLine (aOutStr);
469 if (ERRCODE_NONE != (nErr = pStream->GetError()))
470 return nErr;
475 pStream.reset();
476 uno::Reference< ucb::XSimpleFileAccess3 > xAccess(ucb::SimpleFileAccess::create(xContext));
477 Reference<io::XInputStream> xInputStream(xStream, UNO_QUERY_THROW);
478 uno::Reference<io::XSeekable> xSeek(xInputStream, UNO_QUERY_THROW);
479 xSeek->seek(0);
480 xAccess->writeFile(rURL, xInputStream);
481 //If we are migrating from an older version, then on first successful
482 //write, we're now converted to the latest version, i.e. DIC_VERSION_7
483 nDicVersion = DIC_VERSION_7;
485 catch (const uno::Exception &)
487 DBG_ASSERT( false, "failed to write stream" );
488 return ErrCode(sal_uInt32(-1));
491 return nErr;
494 void DictionaryNeo::launchEvent(sal_Int16 nEvent,
495 const uno::Reference< XDictionaryEntry >& xEntry)
497 MutexGuard aGuard( GetLinguMutex() );
499 DictionaryEvent aEvt;
500 aEvt.Source = uno::Reference< XDictionary >( this );
501 aEvt.nEvent = nEvent;
502 aEvt.xDictionaryEntry = xEntry;
504 aDicEvtListeners.notifyEach( &XDictionaryEventListener::processDictionaryEvent, aEvt);
507 int DictionaryNeo::cmpDicEntry(const OUString& rWord1,
508 const OUString &rWord2,
509 bool bSimilarOnly)
511 MutexGuard aGuard( GetLinguMutex() );
513 // returns 0 if rWord1 is equal to rWord2
514 // " a value < 0 if rWord1 is less than rWord2
515 // " a value > 0 if rWord1 is greater than rWord2
517 int nRes = 0;
519 sal_Int32 nLen1 = rWord1.getLength(),
520 nLen2 = rWord2.getLength();
521 if (bSimilarOnly)
523 const sal_Unicode cChar = '.';
524 if (nLen1 && cChar == rWord1[ nLen1 - 1 ])
525 nLen1--;
526 if (nLen2 && cChar == rWord2[ nLen2 - 1 ])
527 nLen2--;
530 const sal_Unicode cIgnChar = '=';
531 const sal_Unicode cIgnBeg = '['; // for alternative hyphenation, eg. Schif[f]fahrt, Zuc[1k]ker
532 const sal_Unicode cIgnEnd = ']'; // planned: gee"[1-/e]rfde or ge[-/1e]e"rfde (gee"rfde -> ge=erfde)
533 sal_Int32 nIdx1 = 0,
534 nIdx2 = 0,
535 nNumIgnChar1 = 0,
536 nNumIgnChar2 = 0;
538 bool IgnState;
539 sal_Int32 nDiff = 0;
540 sal_Unicode cChar1 = '\0';
541 sal_Unicode cChar2 = '\0';
544 // skip chars to be ignored
545 IgnState = false;
546 while (nIdx1 < nLen1 && ((cChar1 = rWord1[ nIdx1 ]) == cIgnChar || cChar1 == cIgnBeg || IgnState ))
548 if ( cChar1 == cIgnBeg )
549 IgnState = true;
550 else if (cChar1 == cIgnEnd)
551 IgnState = false;
552 nIdx1++;
553 nNumIgnChar1++;
555 IgnState = false;
556 while (nIdx2 < nLen2 && ((cChar2 = rWord2[ nIdx2 ]) == cIgnChar || cChar2 == cIgnBeg || IgnState ))
558 if ( cChar2 == cIgnBeg )
559 IgnState = true;
560 else if (cChar2 == cIgnEnd)
561 IgnState = false;
562 nIdx2++;
563 nNumIgnChar2++;
566 if (nIdx1 < nLen1 && nIdx2 < nLen2)
568 nDiff = cChar1 - cChar2;
569 if (nDiff)
570 break;
571 nIdx1++;
572 nIdx2++;
574 } while (nIdx1 < nLen1 && nIdx2 < nLen2);
577 if (nDiff)
578 nRes = nDiff;
579 else
580 { // the string with the smallest count of not ignored chars is the
581 // shorter one
583 // count remaining IgnChars
584 IgnState = false;
585 while (nIdx1 < nLen1 )
587 if (rWord1[ nIdx1 ] == cIgnBeg)
588 IgnState = true;
589 if (IgnState || rWord1[ nIdx1 ] == cIgnChar)
590 nNumIgnChar1++;
591 if (rWord1[ nIdx1] == cIgnEnd)
592 IgnState = false;
593 nIdx1++;
595 IgnState = false;
596 while (nIdx2 < nLen2 )
598 if (rWord2[ nIdx2 ] == cIgnBeg)
599 IgnState = true;
600 if (IgnState || rWord2[ nIdx2 ] == cIgnChar)
601 nNumIgnChar2++;
602 if (rWord2[ nIdx2 ] == cIgnEnd)
603 IgnState = false;
604 nIdx2++;
607 nRes = (nLen1 - nNumIgnChar1) - (nLen2 - nNumIgnChar2);
610 return nRes;
613 bool DictionaryNeo::seekEntry(const OUString &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(),
625 nMidIdx,
626 nLowerIdx = 0;
627 if( nUpperIdx > 0 )
629 nUpperIdx--;
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 );
637 if(nCmp == 0)
639 if( pPos ) *pPos = nMidIdx;
640 return true;
642 else if(nCmp > 0)
643 nLowerIdx = nMidIdx + 1;
644 else if( nMidIdx == 0 )
646 if( pPos ) *pPos = nLowerIdx;
647 return false;
649 else
650 nUpperIdx = nMidIdx - 1;
653 if( pPos ) *pPos = nLowerIdx;
654 return false;
657 bool DictionaryNeo::isSorted()
659 bool bRes = true;
661 sal_Int32 nEntries = getCount();
662 sal_Int32 i;
663 for (i = 1; i < nEntries; i++)
665 if (cmpDicEntry( aEntries[i-1]->getDictionaryWord(),
666 aEntries[i]->getDictionaryWord() ) > 0)
668 bRes = false;
669 break;
672 return bRes;
675 bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry >& xDicEntry,
676 bool bIsLoadEntries)
678 MutexGuard aGuard( GetLinguMutex() );
680 bool bRes = false;
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
692 sal_Int32 nPos = 0;
693 if (bAddEntry)
695 const bool bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
696 if (bFound)
697 bAddEntry = false;
700 if (bAddEntry)
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");
708 bIsModified = true;
709 bRes = true;
711 if (!bIsLoadEntries)
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 );
731 bRes = true;
732 } else
733 bRes = false;
737 return bRes;
740 OUString SAL_CALL DictionaryNeo::getName( )
742 MutexGuard aGuard( GetLinguMutex() );
743 return aDicName;
746 void SAL_CALL DictionaryNeo::setName( const OUString& aName )
748 MutexGuard aGuard( GetLinguMutex() );
750 if (aDicName != aName)
752 aDicName = aName;
753 launchEvent(DictionaryEventFlags::CHG_NAME, nullptr);
757 DictionaryType SAL_CALL DictionaryNeo::getDictionaryType( )
759 MutexGuard aGuard( GetLinguMutex() );
761 return eDicType;
764 void SAL_CALL DictionaryNeo::setActive( sal_Bool bActivate )
766 MutexGuard aGuard( GetLinguMutex() );
768 if (bIsActive != bool(bActivate))
770 bIsActive = bActivate;
771 sal_Int16 nEvent = bIsActive ?
772 DictionaryEventFlags::ACTIVATE_DIC : DictionaryEventFlags::DEACTIVATE_DIC;
774 // remove entries from memory if dictionary is deactivated
775 if (!bIsActive)
777 bool bIsEmpty = aEntries.empty();
779 // save entries first if necessary
780 if (bIsModified && hasLocation() && !isReadonly())
782 store();
784 aEntries.clear();
785 bNeedEntries = !bIsEmpty;
787 DBG_ASSERT( !bIsModified || !hasLocation() || isReadonly(),
788 "lng : dictionary is still modified" );
791 launchEvent(nEvent, nullptr);
795 sal_Bool SAL_CALL DictionaryNeo::isActive( )
797 MutexGuard aGuard( GetLinguMutex() );
798 return bIsActive;
801 sal_Int32 SAL_CALL DictionaryNeo::getCount( )
803 MutexGuard aGuard( GetLinguMutex() );
805 if (bNeedEntries)
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() );
834 if (bNeedEntries)
835 loadEntries( aMainURL );
837 sal_Int32 nPos;
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() );
850 bool bRes = false;
852 if (!bIsReadonly)
854 if (bNeedEntries)
855 loadEntries( aMainURL );
856 bRes = addEntry_Impl( xDicEntry );
859 return bRes;
862 sal_Bool SAL_CALL
863 DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
864 const OUString& rRplcText )
866 MutexGuard aGuard( GetLinguMutex() );
868 bool bRes = false;
870 if (!bIsReadonly)
872 uno::Reference< XDictionaryEntry > xEntry =
873 new DicEntry( rWord, bIsNegative, rRplcText );
874 bRes = addEntry_Impl( xEntry );
877 return bRes;
880 sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
882 MutexGuard aGuard( GetLinguMutex() );
884 bool bRemoved = false;
886 if (!bIsReadonly)
888 if (bNeedEntries)
889 loadEntries( aMainURL );
891 sal_Int32 nPos;
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
896 if (bFound)
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 );
911 return bRemoved;
914 sal_Bool SAL_CALL DictionaryNeo::isFull( )
916 MutexGuard aGuard( GetLinguMutex() );
918 if (bNeedEntries)
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() );
928 if (bNeedEntries)
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
941 aEntries.clear();
943 bNeedEntries = false;
944 bIsModified = true;
946 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , nullptr );
950 sal_Bool SAL_CALL DictionaryNeo::addDictionaryEventListener(
951 const uno::Reference< XDictionaryEventListener >& xListener )
953 MutexGuard aGuard( GetLinguMutex() );
955 bool bRes = false;
956 if (xListener.is())
958 sal_Int32 nLen = aDicEvtListeners.getLength();
959 bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
961 return bRes;
964 sal_Bool SAL_CALL DictionaryNeo::removeDictionaryEventListener(
965 const uno::Reference< XDictionaryEventListener >& xListener )
967 MutexGuard aGuard( GetLinguMutex() );
969 bool bRes = false;
970 if (xListener.is())
972 sal_Int32 nLen = aDicEvtListeners.getLength();
973 bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
975 return bRes;
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() );
988 return aMainURL;
991 sal_Bool SAL_CALL DictionaryNeo::isReadonly()
993 MutexGuard aGuard( GetLinguMutex() );
995 return bIsReadonly;
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 ))
1017 aMainURL = 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() );
1028 saveEntries(aURL);
1032 DicEntry::DicEntry(const OUString &rDicFileWord,
1033 bool bIsNegativWord)
1035 if (!rDicFileWord.isEmpty())
1036 splitDicFileWord( rDicFileWord, aDicWord, aReplacement );
1037 bIsNegativ = bIsNegativWord;
1040 DicEntry::DicEntry(const OUString &rDicWord, bool bNegativ,
1041 const OUString &rRplcText) :
1042 aDicWord (rDicWord),
1043 aReplacement (rRplcText),
1044 bIsNegativ (bNegativ)
1048 DicEntry::~DicEntry()
1052 void DicEntry::splitDicFileWord(const OUString &rDicFileWord,
1053 OUString &rDicWord,
1054 OUString &rReplacement)
1056 MutexGuard aGuard( GetLinguMutex() );
1058 sal_Int32 nDelimPos = rDicFileWord.indexOf( "==" );
1059 if (-1 != nDelimPos)
1061 sal_Int32 nTriplePos = nDelimPos + 2;
1062 if ( nTriplePos < rDicFileWord.getLength()
1063 && rDicFileWord[ nTriplePos ] == '=' )
1064 ++nDelimPos;
1065 rDicWord = rDicFileWord.copy( 0, nDelimPos );
1066 rReplacement = rDicFileWord.copy( nDelimPos + 2 );
1068 else
1070 rDicWord = rDicFileWord;
1071 rReplacement.clear();
1075 OUString SAL_CALL DicEntry::getDictionaryWord( )
1077 MutexGuard aGuard( GetLinguMutex() );
1078 return aDicWord;
1081 sal_Bool SAL_CALL DicEntry::isNegative( )
1083 MutexGuard aGuard( GetLinguMutex() );
1084 return bIsNegativ;
1087 OUString SAL_CALL DicEntry::getReplacementText( )
1089 MutexGuard aGuard( GetLinguMutex() );
1090 return aReplacement;
1094 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */