Coverage Report

Created: 2025-12-08 09:28

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