tdf#121561 sw DOCX: output toc-end inside last toc paragraph
[LibreOffice.git] / linguistic / source / dicimp.cxx
blob100c9cab7f062d812277120371e1668890662a89
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 <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>
47 #include "defs.hxx"
49 #include <algorithm>
52 using namespace utl;
53 using namespace osl;
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;
61 #define BUFSIZE 4096
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 ) ;
96 return xRes;
99 static bool getTag(const OString &rLine, const sal_Char *pTagName,
100 OString &rTagValue)
102 sal_Int32 nPos = rLine.indexOf(pTagName);
103 if (nPos == -1)
104 return false;
106 rTagValue = comphelper::string::strip(rLine.copy(nPos + rtl_str_getLength(pTagName)),
107 ' ');
108 return true;
112 sal_Int16 ReadDicVersion( SvStreamPtr const &rpStream, LanguageType &nLng, bool &bNeg, OUString &aDicName )
114 // Sniff the header
115 sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
116 sal_Char pMagicHeader[MAX_HEADER_LENGTH];
118 nLng = LANGUAGE_NONE;
119 bNeg = false;
121 if (!rpStream.get() || rpStream->GetError())
122 return -1;
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))
130 bool bSuccess;
131 OString aLine;
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)))
141 OString aTagValue;
143 if (aLine[0] == '#') // skip comments
144 continue;
146 // lang: field
147 if (getTag(aLine, "lang: ", aTagValue))
149 if (aTagValue == "<none>")
150 nLng = LANGUAGE_NONE;
151 else
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";
162 // lang: title
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
168 // of the title text
169 EXTENSION_FOR_TITLE_TEXT;
172 if (aLine.indexOf("---") != -1) // end of header
173 break;
175 if (!bSuccess)
176 return -2;
178 else
180 sal_uInt16 nLen;
182 rpStream->Seek (nSniffPos );
184 rpStream->ReadUInt16( nLen );
185 if (nLen >= MAX_HEADER_LENGTH)
186 return -1;
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;
198 else
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
206 sal_uInt16 nTmp = 0;
207 rpStream->ReadUInt16( nTmp );
208 nLng = LanguageType(nTmp);
209 if (VERS2_NOLANGUAGE == static_cast<sal_uInt16>(nLng))
210 nLng = LANGUAGE_NONE;
212 // Negative Flag
213 rpStream->ReadCharAsBool( bNeg );
217 return nDicVersion;
220 DictionaryNeo::DictionaryNeo(const OUString &rName,
221 LanguageType nLang, DictionaryType eType,
222 const OUString &rMainURL,
223 bool bWriteable) :
224 aDicEvtListeners( GetLinguMutex() ),
225 aDicName (rName),
226 aMainURL (rMainURL),
227 eDicType (eType),
228 nLanguage (nLang)
230 nDicVersion = DIC_VERSION_DONTKNOW;
231 bNeedEntries = true;
232 bIsModified = bIsActive = false;
233 bIsReadonly = !bWriteable;
235 if( !rMainURL.isEmpty())
237 bool bExists = FileExists( rMainURL );
238 if( !bExists )
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" );
248 if (!bIsReadonly)
249 saveEntries( rMainURL );
250 bNeedEntries = false;
253 else
255 // non persistent dictionaries (like IgnoreAllList) should always be writable
256 bIsReadonly = false;
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())
277 return ERRCODE_NONE;
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" );
292 if (!xStream.is())
293 return ErrCode(sal_uInt32(-1));
295 SvStreamPtr pStream = SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream ) );
297 // read header
298 bool bNegativ;
299 LanguageType nLang;
300 nDicVersion = ReadDicVersion(pStream, nLang, bNegativ, aDicName);
301 ErrCode nErr = pStream->GetError();
302 if (nErr != ERRCODE_NONE)
303 return nErr;
305 nLanguage = nLang;
307 eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
309 rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
310 if (nDicVersion >= DIC_VERSION_6)
311 eEnc = RTL_TEXTENCODING_UTF8;
312 aEntries.clear();
314 if (DIC_VERSION_6 == nDicVersion ||
315 DIC_VERSION_5 == nDicVersion ||
316 DIC_VERSION_2 == nDicVersion)
318 sal_uInt16 nLen = 0;
319 sal_Char aWordBuf[ BUFSIZE ];
321 // Read the first word
322 if (!pStream->eof())
324 pStream->ReadUInt16( nLen );
325 if (ERRCODE_NONE != (nErr = pStream->GetError()))
326 return nErr;
327 if ( nLen < BUFSIZE )
329 pStream->ReadBytes(aWordBuf, nLen);
330 if (ERRCODE_NONE != (nErr = pStream->GetError()))
331 return nErr;
332 *(aWordBuf + nLen) = 0;
334 else
335 return SVSTREAM_READ_ERROR;
338 while(!pStream->eof())
340 // Read from file
341 // Paste in dictionary without converting
342 if(*aWordBuf)
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 );
351 if (pStream->eof())
352 break;
353 if (ERRCODE_NONE != (nErr = pStream->GetError()))
354 return nErr;
356 if (nLen < BUFSIZE)
358 pStream->ReadBytes(aWordBuf, nLen);
359 if (ERRCODE_NONE != (nErr = pStream->GetError()))
360 return nErr;
362 else
363 return SVSTREAM_READ_ERROR;
364 *(aWordBuf + nLen) = 0;
367 else if (DIC_VERSION_7 == nDicVersion)
369 OString aLine;
371 // remaining lines - stock strings (a [==] b)
372 while (pStream->ReadLine(aLine))
374 if (aLine.isEmpty() || aLine[0] == '#') // skip comments
375 continue;
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
388 bIsModified = false;
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())
400 aStr.append("==");
401 aStr.append(OUStringToOString(xEntry->getReplacementText(), eEnc));
403 return aStr.makeStringAndClear();
406 ErrCode DictionaryNeo::saveEntries(const OUString &rURL)
408 MutexGuard aGuard( GetLinguMutex() );
410 if (rURL.isEmpty())
411 return ERRCODE_NONE;
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" );
426 if (!xStream.is())
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)
437 return nErr;
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>");
443 else
445 OStringBuffer aLine("lang: ");
446 aLine.append(OUStringToOString(LanguageTag::convertToBcp47(nLanguage), eEnc));
447 pStream->WriteLine(aLine.makeStringAndClear());
449 if (ERRCODE_NONE != (nErr = pStream->GetError()))
450 return nErr;
451 if (eDicType == DictionaryType_POSITIVE)
452 pStream->WriteLine("type: positive");
453 else
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()))
462 return nErr;
463 pStream->WriteLine("---");
464 if (ERRCODE_NONE != (nErr = pStream->GetError()))
465 return nErr;
466 for (Reference<XDictionaryEntry> & aEntrie : aEntries)
468 OString aOutStr = formatForSave(aEntrie, eEnc);
469 pStream->WriteLine (aOutStr);
470 if (ERRCODE_NONE != (nErr = pStream->GetError()))
471 return nErr;
476 pStream.reset();
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);
480 xSeek->seek(0);
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));
492 return nErr;
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,
510 bool bSimilarOnly)
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
518 int nRes = 0;
520 sal_Int32 nLen1 = rWord1.getLength(),
521 nLen2 = rWord2.getLength();
522 if (bSimilarOnly)
524 const sal_Unicode cChar = '.';
525 if (nLen1 && cChar == rWord1[ nLen1 - 1 ])
526 nLen1--;
527 if (nLen2 && cChar == rWord2[ nLen2 - 1 ])
528 nLen2--;
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)
534 sal_Int32 nIdx1 = 0,
535 nIdx2 = 0,
536 nNumIgnChar1 = 0,
537 nNumIgnChar2 = 0;
539 bool IgnState;
540 sal_Int32 nDiff = 0;
541 sal_Unicode cChar1 = '\0';
542 sal_Unicode cChar2 = '\0';
545 // skip chars to be ignored
546 IgnState = false;
547 while (nIdx1 < nLen1 && ((cChar1 = rWord1[ nIdx1 ]) == cIgnChar || cChar1 == cIgnBeg || IgnState ))
549 if ( cChar1 == cIgnBeg )
550 IgnState = true;
551 else if (cChar1 == cIgnEnd)
552 IgnState = false;
553 nIdx1++;
554 nNumIgnChar1++;
556 IgnState = false;
557 while (nIdx2 < nLen2 && ((cChar2 = rWord2[ nIdx2 ]) == cIgnChar || cChar2 == cIgnBeg || IgnState ))
559 if ( cChar2 == cIgnBeg )
560 IgnState = true;
561 else if (cChar2 == cIgnEnd)
562 IgnState = false;
563 nIdx2++;
564 nNumIgnChar2++;
567 if (nIdx1 < nLen1 && nIdx2 < nLen2)
569 nDiff = cChar1 - cChar2;
570 if (nDiff)
571 break;
572 nIdx1++;
573 nIdx2++;
575 } while (nIdx1 < nLen1 && nIdx2 < nLen2);
578 if (nDiff)
579 nRes = nDiff;
580 else
581 { // the string with the smallest count of not ignored chars is the
582 // shorter one
584 // count remaining IgnChars
585 IgnState = false;
586 while (nIdx1 < nLen1 )
588 if (rWord1[ nIdx1 ] == cIgnBeg)
589 IgnState = true;
590 if (IgnState || rWord1[ nIdx1 ] == cIgnChar)
591 nNumIgnChar1++;
592 if (rWord1[ nIdx1] == cIgnEnd)
593 IgnState = false;
594 nIdx1++;
596 IgnState = false;
597 while (nIdx2 < nLen2 )
599 if (rWord2[ nIdx2 ] == cIgnBeg)
600 IgnState = true;
601 if (IgnState || rWord2[ nIdx2 ] == cIgnChar)
602 nNumIgnChar2++;
603 if (rWord2[ nIdx2 ] == cIgnEnd)
604 IgnState = false;
605 nIdx2++;
608 nRes = (nLen1 - nNumIgnChar1) - (nLen2 - nNumIgnChar2);
611 return nRes;
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(),
626 nMidIdx,
627 nLowerIdx = 0;
628 if( nUpperIdx > 0 )
630 nUpperIdx--;
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 );
638 if(nCmp == 0)
640 if( pPos ) *pPos = nMidIdx;
641 return true;
643 else if(nCmp > 0)
644 nLowerIdx = nMidIdx + 1;
645 else if( nMidIdx == 0 )
647 if( pPos ) *pPos = nLowerIdx;
648 return false;
650 else
651 nUpperIdx = nMidIdx - 1;
654 if( pPos ) *pPos = nLowerIdx;
655 return false;
658 bool DictionaryNeo::isSorted()
660 bool bRes = true;
662 sal_Int32 nEntries = getCount();
663 sal_Int32 i;
664 for (i = 1; i < nEntries; i++)
666 if (cmpDicEntry( aEntries[i-1]->getDictionaryWord(),
667 aEntries[i]->getDictionaryWord() ) > 0)
669 bRes = false;
670 break;
673 return bRes;
676 bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry >& xDicEntry,
677 bool bIsLoadEntries)
679 MutexGuard aGuard( GetLinguMutex() );
681 bool bRes = false;
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
693 sal_Int32 nPos = 0;
694 if (bAddEntry)
696 const bool bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
697 if (bFound)
698 bAddEntry = false;
701 if (bAddEntry)
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");
709 bIsModified = true;
710 bRes = true;
712 if (!bIsLoadEntries)
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 );
732 bRes = true;
733 } else
734 bRes = false;
738 return bRes;
741 OUString SAL_CALL DictionaryNeo::getName( )
743 MutexGuard aGuard( GetLinguMutex() );
744 return aDicName;
747 void SAL_CALL DictionaryNeo::setName( const OUString& aName )
749 MutexGuard aGuard( GetLinguMutex() );
751 if (aDicName != aName)
753 aDicName = aName;
754 launchEvent(DictionaryEventFlags::CHG_NAME, nullptr);
758 DictionaryType SAL_CALL DictionaryNeo::getDictionaryType( )
760 MutexGuard aGuard( GetLinguMutex() );
762 return eDicType;
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
776 if (!bIsActive)
778 bool bIsEmpty = aEntries.empty();
780 // save entries first if necessary
781 if (bIsModified && hasLocation() && !isReadonly())
783 store();
785 aEntries.clear();
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() );
799 return bIsActive;
802 sal_Int32 SAL_CALL DictionaryNeo::getCount( )
804 MutexGuard aGuard( GetLinguMutex() );
806 if (bNeedEntries)
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() );
835 if (bNeedEntries)
836 loadEntries( aMainURL );
838 sal_Int32 nPos;
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() );
851 bool bRes = false;
853 if (!bIsReadonly)
855 if (bNeedEntries)
856 loadEntries( aMainURL );
857 bRes = addEntry_Impl( xDicEntry );
860 return bRes;
863 sal_Bool SAL_CALL
864 DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
865 const OUString& rRplcText )
867 MutexGuard aGuard( GetLinguMutex() );
869 bool bRes = false;
871 if (!bIsReadonly)
873 uno::Reference< XDictionaryEntry > xEntry =
874 new DicEntry( rWord, bIsNegative, rRplcText );
875 bRes = addEntry_Impl( xEntry );
878 return bRes;
881 sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
883 MutexGuard aGuard( GetLinguMutex() );
885 bool bRemoved = false;
887 if (!bIsReadonly)
889 if (bNeedEntries)
890 loadEntries( aMainURL );
892 sal_Int32 nPos;
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
897 if (bFound)
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 );
912 return bRemoved;
915 sal_Bool SAL_CALL DictionaryNeo::isFull( )
917 MutexGuard aGuard( GetLinguMutex() );
919 if (bNeedEntries)
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() );
929 if (bNeedEntries)
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
942 aEntries.clear();
944 bNeedEntries = false;
945 bIsModified = true;
947 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , nullptr );
951 sal_Bool SAL_CALL DictionaryNeo::addDictionaryEventListener(
952 const uno::Reference< XDictionaryEventListener >& xListener )
954 MutexGuard aGuard( GetLinguMutex() );
956 bool bRes = false;
957 if (xListener.is())
959 sal_Int32 nLen = aDicEvtListeners.getLength();
960 bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
962 return bRes;
965 sal_Bool SAL_CALL DictionaryNeo::removeDictionaryEventListener(
966 const uno::Reference< XDictionaryEventListener >& xListener )
968 MutexGuard aGuard( GetLinguMutex() );
970 bool bRes = false;
971 if (xListener.is())
973 sal_Int32 nLen = aDicEvtListeners.getLength();
974 bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
976 return bRes;
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() );
989 return aMainURL;
992 sal_Bool SAL_CALL DictionaryNeo::isReadonly()
994 MutexGuard aGuard( GetLinguMutex() );
996 return bIsReadonly;
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 ))
1018 aMainURL = 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() );
1029 saveEntries(aURL);
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,
1054 OUString &rDicWord,
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 ] == '=' )
1065 ++nDelimPos;
1066 rDicWord = rDicFileWord.copy( 0, nDelimPos );
1067 rReplacement = rDicFileWord.copy( nDelimPos + 2 );
1069 else
1071 rDicWord = rDicFileWord;
1072 rReplacement.clear();
1076 OUString SAL_CALL DicEntry::getDictionaryWord( )
1078 MutexGuard aGuard( GetLinguMutex() );
1079 return aDicWord;
1082 sal_Bool SAL_CALL DicEntry::isNegative( )
1084 MutexGuard aGuard( GetLinguMutex() );
1085 return bIsNegativ;
1088 OUString SAL_CALL DicEntry::getReplacementText( )
1090 MutexGuard aGuard( GetLinguMutex() );
1091 return aReplacement;
1095 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */