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