LibreOffice Module linguistic (master) 1
dicimp.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20
22#include "dicimp.hxx"
23#include <i18nlangtag/lang.h>
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>
33#include <comphelper/string.hxx>
36
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>
41
42#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
43#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
44
45#include <algorithm>
46#include <utility>
47
48
49using namespace utl;
50using namespace osl;
51using namespace com::sun::star;
52using namespace com::sun::star::lang;
53using namespace com::sun::star::uno;
54using namespace com::sun::star::linguistic2;
55using namespace linguistic;
56
57
58#define BUFSIZE 4096
59#define VERS2_NOLANGUAGE 1024
60
61#define MAX_HEADER_LENGTH 16
62
63// XML-header to query SPELLML support
64// to handle user words with "Grammar By" model words
65constexpr OUStringLiteral SPELLML_SUPPORT = u"<?xml?>";
66
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).
72//
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.
76constexpr OUStringLiteral EXTENSION_FOR_TITLE_TEXT = u".";
77
78const char* const pVerStr2 = "WBSWG2";
79const char* const pVerStr5 = "WBSWG5";
80const char* const pVerStr6 = "WBSWG6";
81const char* const pVerOOo7 = "OOoUserDict1";
82
83const sal_Int16 DIC_VERSION_DONTKNOW = -1;
84const sal_Int16 DIC_VERSION_2 = 2;
85const sal_Int16 DIC_VERSION_5 = 5;
86const sal_Int16 DIC_VERSION_6 = 6;
87const sal_Int16 DIC_VERSION_7 = 7;
88
89static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl()
90{
91 uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
92 uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create( xContext ) ;
93 return xRes;
94}
95
96static bool getTag(std::string_view rLine, std::string_view rTagName,
97 OString &rTagValue)
98{
99 size_t nPos = rLine.find(rTagName);
100 if (nPos == std::string_view::npos)
101 return false;
102
103 rTagValue = OString(comphelper::string::strip(rLine.substr(nPos + rTagName.size()),
104 ' '));
105 return true;
106}
107
108
109sal_Int16 ReadDicVersion( SvStream& rStream, LanguageType &nLng, bool &bNeg, OUString &aDicName )
110{
111 // Sniff the header
112 sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
113 char pMagicHeader[MAX_HEADER_LENGTH];
114
115 nLng = LANGUAGE_NONE;
116 bNeg = false;
117
118 if (rStream.GetError())
119 return -1;
120
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))
126 {
127 bool bSuccess;
128 OStringBuffer aLine;
129
130 nDicVersion = DIC_VERSION_7;
131
132 // 1st skip magic / header line
133 rStream.ReadLine(aLine);
134
135 // 2nd line: language all | en-US | pt-BR ...
136 while ((bSuccess = rStream.ReadLine(aLine)))
137 {
138 OString aTagValue;
139
140 if (aLine[0] == '#') // skip comments
141 continue;
142
143 // lang: field
144 if (getTag(aLine, "lang: ", aTagValue))
145 {
146 if (aTagValue == "<none>")
147 nLng = LANGUAGE_NONE;
148 else
150 OStringToOUString( aTagValue, RTL_TEXTENCODING_ASCII_US));
151 }
152
153 // type: negative / positive
154 if (getTag(aLine, "type: ", aTagValue))
155 {
156 bNeg = aTagValue == "negative";
157 }
158
159 // lang: title
160 if (getTag(aLine, "title: ", aTagValue))
161 {
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
165 // of the title text
167 }
168
169 if (std::string_view(aLine).find("---") != std::string_view::npos) // end of header
170 break;
171 }
172 if (!bSuccess)
173 return -2;
174 }
175 else
176 {
177 sal_uInt16 nLen;
178
179 rStream.Seek (nSniffPos );
180
181 rStream.ReadUInt16( nLen );
182 if (nLen >= MAX_HEADER_LENGTH)
183 return -1;
184
185 rStream.ReadBytes(pMagicHeader, nLen);
186 pMagicHeader[nLen] = '\0';
187
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;
195 else
196 nDicVersion = DIC_VERSION_DONTKNOW;
197
198 if (DIC_VERSION_2 == nDicVersion ||
199 DIC_VERSION_5 == nDicVersion ||
200 DIC_VERSION_6 == nDicVersion)
201 {
202 // The language of the dictionary
203 sal_uInt16 nTmp = 0;
204 rStream.ReadUInt16( nTmp );
205 nLng = LanguageType(nTmp);
206 if (VERS2_NOLANGUAGE == static_cast<sal_uInt16>(nLng))
207 nLng = LANGUAGE_NONE;
208
209 // Negative Flag
210 rStream.ReadCharAsBool( bNeg );
211 }
212 }
213
214 return nDicVersion;
215}
216
217DictionaryNeo::DictionaryNeo(OUString aName,
218 LanguageType nLang, DictionaryType eType,
219 const OUString &rMainURL,
220 bool bWriteable) :
221 aDicEvtListeners( GetLinguMutex() ),
222 aDicName (std::move(aName)),
223 aMainURL (rMainURL),
224 eDicType (eType),
225 nLanguage (nLang)
226{
227 nDicVersion = DIC_VERSION_DONTKNOW;
228 bNeedEntries = true;
229 bIsModified = bIsActive = false;
230 bIsReadonly = !bWriteable;
231
232 if( !rMainURL.isEmpty())
233 {
234 bool bExists = FileExists( rMainURL );
235 if( !bExists )
236 {
237 // save new dictionaries with in Format 7 (UTF8 plain text)
238 nDicVersion = DIC_VERSION_7;
239
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" );
245 if (!bIsReadonly)
246 saveEntries( rMainURL );
247 bNeedEntries = false;
248 }
249 }
250 else
251 {
252 // non persistent dictionaries (like IgnoreAllList) should always be writable
253 bIsReadonly = false;
254 bNeedEntries = false;
255 }
256}
257
259{
260}
261
262ErrCode DictionaryNeo::loadEntries(const OUString &rMainURL)
263{
264 MutexGuard aGuard( GetLinguMutex() );
265
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!");
269
270 // function should only be called once in order to load entries from file
271 bNeedEntries = false;
272
273 if (rMainURL.isEmpty())
274 return ERRCODE_NONE;
275
276 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
277
278 // get XInputStream stream
279 uno::Reference< io::XInputStream > xStream;
280 try
281 {
282 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
283 xStream = xAccess->openFileRead( rMainURL );
284 }
285 catch (const uno::Exception &)
286 {
287 SAL_WARN( "linguistic", "failed to get input stream" );
288 }
289 if (!xStream.is())
290 return ErrCode(sal_uInt32(-1));
291
292 std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
293
294 // read header
295 bool bNegativ;
296 LanguageType nLang;
297 nDicVersion = ReadDicVersion(*pStream, nLang, bNegativ, aDicName);
298 ErrCode nErr = pStream->GetError();
299 if (nErr != ERRCODE_NONE)
300 return nErr;
301
302 nLanguage = nLang;
303
304 eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
305
306 rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
308 eEnc = RTL_TEXTENCODING_UTF8;
309 aEntries.clear();
310
311 if (DIC_VERSION_6 == nDicVersion ||
314 {
315 sal_uInt16 nLen = 0;
316 char aWordBuf[ BUFSIZE ];
317
318 // Read the first word
319 if (!pStream->eof())
320 {
321 pStream->ReadUInt16( nLen );
322 if (ERRCODE_NONE != (nErr = pStream->GetError()))
323 return nErr;
324 if ( nLen < BUFSIZE )
325 {
326 pStream->ReadBytes(aWordBuf, nLen);
327 if (ERRCODE_NONE != (nErr = pStream->GetError()))
328 return nErr;
329 *(aWordBuf + nLen) = 0;
330 }
331 else
332 return SVSTREAM_READ_ERROR;
333 }
334
335 while(!pStream->eof())
336 {
337 // Read from file
338 // Paste in dictionary without converting
339 if(*aWordBuf)
340 {
341 OUString aText(aWordBuf, rtl_str_getLength(aWordBuf), eEnc);
342 uno::Reference< XDictionaryEntry > xEntry =
343 new DicEntry( aText, bNegativ );
344 addEntry_Impl( xEntry, true );
345 }
346
347 pStream->ReadUInt16( nLen );
348 if (pStream->eof())
349 break;
350 if (ERRCODE_NONE != (nErr = pStream->GetError()))
351 return nErr;
352
353 if (nLen < BUFSIZE)
354 {
355 pStream->ReadBytes(aWordBuf, nLen);
356 if (ERRCODE_NONE != (nErr = pStream->GetError()))
357 return nErr;
358 }
359 else
360 return SVSTREAM_READ_ERROR;
361 *(aWordBuf + nLen) = 0;
362 }
363 }
364 else if (DIC_VERSION_7 == nDicVersion)
365 {
366 OStringBuffer aLine;
367
368 // remaining lines - stock strings (a [==] b)
369 while (pStream->ReadLine(aLine))
370 {
371 if (aLine.isEmpty() || aLine[0] == '#') // skip comments
372 continue;
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 );
377 }
378 }
379
380 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
381
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
385 bIsModified = false;
386
387 return pStream->GetError();
388}
389
390static OString formatForSave(const uno::Reference< XDictionaryEntry > &xEntry,
391 rtl_TextEncoding eEnc )
392{
393 OUStringBuffer aStr(xEntry->getDictionaryWord());
394
395 if (xEntry->isNegative() || !xEntry->getReplacementText().isEmpty())
396 {
397 aStr.append("==" + xEntry->getReplacementText());
398 }
399 return OUStringToOString(aStr, eEnc);
400}
401
403{
404 MutexGuard aGuard( GetLinguMutex() );
405
406 if (rURL.isEmpty())
407 return ERRCODE_NONE;
408 DBG_ASSERT(!INetURLObject( rURL ).HasError(), "lng : invalid URL");
409
410 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
411
412 // get XOutputStream stream
413 uno::Reference<io::XStream> xStream;
414 try
415 {
416 xStream = io::TempFile::create(xContext);
417 }
418 catch (const uno::Exception &)
419 {
420 DBG_ASSERT( false, "failed to get input stream" );
421 }
422 if (!xStream.is())
423 return ErrCode(sal_uInt32(-1));
424
425 std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
426
427 // Always write as the latest version, i.e. DIC_VERSION_7
428
429 rtl_TextEncoding eEnc = RTL_TEXTENCODING_UTF8;
430 pStream->WriteLine(pVerOOo7);
431 ErrCode nErr = pStream->GetError();
432 if (nErr != ERRCODE_NONE)
433 return nErr;
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() */
438 pStream->WriteLine("lang: <none>");
439 else
440 {
441 OString aLine = "lang: " + OUStringToOString(LanguageTag::convertToBcp47(nLanguage), eEnc);
442 pStream->WriteLine(aLine);
443 }
444 if (ERRCODE_NONE != (nErr = pStream->GetError()))
445 return nErr;
446 if (eDicType == DictionaryType_POSITIVE)
447 pStream->WriteLine("type: positive");
448 else
449 pStream->WriteLine("type: negative");
451 {
452 pStream->WriteLine(Concat2View("title: " + OUStringToOString(
453 // strip EXTENSION_FOR_TITLE_TEXT
454 aDicName.subView(0, aDicName.lastIndexOf(EXTENSION_FOR_TITLE_TEXT)), eEnc)));
455 }
456 if (ERRCODE_NONE != (nErr = pStream->GetError()))
457 return nErr;
458 pStream->WriteLine("---");
459 if (ERRCODE_NONE != (nErr = pStream->GetError()))
460 return nErr;
461 for (const Reference<XDictionaryEntry> & aEntrie : aEntries)
462 {
463 OString aOutStr = formatForSave(aEntrie, eEnc);
464 pStream->WriteLine (aOutStr);
465 if (ERRCODE_NONE != (nErr = pStream->GetError()))
466 return nErr;
467 }
468
469 try
470 {
471 pStream.reset();
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);
475 xSeek->seek(0);
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
480 }
481 catch (const uno::Exception &)
482 {
483 DBG_ASSERT( false, "failed to write stream" );
484 return ErrCode(sal_uInt32(-1));
485 }
486
487 return nErr;
488}
489
490void DictionaryNeo::launchEvent(sal_Int16 nEvent,
491 const uno::Reference< XDictionaryEntry >& xEntry)
492{
493 MutexGuard aGuard( GetLinguMutex() );
494
495 DictionaryEvent aEvt;
496 aEvt.Source = uno::Reference< XDictionary >( this );
497 aEvt.nEvent = nEvent;
498 aEvt.xDictionaryEntry = xEntry;
499
500 aDicEvtListeners.notifyEach( &XDictionaryEventListener::processDictionaryEvent, aEvt);
501}
502
503int DictionaryNeo::cmpDicEntry(std::u16string_view rWord1,
504 std::u16string_view rWord2,
505 bool bSimilarOnly)
506{
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
510
511 int nRes = 0;
512
513 sal_Int32 nLen1 = rWord1.size(),
514 nLen2 = rWord2.size();
515 if (bSimilarOnly)
516 {
517 const sal_Unicode cChar = '.';
518 if (nLen1 && cChar == rWord1[ nLen1 - 1 ])
519 nLen1--;
520 if (nLen2 && cChar == rWord2[ nLen2 - 1 ])
521 nLen2--;
522 }
523
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)
527 sal_Int32 nIdx1 = 0,
528 nIdx2 = 0,
529 nNumIgnChar1 = 0,
530 nNumIgnChar2 = 0;
531
532 bool IgnState;
533 sal_Int32 nDiff = 0;
534 sal_Unicode cChar1 = '\0';
535 sal_Unicode cChar2 = '\0';
536 do
537 {
538 // skip chars to be ignored
539 IgnState = false;
540 while (nIdx1 < nLen1)
541 {
542 cChar1 = rWord1[ nIdx1 ];
543 if (cChar1 != cIgnChar && cChar1 != cIgnBeg && !IgnState )
544 break;
545 if ( cChar1 == cIgnBeg )
546 IgnState = true;
547 else if (cChar1 == cIgnEnd)
548 IgnState = false;
549 nIdx1++;
550 nNumIgnChar1++;
551 }
552 IgnState = false;
553 while (nIdx2 < nLen2)
554 {
555 cChar2 = rWord2[ nIdx2 ];
556 if (cChar2 != cIgnChar && cChar2 != cIgnBeg && !IgnState )
557 break;
558 if ( cChar2 == cIgnBeg )
559 IgnState = true;
560 else if (cChar2 == cIgnEnd)
561 IgnState = false;
562 nIdx2++;
563 nNumIgnChar2++;
564 }
565
566 if (nIdx1 < nLen1 && nIdx2 < nLen2)
567 {
568 nDiff = cChar1 - cChar2;
569 if (nDiff)
570 break;
571 nIdx1++;
572 nIdx2++;
573 }
574 } while (nIdx1 < nLen1 && nIdx2 < nLen2);
575
576
577 if (nDiff)
578 nRes = nDiff;
579 else
580 { // the string with the smallest count of not ignored chars is the
581 // shorter one
582
583 // count remaining IgnChars
584 IgnState = false;
585 while (nIdx1 < nLen1 )
586 {
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++;
594 }
595 IgnState = false;
596 while (nIdx2 < nLen2 )
597 {
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++;
605 }
606
607 nRes = (nLen1 - nNumIgnChar1) - (nLen2 - nNumIgnChar2);
608 }
609
610 return nRes;
611}
612
613bool DictionaryNeo::seekEntry(std::u16string_view rWord,
614 sal_Int32 *pPos, bool bSimilarOnly)
615{
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
621
622 MutexGuard aGuard( GetLinguMutex() );
623
624 sal_Int32 nUpperIdx = getCount(),
625 nMidIdx,
626 nLowerIdx = 0;
627 if( nUpperIdx > 0 )
628 {
629 nUpperIdx--;
630 while( nLowerIdx <= nUpperIdx )
631 {
632 nMidIdx = (nLowerIdx + nUpperIdx) / 2;
633 DBG_ASSERT(aEntries[nMidIdx].is(), "lng : empty entry encountered");
634
635 int nCmp = - cmpDicEntry( aEntries[nMidIdx]->getDictionaryWord(),
636 rWord, bSimilarOnly );
637 if(nCmp == 0)
638 {
639 if( pPos ) *pPos = nMidIdx;
640 return true;
641 }
642 else if(nCmp > 0)
643 nLowerIdx = nMidIdx + 1;
644 else if( nMidIdx == 0 )
645 {
646 if( pPos ) *pPos = nLowerIdx;
647 return false;
648 }
649 else
650 nUpperIdx = nMidIdx - 1;
651 }
652 }
653 if( pPos ) *pPos = nLowerIdx;
654 return false;
655}
656
658{
659 bool bRes = true;
660
661 sal_Int32 nEntries = getCount();
662 sal_Int32 i;
663 for (i = 1; i < nEntries; i++)
664 {
665 if (cmpDicEntry( aEntries[i-1]->getDictionaryWord(),
666 aEntries[i]->getDictionaryWord() ) > 0)
667 {
668 bRes = false;
669 break;
670 }
671 }
672 return bRes;
673}
674
675bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry >& xDicEntry,
676 bool bIsLoadEntries)
677{
678 MutexGuard aGuard( GetLinguMutex() );
679
680 bool bRes = false;
681
682 if ( bIsLoadEntries || (!bIsReadonly && xDicEntry.is()) )
683 {
684 bool bIsNegEntry = xDicEntry->isNegative();
685 bool bAddEntry = !isFull() &&
686 ( ( eDicType == DictionaryType_POSITIVE && !bIsNegEntry )
687 || ( eDicType == DictionaryType_NEGATIVE && bIsNegEntry )
688 || ( eDicType == DictionaryType_MIXED ) );
689
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)
694 {
695 const bool bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
696 if (bFound)
697 bAddEntry = false;
698 }
699
700 if (bAddEntry)
701 {
702 DBG_ASSERT(!bNeedEntries, "lng : entries still not loaded");
703
704 // insert new entry at specified position
705 aEntries.insert(aEntries.begin() + nPos, xDicEntry);
706 SAL_WARN_IF(!isSorted(), "linguistic", "dictionary entries unsorted");
707
708 bIsModified = true;
709 bRes = true;
710
711 if (!bIsLoadEntries)
712 launchEvent( DictionaryEventFlags::ADD_ENTRY, xDicEntry );
713 }
714 }
715
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;
721 xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY );
723 if (xSpell.is() && (xSpell->isValid( SPELLML_SUPPORT, static_cast<sal_uInt16>(nLanguage), aEmptySeq )))
724 {
725 // "Grammar By" sample word is a Hunspell dictionary word?
726 if (xSpell->isValid( xDicEntry->getReplacementText(), static_cast<sal_uInt16>(nLanguage), aEmptySeq ))
727 {
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;
734 }
735 }
736
737 return bRes;
738}
739
740OUString SAL_CALL DictionaryNeo::getName( )
741{
742 MutexGuard aGuard( GetLinguMutex() );
743 return aDicName;
744}
745
746void SAL_CALL DictionaryNeo::setName( const OUString& aName )
747{
748 MutexGuard aGuard( GetLinguMutex() );
749
750 if (aDicName != aName)
751 {
752 aDicName = aName;
753 launchEvent(DictionaryEventFlags::CHG_NAME, nullptr);
754 }
755}
756
757DictionaryType SAL_CALL DictionaryNeo::getDictionaryType( )
758{
759 MutexGuard aGuard( GetLinguMutex() );
760
761 return eDicType;
762}
763
764void SAL_CALL DictionaryNeo::setActive( sal_Bool bActivate )
765{
766 MutexGuard aGuard( GetLinguMutex() );
767
768 if (bIsActive == bool(bActivate))
769 return;
770
771 bIsActive = bActivate;
772 sal_Int16 nEvent = bIsActive ?
773 DictionaryEventFlags::ACTIVATE_DIC : DictionaryEventFlags::DEACTIVATE_DIC;
774
775 // remove entries from memory if dictionary is deactivated
776 if (!bIsActive)
777 {
778 bool bIsEmpty = aEntries.empty();
779
780 // save entries first if necessary
781 if (bIsModified && hasLocation() && !isReadonly())
782 {
783 store();
784
785 aEntries.clear();
786 bNeedEntries = !bIsEmpty;
787 }
789 "lng : dictionary is still modified" );
790 }
791
792 launchEvent(nEvent, nullptr);
793}
794
796{
797 MutexGuard aGuard( GetLinguMutex() );
798 return bIsActive;
799}
800
801sal_Int32 SAL_CALL DictionaryNeo::getCount( )
802{
803 MutexGuard aGuard( GetLinguMutex() );
804
805 if (bNeedEntries)
807 return static_cast<sal_Int32>(aEntries.size());
808}
809
811{
812 MutexGuard aGuard( GetLinguMutex() );
814}
815
816void SAL_CALL DictionaryNeo::setLocale( const Locale& aLocale )
817{
818 MutexGuard aGuard( GetLinguMutex() );
819 LanguageType nLanguageP = LinguLocaleToLanguage( aLocale );
820 if (!bIsReadonly && nLanguage != nLanguageP)
821 {
822 nLanguage = nLanguageP;
823 bIsModified = true; // new language needs to be saved with dictionary
824
825 launchEvent( DictionaryEventFlags::CHG_LANGUAGE, nullptr );
826 }
827}
828
829uno::Reference< XDictionaryEntry > SAL_CALL DictionaryNeo::getEntry(
830 const OUString& aWord )
831{
832 MutexGuard aGuard( GetLinguMutex() );
833
834 if (bNeedEntries)
836
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");
840
841 return bFound ? aEntries[ nPos ]
842 : uno::Reference< XDictionaryEntry >();
843}
844
846 const uno::Reference< XDictionaryEntry >& xDicEntry )
847{
848 MutexGuard aGuard( GetLinguMutex() );
849
850 bool bRes = false;
851
852 if (!bIsReadonly)
853 {
854 if (bNeedEntries)
856 bRes = addEntry_Impl( xDicEntry );
857 }
858
859 return bRes;
860}
861
862sal_Bool SAL_CALL
863 DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
864 const OUString& rRplcText )
865{
866 MutexGuard aGuard( GetLinguMutex() );
867
868 bool bRes = false;
869
870 if (!bIsReadonly)
871 {
872 uno::Reference< XDictionaryEntry > xEntry =
873 new DicEntry( rWord, bIsNegative, rRplcText );
874 bRes = addEntry_Impl( xEntry );
875 }
876
877 return bRes;
878}
879
880sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
881{
882 MutexGuard aGuard( GetLinguMutex() );
883
884 bool bRemoved = false;
885
886 if (!bIsReadonly)
887 {
888 if (bNeedEntries)
890
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");
894
895 // remove element if found
896 if (bFound)
897 {
898 // entry to be removed
899 uno::Reference< XDictionaryEntry >
900 xDicEntry( aEntries[ nPos ] );
901 DBG_ASSERT(xDicEntry.is(), "lng : dictionary entry is NULL");
902
903 aEntries.erase(aEntries.begin() + nPos);
904
905 bRemoved = bIsModified = true;
906
907 launchEvent( DictionaryEventFlags::DEL_ENTRY, xDicEntry );
908 }
909 }
910
911 return bRemoved;
912}
913
915{
916 MutexGuard aGuard( GetLinguMutex() );
917
918 if (bNeedEntries)
920 return aEntries.size() >= DIC_MAX_ENTRIES;
921}
922
923uno::Sequence< uno::Reference< XDictionaryEntry > >
925{
926 MutexGuard aGuard( GetLinguMutex() );
927
928 if (bNeedEntries)
931}
932
933
934void SAL_CALL DictionaryNeo::clear( )
935{
936 MutexGuard aGuard( GetLinguMutex() );
937
938 if (!bIsReadonly && !aEntries.empty())
939 {
940 // release all references to old entries
941 aEntries.clear();
942
943 bNeedEntries = false;
944 bIsModified = true;
945
946 launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , nullptr );
947 }
948}
949
951 const uno::Reference< XDictionaryEventListener >& xListener )
952{
953 MutexGuard aGuard( GetLinguMutex() );
954
955 bool bRes = false;
956 if (xListener.is())
957 {
958 sal_Int32 nLen = aDicEvtListeners.getLength();
959 bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
960 }
961 return bRes;
962}
963
965 const uno::Reference< XDictionaryEventListener >& xListener )
966{
967 MutexGuard aGuard( GetLinguMutex() );
968
969 bool bRes = false;
970 if (xListener.is())
971 {
972 sal_Int32 nLen = aDicEvtListeners.getLength();
973 bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
974 }
975 return bRes;
976}
977
978
980{
981 MutexGuard aGuard( GetLinguMutex() );
982 return !aMainURL.isEmpty();
983}
984
985OUString SAL_CALL DictionaryNeo::getLocation()
986{
987 MutexGuard aGuard( GetLinguMutex() );
988 return aMainURL;
989}
990
992{
993 MutexGuard aGuard( GetLinguMutex() );
994
995 return bIsReadonly;
996}
997
998void SAL_CALL DictionaryNeo::store()
999{
1000 MutexGuard aGuard( GetLinguMutex() );
1001
1002 if (bIsModified && hasLocation() && !isReadonly())
1003 {
1004 if (!saveEntries( aMainURL ))
1005 bIsModified = false;
1006 }
1007}
1008
1010 const OUString& aURL,
1011 const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1012{
1013 MutexGuard aGuard( GetLinguMutex() );
1014
1015 if (!saveEntries( aURL ))
1016 {
1017 aMainURL = aURL;
1018 bIsModified = false;
1020 }
1021}
1022
1024 const OUString& aURL,
1025 const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1026{
1027 MutexGuard aGuard( GetLinguMutex() );
1029}
1030
1031
1032DicEntry::DicEntry(const OUString &rDicFileWord,
1033 bool bIsNegativWord)
1034{
1035 if (!rDicFileWord.isEmpty())
1036 splitDicFileWord( rDicFileWord, aDicWord, aReplacement );
1037 bIsNegativ = bIsNegativWord;
1038}
1039
1040DicEntry::DicEntry(OUString aDicWord_, bool bNegativ,
1041 OUString aRplcText_) :
1042 aDicWord (std::move(aDicWord_)),
1043 aReplacement (std::move(aRplcText_)),
1044 bIsNegativ (bNegativ)
1045{
1046}
1047
1049{
1050}
1051
1052void DicEntry::splitDicFileWord(const OUString &rDicFileWord,
1053 OUString &rDicWord,
1054 OUString &rReplacement)
1055{
1056 sal_Int32 nDelimPos = rDicFileWord.indexOf( "==" );
1057 if (-1 != nDelimPos)
1058 {
1059 sal_Int32 nTriplePos = nDelimPos + 2;
1060 if ( nTriplePos < rDicFileWord.getLength()
1061 && rDicFileWord[ nTriplePos ] == '=' )
1062 ++nDelimPos;
1063 rDicWord = rDicFileWord.copy( 0, nDelimPos );
1064 rReplacement = rDicFileWord.copy( nDelimPos + 2 );
1065 }
1066 else
1067 {
1068 rDicWord = rDicFileWord;
1069 rReplacement.clear();
1070 }
1071}
1072
1074{
1075 return aDicWord;
1076}
1077
1079{
1080 return bIsNegativ;
1081}
1082
1084{
1085 return aReplacement;
1086}
1087
1088
1089/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Reference< XInputStream > xStream
virtual ~DicEntry() override
Definition: dicimp.cxx:1048
bool bIsNegativ
Definition: dicimp.hxx:146
virtual sal_Bool SAL_CALL isNegative() override
Definition: dicimp.cxx:1078
OUString aDicWord
Definition: dicimp.hxx:144
OUString aReplacement
Definition: dicimp.hxx:145
DicEntry(const DicEntry &)=delete
virtual OUString SAL_CALL getDictionaryWord() override
Definition: dicimp.cxx:1073
virtual OUString SAL_CALL getReplacementText() override
Definition: dicimp.cxx:1083
static void splitDicFileWord(const OUString &rDicFileWord, OUString &rDicWord, OUString &rReplacement)
Definition: dicimp.cxx:1052
virtual sal_Bool SAL_CALL isFull() override
Definition: dicimp.cxx:914
bool seekEntry(std::u16string_view rWord, sal_Int32 *pPos, bool bSimilarOnly=false)
Definition: dicimp.cxx:613
static int cmpDicEntry(std::u16string_view rWord1, std::u16string_view rWord2, bool bSimilarOnly=false)
Definition: dicimp.cxx:503
OUString aDicName
Definition: dicimp.hxx:47
ErrCode loadEntries(const OUString &rMainURL)
Definition: dicimp.cxx:262
virtual sal_Bool SAL_CALL addEntry(const css::uno::Reference< css::linguistic2::XDictionaryEntry > &xDicEntry) override
Definition: dicimp.cxx:845
virtual sal_Bool SAL_CALL add(const OUString &aWord, sal_Bool bIsNegative, const OUString &aRplcText) override
Definition: dicimp.cxx:863
virtual void SAL_CALL store() override
Definition: dicimp.cxx:998
void launchEvent(sal_Int16 nEvent, const css::uno::Reference< css::linguistic2::XDictionaryEntry > &xEntry)
Definition: dicimp.cxx:490
virtual OUString SAL_CALL getLocation() override
Definition: dicimp.cxx:985
virtual css::uno::Reference< css::linguistic2::XDictionaryEntry > SAL_CALL getEntry(const OUString &aWord) override
Definition: dicimp.cxx:829
bool bIsActive
Definition: dicimp.hxx:54
bool addEntry_Impl(const css::uno::Reference< css::linguistic2::XDictionaryEntry > &rDicEntry, bool bIsLoadEntries=false)
Definition: dicimp.cxx:675
OUString aMainURL
Definition: dicimp.hxx:48
bool bIsReadonly
Definition: dicimp.hxx:55
virtual void SAL_CALL setActive(sal_Bool bActivate) override
Definition: dicimp.cxx:764
virtual void SAL_CALL setLocale(const css::lang::Locale &aLocale) override
Definition: dicimp.cxx:816
virtual sal_Bool SAL_CALL isReadonly() override
Definition: dicimp.cxx:991
bool bIsModified
Definition: dicimp.hxx:53
virtual sal_Bool SAL_CALL hasLocation() override
Definition: dicimp.cxx:979
ErrCode saveEntries(const OUString &rMainURL)
Definition: dicimp.cxx:402
virtual void SAL_CALL clear() override
Definition: dicimp.cxx:934
virtual void SAL_CALL storeAsURL(const OUString &aURL, const css::uno::Sequence< css::beans::PropertyValue > &aArgs) override
Definition: dicimp.cxx:1009
virtual css::lang::Locale SAL_CALL getLocale() override
Definition: dicimp.cxx:810
LanguageType nLanguage
Definition: dicimp.hxx:50
virtual sal_Bool SAL_CALL isActive() override
Definition: dicimp.cxx:795
virtual void SAL_CALL setName(const OUString &aName) override
Definition: dicimp.cxx:746
virtual sal_Bool SAL_CALL removeDictionaryEventListener(const css::uno::Reference< css::linguistic2::XDictionaryEventListener > &xListener) override
Definition: dicimp.cxx:964
virtual css::linguistic2::DictionaryType SAL_CALL getDictionaryType() override
Definition: dicimp.cxx:757
::comphelper::OInterfaceContainerHelper3< css::linguistic2::XDictionaryEventListener > aDicEvtListeners
Definition: dicimp.hxx:44
virtual sal_Bool SAL_CALL remove(const OUString &aWord) override
Definition: dicimp.cxx:880
virtual void SAL_CALL storeToURL(const OUString &aURL, const css::uno::Sequence< css::beans::PropertyValue > &aArgs) override
Definition: dicimp.cxx:1023
virtual OUString SAL_CALL getName() override
Definition: dicimp.cxx:740
DictionaryNeo(const DictionaryNeo &)=delete
css::linguistic2::DictionaryType eDicType
Definition: dicimp.hxx:49
std::vector< css::uno::Reference< css::linguistic2::XDictionaryEntry > > aEntries
Definition: dicimp.hxx:46
virtual ~DictionaryNeo() override
Definition: dicimp.cxx:258
virtual sal_Int32 SAL_CALL getCount() override
Definition: dicimp.cxx:801
virtual sal_Bool SAL_CALL addDictionaryEventListener(const css::uno::Reference< css::linguistic2::XDictionaryEventListener > &xListener) override
Definition: dicimp.cxx:950
bool bNeedEntries
Definition: dicimp.hxx:52
virtual css::uno::Sequence< css::uno::Reference< css::linguistic2::XDictionaryEntry > > SAL_CALL getEntries() override
Definition: dicimp.cxx:924
sal_Int16 nDicVersion
Definition: dicimp.hxx:51
bool isSorted()
Definition: dicimp.cxx:657
static css::lang::Locale convertToLocale(LanguageType nLangID, bool bResolveSystem=true)
static OUString convertToBcp47(LanguageType nLangID)
static LanguageType convertToLanguageType(const css::lang::Locale &rLocale, bool bResolveSystem=true)
SvStream & ReadCharAsBool(bool &rBool)
sal_uInt64 Tell() const
bool ReadLine(OStringBuffer &rStr, sal_Int32 nMaxBytesToRead=0xFFFE)
sal_uInt64 Seek(sal_uInt64 nPos)
std::size_t ReadBytes(void *pData, std::size_t nSize)
ErrCode GetError() const
SvStream & ReadUInt16(sal_uInt16 &rUInt16)
sal_Int32 addInterface(const css::uno::Reference< ListenerT > &rxIFace)
sal_Int32 removeInterface(const css::uno::Reference< ListenerT > &rxIFace)
void notifyEach(void(SAL_CALL ListenerT::*NotificationMethod)(const EventT &), const EventT &Event)
static std::unique_ptr< SvStream > CreateStream(const OUString &rFileName, StreamMode eOpenMode, css::uno::Reference< css::awt::XWindow > xParentWin=nullptr)
#define DBG_ASSERT(sCon, aError)
const char *const pVerOOo7
Definition: dicimp.cxx:81
const sal_Int16 DIC_VERSION_7
Definition: dicimp.cxx:87
static bool getTag(std::string_view rLine, std::string_view rTagName, OString &rTagValue)
Definition: dicimp.cxx:96
static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl()
Definition: dicimp.cxx:89
const sal_Int16 DIC_VERSION_2
Definition: dicimp.cxx:84
#define VERS2_NOLANGUAGE
Definition: dicimp.cxx:59
const sal_Int16 DIC_VERSION_6
Definition: dicimp.cxx:86
const char *const pVerStr2
Definition: dicimp.cxx:78
const sal_Int16 DIC_VERSION_DONTKNOW
Definition: dicimp.cxx:83
constexpr OUStringLiteral EXTENSION_FOR_TITLE_TEXT
Definition: dicimp.cxx:76
const char *const pVerStr6
Definition: dicimp.cxx:80
const sal_Int16 DIC_VERSION_5
Definition: dicimp.cxx:85
static OString formatForSave(const uno::Reference< XDictionaryEntry > &xEntry, rtl_TextEncoding eEnc)
Definition: dicimp.cxx:390
constexpr OUStringLiteral SPELLML_SUPPORT
Definition: dicimp.cxx:65
const char *const pVerStr5
Definition: dicimp.cxx:79
#define BUFSIZE
Definition: dicimp.cxx:58
#define MAX_HEADER_LENGTH
Definition: dicimp.cxx:61
sal_Int16 ReadDicVersion(SvStream &rStream, LanguageType &nLng, bool &bNeg, OUString &aDicName)
Definition: dicimp.cxx:109
#define DIC_MAX_ENTRIES
Definition: dicimp.hxx:32
URL aURL
float u
#define ERRCODE_NONE
#define SVSTREAM_READ_ERROR
DocumentType eType
OUString aName
#define LANGUAGE_NONE
sal_uInt16 nPos
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
aStr
OString strip(const OString &rIn, char c)
css::uno::Sequence< DstElementType > containerToSequence(const SrcType &i_Container)
Reference< XComponentContext > getProcessComponentContext()
OSQLColumns::const_iterator find(const OSQLColumns::const_iterator &first, const OSQLColumns::const_iterator &last, std::u16string_view _rVal, const ::comphelper::UStringMixEqual &_rCase)
int i
bool IsReadOnly(const OUString &rURL, bool *pbExist)
Definition: misc.cxx:385
bool FileExists(const OUString &rMainURL)
Definition: misc2.cxx:60
bool LinguIsUnspecified(LanguageType nLanguage)
Checks if a LanguageType is one of the values that denote absence of language or undetermined languag...
Definition: misc.cxx:88
osl::Mutex & GetLinguMutex()
! multi-thread safe mutex for all platforms !!
Definition: misc.cxx:60
LanguageType LinguLocaleToLanguage(const css::lang::Locale &rLocale)
Convert Locale to LanguageType for legacy handling.
Definition: misc.cxx:74
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
unsigned char sal_Bool
sal_uInt16 sal_Unicode