xref: /aoo42x/main/linguistic/source/dicimp.cxx (revision cdf0e10c)
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