xref: /trunk/main/linguistic/source/dicimp.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_linguistic.hxx"
30 
31 #include <cppuhelper/factory.hxx>
32 #include <dicimp.hxx>
33 #include <hyphdsp.hxx>
34 #include <i18npool/lang.h>
35 #include <i18npool/mslangid.hxx>
36 #include <osl/mutex.hxx>
37 #include <tools/debug.hxx>
38 #include <tools/fsys.hxx>
39 #include <tools/stream.hxx>
40 #include <tools/string.hxx>
41 #include <tools/urlobj.hxx>
42 #include <unotools/processfactory.hxx>
43 #include <unotools/ucbstreamhelper.hxx>
44 
45 #include <com/sun/star/ucb/XSimpleFileAccess.hpp>
46 #include <com/sun/star/linguistic2/DictionaryType.hpp>
47 #include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
48 #include <com/sun/star/registry/XRegistryKey.hpp>
49 #include <com/sun/star/io/XInputStream.hpp>
50 #include <com/sun/star/io/XOutputStream.hpp>
51 
52 #include "defs.hxx"
53 
54 
55 using namespace utl;
56 using namespace osl;
57 using namespace rtl;
58 using namespace com::sun::star;
59 using namespace com::sun::star::lang;
60 using namespace com::sun::star::uno;
61 using namespace com::sun::star::linguistic2;
62 using namespace linguistic;
63 
64 ///////////////////////////////////////////////////////////////////////////
65 
66 #define BUFSIZE             4096
67 #define VERS2_NOLANGUAGE    1024
68 
69 #define MAX_HEADER_LENGTH 16
70 
71 static const sal_Char*      pDicExt     = "dic";
72 static const sal_Char*      pVerStr2    = "WBSWG2";
73 static const sal_Char*      pVerStr5    = "WBSWG5";
74 static const sal_Char*      pVerStr6    = "WBSWG6";
75 static const sal_Char*      pVerOOo7    = "OOoUserDict1";
76 
77 static const sal_Int16 DIC_VERSION_DONTKNOW = -1;
78 static const sal_Int16 DIC_VERSION_2 = 2;
79 static const sal_Int16 DIC_VERSION_5 = 5;
80 static const sal_Int16 DIC_VERSION_6 = 6;
81 static const sal_Int16 DIC_VERSION_7 = 7;
82 
83 static sal_Bool getTag(const ByteString &rLine,
84         const sal_Char *pTagName, ByteString &rTagValue)
85 {
86     xub_StrLen nPos = rLine.Search( pTagName );
87     if (nPos == STRING_NOTFOUND)
88         return sal_False;
89 
90     rTagValue = rLine.Copy( nPos + sal::static_int_cast< xub_StrLen >(strlen( pTagName )) ).EraseLeadingAndTrailingChars();
91     return sal_True;
92 }
93 
94 
95 sal_Int16 ReadDicVersion( SvStreamPtr &rpStream, sal_uInt16 &nLng, sal_Bool &bNeg )
96 {
97     // Sniff the header
98     sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
99     sal_Char pMagicHeader[MAX_HEADER_LENGTH];
100 
101     nLng = LANGUAGE_NONE;
102     bNeg = sal_False;
103 
104     if (!rpStream.get() || rpStream->GetError())
105         return -1;
106 
107     sal_Size nSniffPos = rpStream->Tell();
108     static sal_Size nVerOOo7Len = sal::static_int_cast< sal_Size >(strlen( pVerOOo7 ));
109     pMagicHeader[ nVerOOo7Len ] = '\0';
110     if ((rpStream->Read((void *) pMagicHeader, nVerOOo7Len) == nVerOOo7Len) &&
111         !strcmp(pMagicHeader, pVerOOo7))
112     {
113         sal_Bool bSuccess;
114         ByteString aLine;
115 
116         nDicVersion = DIC_VERSION_7;
117 
118         // 1st skip magic / header line
119         rpStream->ReadLine(aLine);
120 
121         // 2nd line: language all | en-US | pt-BR ...
122         while (sal_True == (bSuccess = rpStream->ReadLine(aLine)))
123         {
124             ByteString aTagValue;
125 
126             if (aLine.GetChar(0) == '#') // skip comments
127                 continue;
128 
129             // lang: field
130             if (getTag(aLine, "lang: ", aTagValue))
131             {
132                 if (aTagValue == "<none>")
133                     nLng = LANGUAGE_NONE;
134                 else
135                     nLng = MsLangId::convertIsoStringToLanguage(OUString(aTagValue.GetBuffer(),
136                                 aTagValue.Len(), RTL_TEXTENCODING_ASCII_US));
137             }
138 
139             // type: negative / positive
140             if (getTag(aLine, "type: ", aTagValue))
141             {
142                 if (aTagValue == "negative")
143                     bNeg = sal_True;
144                 else
145                     bNeg = sal_False;
146             }
147 
148             if (aLine.Search ("---") != STRING_NOTFOUND) // end of header
149                 break;
150         }
151         if (!bSuccess)
152             return -2;
153     }
154     else
155     {
156         sal_uInt16 nLen;
157 
158         rpStream->Seek (nSniffPos );
159 
160         *rpStream >> nLen;
161         if (nLen >= MAX_HEADER_LENGTH)
162             return -1;
163 
164         rpStream->Read(pMagicHeader, nLen);
165         pMagicHeader[nLen] = '\0';
166 
167         // Check version magic
168         if (0 == strcmp( pMagicHeader, pVerStr6 ))
169             nDicVersion = DIC_VERSION_6;
170         else if (0 == strcmp( pMagicHeader, pVerStr5 ))
171             nDicVersion = DIC_VERSION_5;
172         else if (0 == strcmp( pMagicHeader, pVerStr2 ))
173             nDicVersion = DIC_VERSION_2;
174         else
175             nDicVersion = DIC_VERSION_DONTKNOW;
176 
177         if (DIC_VERSION_2 == nDicVersion ||
178             DIC_VERSION_5 == nDicVersion ||
179             DIC_VERSION_6 == nDicVersion)
180         {
181             // The language of the dictionary
182             *rpStream >> nLng;
183 
184             if (VERS2_NOLANGUAGE == nLng)
185                 nLng = LANGUAGE_NONE;
186 
187             // Negative Flag
188             sal_Char nTmp;
189             *rpStream >> nTmp;
190             bNeg = (sal_Bool)nTmp;
191         }
192     }
193 
194     return nDicVersion;
195 }
196 
197 
198 
199 const String GetDicExtension()
200 {
201     return String::CreateFromAscii( pDicExt );
202 }
203 
204 ///////////////////////////////////////////////////////////////////////////
205 
206 DictionaryNeo::DictionaryNeo() :
207     aDicEvtListeners( GetLinguMutex() ),
208     eDicType        (DictionaryType_POSITIVE),
209     nLanguage       (LANGUAGE_NONE)
210 {
211     nCount       = 0;
212     nDicVersion  = DIC_VERSION_DONTKNOW;
213     bNeedEntries = sal_False;
214     bIsModified  = bIsActive = sal_False;
215     bIsReadonly  = sal_False;
216 }
217 
218 DictionaryNeo::DictionaryNeo(const OUString &rName,
219                              sal_Int16 nLang, DictionaryType eType,
220                              const OUString &rMainURL,
221                              sal_Bool bWriteable) :
222     aDicEvtListeners( GetLinguMutex() ),
223     aDicName        (rName),
224     aMainURL        (rMainURL),
225     eDicType        (eType),
226     nLanguage       (nLang)
227 {
228     nCount       = 0;
229     nDicVersion  = DIC_VERSION_DONTKNOW;
230     bNeedEntries = sal_True;
231     bIsModified  = bIsActive = sal_False;
232     bIsReadonly = !bWriteable;
233 
234     if( rMainURL.getLength() > 0 )
235     {
236         sal_Bool bExists = FileExists( rMainURL );
237         if( !bExists )
238         {
239             // save new dictionaries with in Format 7 (UTF8 plain text)
240             nDicVersion  = DIC_VERSION_7;
241 
242             //! create physical representation of an **empty** dictionary
243             //! that could be found by the dictionary-list implementation
244             // (Note: empty dictionaries are not just empty files!)
245             DBG_ASSERT( !bIsReadonly,
246                     "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
247             if (!bIsReadonly)
248                 saveEntries( rMainURL );
249             bNeedEntries = sal_False;
250         }
251     }
252     else
253     {
254         // non persistent dictionaries (like IgnoreAllList) should always be writable
255         bIsReadonly  = sal_False;
256         bNeedEntries = sal_False;
257     }
258 }
259 
260 DictionaryNeo::~DictionaryNeo()
261 {
262 }
263 
264 sal_uLong DictionaryNeo::loadEntries(const OUString &rMainURL)
265 {
266     MutexGuard  aGuard( GetLinguMutex() );
267 
268     // counter check that it is safe to set bIsModified to sal_False at
269     // the end of the function
270     DBG_ASSERT(!bIsModified, "lng : dictionary already modified!");
271 
272     // function should only be called once in order to load entries from file
273     bNeedEntries = sal_False;
274 
275     if (rMainURL.getLength() == 0)
276         return 0;
277 
278     uno::Reference< lang::XMultiServiceFactory > xServiceFactory( utl::getProcessServiceFactory() );
279 
280     // get XInputStream stream
281     uno::Reference< io::XInputStream > xStream;
282     try
283     {
284         uno::Reference< ucb::XSimpleFileAccess > xAccess( xServiceFactory->createInstance(
285                 A2OU( "com.sun.star.ucb.SimpleFileAccess" ) ), uno::UNO_QUERY_THROW );
286         xStream = xAccess->openFileRead( rMainURL );
287     }
288     catch (uno::Exception & e)
289     {
290         DBG_ASSERT( 0, "failed to get input stream" );
291         (void) e;
292     }
293     if (!xStream.is())
294         return static_cast< sal_uLong >(-1);
295 
296     SvStreamPtr pStream = SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream ) );
297 
298     sal_uLong nErr = sal::static_int_cast< sal_uLong >(-1);
299 
300     // Header einlesen
301     sal_Bool bNegativ;
302     sal_uInt16 nLang;
303     nDicVersion = ReadDicVersion(pStream, nLang, bNegativ);
304     if (0 != (nErr = pStream->GetError()))
305         return nErr;
306 
307     nLanguage = nLang;
308 
309     eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
310 
311     rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
312     if (nDicVersion >= DIC_VERSION_6)
313         eEnc = RTL_TEXTENCODING_UTF8;
314     nCount = 0;
315 
316     if (DIC_VERSION_6 == nDicVersion ||
317         DIC_VERSION_5 == nDicVersion ||
318         DIC_VERSION_2 == nDicVersion)
319     {
320         sal_uInt16  nLen = 0;
321         sal_Char aWordBuf[ BUFSIZE ];
322 
323         // Das erste Wort einlesen
324         if (!pStream->IsEof())
325         {
326             *pStream >> nLen;
327             if (0 != (nErr = pStream->GetError()))
328                 return nErr;
329             if ( nLen < BUFSIZE )
330             {
331                 pStream->Read(aWordBuf, nLen);
332                 if (0 != (nErr = pStream->GetError()))
333                     return nErr;
334                 *(aWordBuf + nLen) = 0;
335             }
336         }
337 
338         while(!pStream->IsEof())
339         {
340             // Aus dem File einlesen
341             // Einfuegen ins Woerterbuch ohne Konvertierung
342             if(*aWordBuf)
343             {
344                 ByteString aDummy( aWordBuf );
345                 String aText( aDummy, eEnc );
346                 uno::Reference< XDictionaryEntry > xEntry =
347                         new DicEntry( aText, bNegativ );
348                 addEntry_Impl( xEntry , sal_True ); //! don't launch events here
349             }
350 
351             *pStream >> nLen;
352             if (pStream->IsEof())   // #75082# GPF in online-spelling
353                 break;
354             if (0 != (nErr = pStream->GetError()))
355                 return nErr;
356 #ifdef LINGU_EXCEPTIONS
357             if (nLen >= BUFSIZE)
358                 throw  io::IOException() ;
359 #endif
360 
361             if (nLen < BUFSIZE)
362             {
363                 pStream->Read(aWordBuf, nLen);
364                 if (0 != (nErr = pStream->GetError()))
365                     return nErr;
366             }
367             else
368                 return SVSTREAM_READ_ERROR;
369             *(aWordBuf + nLen) = 0;
370         }
371     }
372     else if (DIC_VERSION_7 == nDicVersion)
373     {
374         sal_Bool bSuccess;
375         ByteString aLine;
376 
377         // remaining lines - stock strings (a [==] b)
378         while (sal_True == (bSuccess = pStream->ReadLine(aLine)))
379         {
380             if (aLine.GetChar(0) == '#') // skip comments
381                 continue;
382             rtl::OUString aText = rtl::OStringToOUString (aLine, RTL_TEXTENCODING_UTF8);
383             uno::Reference< XDictionaryEntry > xEntry =
384                     new DicEntry( aText, eDicType == DictionaryType_NEGATIVE );
385             addEntry_Impl( xEntry , sal_True ); //! don't launch events here
386         }
387     }
388 
389     DBG_ASSERT(isSorted(), "lng : dictionary is not sorted");
390 
391     // since this routine should be called only initialy (prior to any
392     // modification to be saved) we reset the bIsModified flag here that
393     // was implicitly set by addEntry_Impl
394     bIsModified = sal_False;
395 
396     return pStream->GetError();
397 }
398 
399 
400 static ByteString formatForSave(
401         const uno::Reference< XDictionaryEntry > &xEntry, rtl_TextEncoding eEnc )
402 {
403    ByteString aStr(xEntry->getDictionaryWord().getStr(), eEnc);
404 
405    if (xEntry->isNegative())
406    {
407        aStr += "==";
408        aStr += ByteString(xEntry->getReplacementText().getStr(), eEnc);
409    }
410    return aStr;
411 }
412 
413 
414 sal_uLong DictionaryNeo::saveEntries(const OUString &rURL)
415 {
416     MutexGuard  aGuard( GetLinguMutex() );
417 
418     if (rURL.getLength() == 0)
419         return 0;
420     DBG_ASSERT(!INetURLObject( rURL ).HasError(), "lng : invalid URL");
421 
422     uno::Reference< lang::XMultiServiceFactory > xServiceFactory( utl::getProcessServiceFactory() );
423 
424     // get XOutputStream stream
425     uno::Reference< io::XStream > xStream;
426     try
427     {
428         uno::Reference< ucb::XSimpleFileAccess > xAccess( xServiceFactory->createInstance(
429                 A2OU( "com.sun.star.ucb.SimpleFileAccess" ) ), uno::UNO_QUERY_THROW );
430         xStream = xAccess->openFileReadWrite( rURL );
431     }
432     catch (uno::Exception & e)
433     {
434         DBG_ASSERT( 0, "failed to get input stream" );
435         (void) e;
436     }
437     if (!xStream.is())
438         return static_cast< sal_uLong >(-1);
439 
440     SvStreamPtr pStream = SvStreamPtr( utl::UcbStreamHelper::CreateStream( xStream ) );
441     sal_uLong nErr = sal::static_int_cast< sal_uLong >(-1);
442 
443     //
444     // Always write as the latest version, i.e. DIC_VERSION_7
445     //
446     rtl_TextEncoding eEnc = RTL_TEXTENCODING_UTF8;
447     pStream->WriteLine(ByteString (pVerOOo7));
448     if (0 != (nErr = pStream->GetError()))
449         return nErr;
450     if (nLanguage == LANGUAGE_NONE)
451         pStream->WriteLine(ByteString("lang: <none>"));
452     else
453     {
454         ByteString aLine("lang: ");
455         aLine += ByteString( String( MsLangId::convertLanguageToIsoString( nLanguage ) ), eEnc);
456         pStream->WriteLine( aLine );
457     }
458     if (0 != (nErr = pStream->GetError()))
459         return nErr;
460     if (eDicType == DictionaryType_POSITIVE)
461         pStream->WriteLine(ByteString("type: positive"));
462     else
463         pStream->WriteLine(ByteString("type: negative"));
464     if (0 != (nErr = pStream->GetError()))
465         return nErr;
466     pStream->WriteLine(ByteString("---"));
467     if (0 != (nErr = pStream->GetError()))
468         return nErr;
469     const uno::Reference< XDictionaryEntry > *pEntry = aEntries.getConstArray();
470     for (sal_Int32 i = 0;  i < nCount;  i++)
471     {
472         ByteString aOutStr = formatForSave(pEntry[i], eEnc);
473         pStream->WriteLine (aOutStr);
474         if (0 != (nErr = pStream->GetError()))
475             return nErr;
476     }
477 
478     //If we are migrating from an older version, then on first successful
479     //write, we're now converted to the latest version, i.e. DIC_VERSION_7
480     nDicVersion = DIC_VERSION_7;
481 
482     return nErr;
483 }
484 
485 void DictionaryNeo::launchEvent(sal_Int16 nEvent,
486                                 uno::Reference< XDictionaryEntry > xEntry)
487 {
488     MutexGuard  aGuard( GetLinguMutex() );
489 
490     DictionaryEvent aEvt;
491     aEvt.Source = uno::Reference< XDictionary >( this );
492     aEvt.nEvent = nEvent;
493     aEvt.xDictionaryEntry = xEntry;
494 
495     cppu::OInterfaceIteratorHelper aIt( aDicEvtListeners );
496     while (aIt.hasMoreElements())
497     {
498         uno::Reference< XDictionaryEventListener > xRef( aIt.next(), UNO_QUERY );
499         if (xRef.is())
500             xRef->processDictionaryEvent( aEvt );
501     }
502 }
503 
504 int DictionaryNeo::cmpDicEntry(const OUString& rWord1,
505                                const OUString &rWord2,
506                                sal_Bool bSimilarOnly)
507 {
508     MutexGuard  aGuard( GetLinguMutex() );
509 
510     // returns 0 if rWord1 is equal to rWord2
511     //   "     a value < 0 if rWord1 is less than rWord2
512     //   "     a value > 0 if rWord1 is greater than rWord2
513 
514     int nRes = 0;
515 
516     OUString    aWord1( rWord1 ),
517                 aWord2( rWord2 );
518     sal_Int32       nLen1 = aWord1.getLength(),
519                 nLen2 = aWord2.getLength();
520     if (bSimilarOnly)
521     {
522         const sal_Unicode cChar = '.';
523         if (nLen1  &&  cChar == aWord1[ nLen1 - 1 ])
524             nLen1--;
525         if (nLen2  &&  cChar == aWord2[ nLen2 - 1 ])
526             nLen2--;
527     }
528 
529     const sal_Unicode cIgnChar = '=';
530     sal_Int32       nIdx1 = 0,
531                 nIdx2 = 0,
532                 nNumIgnChar1 = 0,
533                 nNumIgnChar2 = 0;
534 
535     sal_Int32 nDiff = 0;
536     sal_Unicode cChar1 = '\0';
537     sal_Unicode cChar2 = '\0';
538     do
539     {
540         // skip chars to be ignored
541         while (nIdx1 < nLen1  &&  (cChar1 = aWord1[ nIdx1 ]) == cIgnChar)
542         {
543             nIdx1++;
544             nNumIgnChar1++;
545         }
546         while (nIdx2 < nLen2  &&  (cChar2 = aWord2[ nIdx2 ]) == cIgnChar)
547         {
548             nIdx2++;
549             nNumIgnChar2++;
550         }
551 
552         if (nIdx1 < nLen1  &&  nIdx2 < nLen2)
553         {
554             nDiff = cChar1 - cChar2;
555             if (nDiff)
556                 break;
557             nIdx1++;
558             nIdx2++;
559         }
560     } while (nIdx1 < nLen1  &&  nIdx2 < nLen2);
561 
562 
563     if (nDiff)
564         nRes = nDiff;
565     else
566     {   // the string with the smallest count of not ignored chars is the
567         // shorter one
568 
569         // count remaining IgnChars
570         while (nIdx1 < nLen1 )
571         {
572             if (aWord1[ nIdx1++ ] == cIgnChar)
573                 nNumIgnChar1++;
574         }
575         while (nIdx2 < nLen2 )
576         {
577             if (aWord2[ nIdx2++ ] == cIgnChar)
578                 nNumIgnChar2++;
579         }
580 
581         nRes = ((sal_Int32) nLen1 - nNumIgnChar1) - ((sal_Int32) nLen2 - nNumIgnChar2);
582     }
583 
584     return nRes;
585 }
586 
587 sal_Bool DictionaryNeo::seekEntry(const OUString &rWord,
588                               sal_Int32 *pPos, sal_Bool bSimilarOnly)
589 {
590     // look for entry with binary search.
591     // return sal_True if found sal_False else.
592     // if pPos != NULL it will become the position of the found entry, or
593     // if that was not found the position where it has to be inserted
594     // to keep the entries sorted
595 
596     MutexGuard  aGuard( GetLinguMutex() );
597 
598     const uno::Reference< XDictionaryEntry > *pEntry = aEntries.getConstArray();
599     sal_Int32 nUpperIdx = getCount(),
600           nMidIdx,
601           nLowerIdx = 0;
602     if( nUpperIdx > 0 )
603     {
604         nUpperIdx--;
605         while( nLowerIdx <= nUpperIdx )
606         {
607             nMidIdx = (nLowerIdx + nUpperIdx) / 2;
608             DBG_ASSERT(pEntry[nMidIdx].is(), "lng : empty entry encountered");
609 
610             int nCmp = - cmpDicEntry( pEntry[nMidIdx]->getDictionaryWord(),
611                                       rWord, bSimilarOnly );
612             if(nCmp == 0)
613             {
614                 if( pPos ) *pPos = nMidIdx;
615                 return sal_True;
616             }
617             else if(nCmp > 0)
618                 nLowerIdx = nMidIdx + 1;
619             else if( nMidIdx == 0 )
620             {
621                 if( pPos ) *pPos = nLowerIdx;
622                 return sal_False;
623             }
624             else
625                 nUpperIdx = nMidIdx - 1;
626         }
627     }
628     if( pPos ) *pPos = nLowerIdx;
629     return sal_False;
630 }
631 
632 sal_Bool DictionaryNeo::isSorted()
633 {
634     sal_Bool bRes = sal_True;
635 
636     const uno::Reference< XDictionaryEntry > *pEntry = aEntries.getConstArray();
637     sal_Int32 nEntries = getCount();
638     sal_Int32 i;
639     for (i = 1;  i < nEntries;  i++)
640     {
641         if (cmpDicEntry( pEntry[i-1]->getDictionaryWord(),
642                          pEntry[i]->getDictionaryWord() ) > 0)
643         {
644             bRes = sal_False;
645             break;
646         }
647     }
648     return bRes;
649 }
650 
651 sal_Bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry > xDicEntry,
652         sal_Bool bIsLoadEntries)
653 {
654     MutexGuard  aGuard( GetLinguMutex() );
655 
656     sal_Bool bRes = sal_False;
657 
658     if ( bIsLoadEntries || (!bIsReadonly  &&  xDicEntry.is()) )
659     {
660         sal_Bool bIsNegEntry = xDicEntry->isNegative();
661         sal_Bool bAddEntry   = !isFull() &&
662                    (   ( eDicType == DictionaryType_POSITIVE && !bIsNegEntry )
663                     || ( eDicType == DictionaryType_NEGATIVE &&  bIsNegEntry )
664                     || ( eDicType == DictionaryType_MIXED ) );
665 
666         // look for position to insert entry at
667         // if there is already an entry do not insert the new one
668         sal_Int32 nPos = 0;
669         sal_Bool bFound = sal_False;
670         if (bAddEntry)
671         {
672             bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
673             if (bFound)
674                 bAddEntry = sal_False;
675         }
676 
677         if (bAddEntry)
678         {
679             DBG_ASSERT(!bNeedEntries, "lng : entries still not loaded");
680 
681             if (nCount >= aEntries.getLength())
682                 aEntries.realloc( Max(2 * nCount, nCount + 32) );
683             uno::Reference< XDictionaryEntry > *pEntry = aEntries.getArray();
684 
685             // shift old entries right
686             sal_Int32 i;
687             for (i = nCount - 1; i >= nPos;  i--)
688                 pEntry[ i+1 ] = pEntry[ i ];
689             // insert new entry at specified position
690             pEntry[ nPos ] = xDicEntry;
691             DBG_ASSERT(isSorted(), "lng : dictionary entries unsorted");
692 
693             nCount++;
694 
695             bIsModified = sal_True;
696             bRes = sal_True;
697 
698             if (!bIsLoadEntries)
699                 launchEvent( DictionaryEventFlags::ADD_ENTRY, xDicEntry );
700         }
701     }
702 
703     return bRes;
704 }
705 
706 
707 uno::Reference< XInterface > SAL_CALL DictionaryNeo_CreateInstance(
708             const uno::Reference< XMultiServiceFactory > & /*rSMgr*/ )
709         throw(Exception)
710 {
711     uno::Reference< XInterface > xService =
712             (cppu::OWeakObject*) new DictionaryNeo;
713     return xService;
714 }
715 
716 OUString SAL_CALL DictionaryNeo::getName(  )
717         throw(RuntimeException)
718 {
719     MutexGuard  aGuard( GetLinguMutex() );
720     return aDicName;
721 }
722 
723 void SAL_CALL DictionaryNeo::setName( const OUString& aName )
724         throw(RuntimeException)
725 {
726     MutexGuard  aGuard( GetLinguMutex() );
727 
728     if (aDicName != aName)
729     {
730         aDicName = aName;
731         launchEvent(DictionaryEventFlags::CHG_NAME, NULL);
732     }
733 }
734 
735 DictionaryType SAL_CALL DictionaryNeo::getDictionaryType(  )
736         throw(RuntimeException)
737 {
738     MutexGuard  aGuard( GetLinguMutex() );
739 
740     return eDicType;
741 }
742 
743 void SAL_CALL DictionaryNeo::setActive( sal_Bool bActivate )
744         throw(RuntimeException)
745 {
746     MutexGuard  aGuard( GetLinguMutex() );
747 
748     if (bIsActive != bActivate)
749     {
750         bIsActive = bActivate != 0;
751         sal_Int16 nEvent = bIsActive ?
752                 DictionaryEventFlags::ACTIVATE_DIC : DictionaryEventFlags::DEACTIVATE_DIC;
753 
754         // remove entries from memory if dictionary is deactivated
755         if (bIsActive == sal_False)
756         {
757             sal_Bool bIsEmpty = nCount == 0;
758 
759             // save entries first if necessary
760             if (bIsModified && hasLocation() && !isReadonly())
761             {
762                 store();
763 
764                 aEntries.realloc( 0 );
765                 nCount = 0;
766                 bNeedEntries = !bIsEmpty;
767             }
768             DBG_ASSERT( !bIsModified || !hasLocation() || isReadonly(),
769                     "lng : dictionary is still modified" );
770         }
771 
772         launchEvent(nEvent, NULL);
773     }
774 }
775 
776 sal_Bool SAL_CALL DictionaryNeo::isActive(  )
777         throw(RuntimeException)
778 {
779     MutexGuard  aGuard( GetLinguMutex() );
780     return bIsActive;
781 }
782 
783 sal_Int32 SAL_CALL DictionaryNeo::getCount(  )
784         throw(RuntimeException)
785 {
786     MutexGuard  aGuard( GetLinguMutex() );
787 
788     if (bNeedEntries)
789         loadEntries( aMainURL );
790     return nCount;
791 }
792 
793 Locale SAL_CALL DictionaryNeo::getLocale(  )
794         throw(RuntimeException)
795 {
796     MutexGuard  aGuard( GetLinguMutex() );
797     Locale aRes;
798     return LanguageToLocale( aRes, nLanguage );
799 }
800 
801 void SAL_CALL DictionaryNeo::setLocale( const Locale& aLocale )
802         throw(RuntimeException)
803 {
804     MutexGuard  aGuard( GetLinguMutex() );
805     sal_Int16 nLanguageP = LocaleToLanguage( aLocale );
806     if (!bIsReadonly  &&  nLanguage != nLanguageP)
807     {
808         nLanguage = nLanguageP;
809         bIsModified = sal_True; // new language needs to be saved with dictionary
810 
811         launchEvent( DictionaryEventFlags::CHG_LANGUAGE, NULL );
812     }
813 }
814 
815 uno::Reference< XDictionaryEntry > SAL_CALL DictionaryNeo::getEntry(
816             const OUString& aWord )
817         throw(RuntimeException)
818 {
819     MutexGuard  aGuard( GetLinguMutex() );
820 
821     if (bNeedEntries)
822         loadEntries( aMainURL );
823 
824     sal_Int32 nPos;
825     sal_Bool bFound = seekEntry( aWord, &nPos, sal_True );
826     DBG_ASSERT( nCount <= aEntries.getLength(), "lng : wrong number of entries");
827     DBG_ASSERT(!bFound || nPos < nCount, "lng : index out of range");
828 
829     return bFound ? aEntries.getConstArray()[ nPos ]
830                     : uno::Reference< XDictionaryEntry >();
831 }
832 
833 sal_Bool SAL_CALL DictionaryNeo::addEntry(
834             const uno::Reference< XDictionaryEntry >& xDicEntry )
835         throw(RuntimeException)
836 {
837     MutexGuard  aGuard( GetLinguMutex() );
838 
839     sal_Bool bRes = sal_False;
840 
841     if (!bIsReadonly)
842     {
843         if (bNeedEntries)
844             loadEntries( aMainURL );
845         bRes = addEntry_Impl( xDicEntry );
846     }
847 
848     return bRes;
849 }
850 
851 sal_Bool SAL_CALL
852     DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
853             const OUString& rRplcText )
854         throw(RuntimeException)
855 {
856     MutexGuard  aGuard( GetLinguMutex() );
857 
858     sal_Bool bRes = sal_False;
859 
860     if (!bIsReadonly)
861     {
862         uno::Reference< XDictionaryEntry > xEntry =
863                 new DicEntry( rWord, bIsNegative, rRplcText );
864         bRes = addEntry_Impl( xEntry );
865     }
866 
867     return bRes;
868 }
869 
870 void lcl_SequenceRemoveElementAt(
871             uno::Sequence< uno::Reference< XDictionaryEntry > >& rEntries, int nPos )
872 {
873     //TODO: helper for SequenceRemoveElementAt available?
874     if(nPos >= rEntries.getLength())
875         return;
876     uno::Sequence< uno::Reference< XDictionaryEntry > > aTmp(rEntries.getLength() - 1);
877     uno::Reference< XDictionaryEntry > * pOrig = rEntries.getArray();
878     uno::Reference< XDictionaryEntry > * pTemp = aTmp.getArray();
879     int nOffset = 0;
880     for(int i = 0; i < aTmp.getLength(); i++)
881     {
882         if(nPos == i)
883             nOffset++;
884         pTemp[i] = pOrig[i + nOffset];
885     }
886 
887     rEntries = aTmp;
888 }
889 
890 sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
891         throw(RuntimeException)
892 {
893     MutexGuard  aGuard( GetLinguMutex() );
894 
895     sal_Bool bRemoved = sal_False;
896 
897     if (!bIsReadonly)
898     {
899         if (bNeedEntries)
900             loadEntries( aMainURL );
901 
902         sal_Int32 nPos;
903         sal_Bool bFound = seekEntry( aWord, &nPos );
904         DBG_ASSERT( nCount < aEntries.getLength(),
905                 "lng : wrong number of entries");
906         DBG_ASSERT(!bFound || nPos < nCount, "lng : index out of range");
907 
908         // remove element if found
909         if (bFound)
910         {
911             // entry to be removed
912             uno::Reference< XDictionaryEntry >
913                     xDicEntry( aEntries.getConstArray()[ nPos ] );
914             DBG_ASSERT(xDicEntry.is(), "lng : dictionary entry is NULL");
915 
916             nCount--;
917 
918             //! the following call reduces the length of the sequence by 1 also
919             lcl_SequenceRemoveElementAt( aEntries, nPos );
920             bRemoved = bIsModified = sal_True;
921 
922             launchEvent( DictionaryEventFlags::DEL_ENTRY, xDicEntry );
923         }
924     }
925 
926     return bRemoved;
927 }
928 
929 sal_Bool SAL_CALL DictionaryNeo::isFull(  )
930         throw(RuntimeException)
931 {
932     MutexGuard  aGuard( GetLinguMutex() );
933 
934     if (bNeedEntries)
935         loadEntries( aMainURL );
936     return nCount >= DIC_MAX_ENTRIES;
937 }
938 
939 uno::Sequence< uno::Reference< XDictionaryEntry > >
940     SAL_CALL DictionaryNeo::getEntries(  )
941         throw(RuntimeException)
942 {
943     MutexGuard  aGuard( GetLinguMutex() );
944 
945     if (bNeedEntries)
946         loadEntries( aMainURL );
947     //! return sequence with length equal to the number of dictionary entries
948     //! (internal used sequence may have additional unused elements.)
949     return uno::Sequence< uno::Reference< XDictionaryEntry > >
950         (aEntries.getConstArray(), nCount);
951 }
952 
953 
954 void SAL_CALL DictionaryNeo::clear(  )
955         throw(RuntimeException)
956 {
957     MutexGuard  aGuard( GetLinguMutex() );
958 
959     if (!bIsReadonly && nCount)
960     {
961         // release all references to old entries and provide space for new ones
962         aEntries = uno::Sequence< uno::Reference< XDictionaryEntry > > ( 32 );
963 
964         nCount = 0;
965         bNeedEntries = sal_False;
966         bIsModified = sal_True;
967 
968         launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , NULL );
969     }
970 }
971 
972 sal_Bool SAL_CALL DictionaryNeo::addDictionaryEventListener(
973             const uno::Reference< XDictionaryEventListener >& xListener )
974         throw(RuntimeException)
975 {
976     MutexGuard  aGuard( GetLinguMutex() );
977 
978     sal_Bool bRes = sal_False;
979     if (xListener.is())
980     {
981         sal_Int32   nLen = aDicEvtListeners.getLength();
982         bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
983     }
984     return bRes;
985 }
986 
987 sal_Bool SAL_CALL DictionaryNeo::removeDictionaryEventListener(
988             const uno::Reference< XDictionaryEventListener >& xListener )
989         throw(RuntimeException)
990 {
991     MutexGuard  aGuard( GetLinguMutex() );
992 
993     sal_Bool bRes = sal_False;
994     if (xListener.is())
995     {
996         sal_Int32   nLen = aDicEvtListeners.getLength();
997         bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
998     }
999     return bRes;
1000 }
1001 
1002 
1003 sal_Bool SAL_CALL DictionaryNeo::hasLocation()
1004         throw(RuntimeException)
1005 {
1006     MutexGuard  aGuard( GetLinguMutex() );
1007     return aMainURL.getLength() > 0;
1008 }
1009 
1010 OUString SAL_CALL DictionaryNeo::getLocation()
1011         throw(RuntimeException)
1012 {
1013     MutexGuard  aGuard( GetLinguMutex() );
1014     return aMainURL;
1015 }
1016 
1017 sal_Bool SAL_CALL DictionaryNeo::isReadonly()
1018         throw(RuntimeException)
1019 {
1020     MutexGuard  aGuard( GetLinguMutex() );
1021 
1022     return bIsReadonly;
1023 }
1024 
1025 void SAL_CALL DictionaryNeo::store()
1026         throw(io::IOException, RuntimeException)
1027 {
1028     MutexGuard  aGuard( GetLinguMutex() );
1029 
1030     if (bIsModified && hasLocation() && !isReadonly())
1031     {
1032         if (saveEntries( aMainURL ))
1033         {
1034 #ifdef LINGU_EXCEPTIONS
1035             throw io::IOException();
1036 #endif
1037         }
1038         else
1039             bIsModified = sal_False;
1040     }
1041 }
1042 
1043 void SAL_CALL DictionaryNeo::storeAsURL(
1044             const OUString& aURL,
1045             const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1046         throw(io::IOException, RuntimeException)
1047 {
1048     MutexGuard  aGuard( GetLinguMutex() );
1049 
1050     if (saveEntries( aURL ))
1051     {
1052 #ifdef LINGU_EXCEPTIONS
1053         throw io::IOException();
1054 #endif
1055     }
1056     else
1057     {
1058         aMainURL = aURL;
1059         bIsModified = sal_False;
1060         bIsReadonly = IsReadOnly( getLocation() );
1061     }
1062 }
1063 
1064 void SAL_CALL DictionaryNeo::storeToURL(
1065             const OUString& aURL,
1066             const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
1067         throw(io::IOException, RuntimeException)
1068 {
1069     MutexGuard  aGuard( GetLinguMutex() );
1070 
1071     if (saveEntries( aURL ))
1072     {
1073 #ifdef LINGU_EXCEPTIONS
1074         throw io::IOException();
1075 #endif
1076     }
1077 }
1078 
1079 ///////////////////////////////////////////////////////////////////////////
1080 
1081 DicEntry::DicEntry()
1082 {
1083     bIsNegativ = sal_False;
1084 }
1085 
1086 DicEntry::DicEntry(const OUString &rDicFileWord,
1087                    sal_Bool bIsNegativWord)
1088 {
1089     if (rDicFileWord.getLength())
1090         splitDicFileWord( rDicFileWord, aDicWord, aReplacement );
1091     bIsNegativ = bIsNegativWord;
1092 }
1093 
1094 DicEntry::DicEntry(const OUString &rDicWord, sal_Bool bNegativ,
1095                    const OUString &rRplcText) :
1096     aDicWord                (rDicWord),
1097     aReplacement            (rRplcText),
1098     bIsNegativ              (bNegativ)
1099 {
1100 }
1101 
1102 DicEntry::~DicEntry()
1103 {
1104 }
1105 
1106 void DicEntry::splitDicFileWord(const OUString &rDicFileWord,
1107                                 OUString &rDicWord,
1108                                 OUString &rReplacement)
1109 {
1110     MutexGuard  aGuard( GetLinguMutex() );
1111 
1112     static const OUString aDelim( A2OU( "==" ) );
1113 
1114     sal_Int32 nDelimPos = rDicFileWord.indexOf( aDelim );
1115     if (-1 != nDelimPos)
1116     {
1117         sal_Int32 nTriplePos = nDelimPos + 2;
1118         if (    nTriplePos < rDicFileWord.getLength()
1119             &&  rDicFileWord[ nTriplePos ] == '=' )
1120             ++nDelimPos;
1121         rDicWord     = rDicFileWord.copy( 0, nDelimPos );
1122         rReplacement = rDicFileWord.copy( nDelimPos + 2 );
1123     }
1124     else
1125     {
1126         rDicWord     = rDicFileWord;
1127         rReplacement = OUString();
1128     }
1129 }
1130 
1131 OUString SAL_CALL DicEntry::getDictionaryWord(  )
1132         throw(RuntimeException)
1133 {
1134     MutexGuard  aGuard( GetLinguMutex() );
1135     return aDicWord;
1136 }
1137 
1138 sal_Bool SAL_CALL DicEntry::isNegative(  )
1139         throw(RuntimeException)
1140 {
1141     MutexGuard  aGuard( GetLinguMutex() );
1142     return bIsNegativ;
1143 }
1144 
1145 OUString SAL_CALL DicEntry::getReplacementText(  )
1146         throw(RuntimeException)
1147 {
1148     MutexGuard  aGuard( GetLinguMutex() );
1149     return aReplacement;
1150 }
1151 
1152 
1153 ///////////////////////////////////////////////////////////////////////////
1154 
1155