xref: /AOO41X/main/lingucomponent/source/spellcheck/macosxspell/macspellimp.cxx (revision e8c8fa4bdcac50a8fe6c60960dd164b285c48c7e)
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_lingucomponent.hxx"
26 #include <com/sun/star/uno/Reference.h>
27 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
28 
29 #include <com/sun/star/linguistic2/SpellFailure.hpp>
30 #include <cppuhelper/factory.hxx>	// helper for factories
31 #include <com/sun/star/registry/XRegistryKey.hpp>
32 #include <tools/debug.hxx>
33 #include <unotools/processfactory.hxx>
34 #include <osl/mutex.hxx>
35 
36 //#include <hunspell.hxx>
37 #include <dictmgr.hxx>
38 #include <macspellimp.hxx>
39 
40 //#include <linguistic/lngprops.hxx>
41 #include <linguistic/spelldta.hxx>
42 #include <unotools/pathoptions.hxx>
43 #include <unotools/useroptions.hxx>
44 #include <osl/file.hxx>
45 #include <rtl/ustrbuf.hxx>
46 
47 
48 using namespace utl;
49 using namespace osl;
50 using namespace rtl;
51 using namespace com::sun::star;
52 using namespace com::sun::star::beans;
53 using namespace com::sun::star::lang;
54 using namespace com::sun::star::uno;
55 using namespace com::sun::star::linguistic2;
56 using namespace linguistic;
57 ///////////////////////////////////////////////////////////////////////////
58 // dbg_dump for development
59 #if OSL_DEBUG_LEVEL > 1
60 #include <rtl/strbuf.hxx>
61 #include <rtl/ustring.hxx>
62 
63 const sal_Char *dbg_dump(const rtl::OString &rStr)
64 {
65     static rtl::OStringBuffer aStr;
66 
67     aStr = rtl::OStringBuffer(rStr);
68     aStr.append(static_cast<char>(0));
69     return aStr.getStr();
70 }
71 
72 const sal_Char *dbg_dump(const rtl::OUString &rStr)
73 {
74     return dbg_dump(rtl::OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
75 }
76 
77 const sal_Char *dbg_dump(rtl_String *pStr)
78 {
79     return dbg_dump(rtl::OString(pStr));
80 }
81 
82 const sal_Char *dbg_dump(rtl_uString *pStr)
83 {
84     return dbg_dump(rtl::OUString(pStr));
85 }
86 
87 #endif
88 ///////////////////////////////////////////////////////////////////////////
89 
90 MacSpellChecker::MacSpellChecker() :
91 	aEvtListeners	( GetLinguMutex() )
92 {
93 //    aDicts = NULL;
94 	aDEncs = NULL;
95 	aDLocs = NULL;
96 	aDNames = NULL;
97 	bDisposing = sal_False;
98 	pPropHelper = NULL;
99     numdict = 0;
100     NSApplicationLoad();
101 	NSAutoreleasePool* pool	= [[NSAutoreleasePool alloc] init];
102     macSpell = [NSSpellChecker sharedSpellChecker];
103     macTag = [NSSpellChecker uniqueSpellDocumentTag];
104     [pool release];
105 }
106 
107 
108 MacSpellChecker::~MacSpellChecker()
109 {
110   // if (aDicts) {
111   //    for (int i = 0; i < numdict; i++) {
112   //           if (aDicts[i]) delete aDicts[i];
113   //           aDicts[i] = NULL;
114   //    }
115   //    delete[] aDicts;
116   // }
117   // aDicts = NULL;
118   numdict = 0;
119   if (aDEncs) delete[] aDEncs;
120   aDEncs = NULL;
121   if (aDLocs) delete[] aDLocs;
122   aDLocs = NULL;
123   if (aDNames) delete[] aDNames;
124   aDNames = NULL;
125   if (pPropHelper)
126 	 pPropHelper->RemoveAsPropListener();
127 }
128 
129 
130 PropertyHelper_Spell & MacSpellChecker::GetPropHelper_Impl()
131 {
132 	if (!pPropHelper)
133 	{
134 		Reference< XPropertySet	>	xPropSet( GetLinguProperties(), UNO_QUERY );
135 
136 		pPropHelper	= new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
137 		xPropHelper = pPropHelper;
138 		pPropHelper->AddAsPropListener();	//! after a reference is established
139 	}
140 	return *pPropHelper;
141 }
142 
143 
144 Sequence< Locale > SAL_CALL MacSpellChecker::getLocales()
145 		throw(RuntimeException)
146 {
147 	MutexGuard	aGuard( GetLinguMutex() );
148 
149         // this routine should return the locales supported by the installed
150         // dictionaries.  So here we need to parse both the user edited
151         // dictionary list and the shared dictionary list
152         // to see what dictionaries the admin/user has installed
153 
154         int numusr;          // number of user dictionary entries
155         int numshr;          // number of shared dictionary entries
156         dictentry * spdict;  // shared dict entry pointer
157         dictentry * updict;  // user dict entry pointer
158         SvtPathOptions aPathOpt;
159 		rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
160 
161     	std::vector<objc_object *> postspdict;
162     	//std::vector<dictentry *> postspdict;
163         std::vector<dictentry *> postupdict;
164 
165 
166 	if (!numdict) {
167 
168         // invoke a dictionary manager to get the user dictionary list
169         // TODO How on Mac OS X?
170 
171         // invoke a second  dictionary manager to get the shared dictionary list
172         NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
173 
174         //Test for existence of the dictionaries
175         for (unsigned int i = 0; i < [aLocales count]; i++)
176         {
177             if( [macSpell setLanguage:[aLocales objectAtIndex:i] ] )
178             {
179                 postspdict.push_back( [ aLocales objectAtIndex:i ] );
180             }
181         }
182 
183 	    numusr = postupdict.size();
184         numshr = postspdict.size();
185 
186         // we really should merge these and remove duplicates but since
187         // users can name their dictionaries anything they want it would
188         // be impossible to know if a real duplication exists unless we
189         // add some unique key to each myspell dictionary
190         numdict = numshr + numusr;
191 
192         if (numdict) {
193             aDLocs = new Locale [numdict];
194             aDEncs  = new rtl_TextEncoding [numdict];
195             aDNames = new OUString [numdict];
196             aSuppLocales.realloc(numdict);
197             Locale * pLocale = aSuppLocales.getArray();
198             int numlocs = 0;
199             int newloc;
200             int i,j;
201             int k = 0;
202 
203             //first add the user dictionaries
204             //TODO for MAC?
205 
206             // now add the shared dictionaries
207             for (i = 0; i < numshr; i++) {
208                 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
209                 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
210                 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
211                 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
212                 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
213                 Locale nLoc( lang, country, OUString() );
214                 newloc = 1;
215                 //eliminate duplicates (is this needed for MacOS?)
216                 for (j = 0; j < numlocs; j++) {
217                     if (nLoc == pLocale[j]) newloc = 0;
218                 }
219                 if (newloc) {
220                     pLocale[numlocs] = nLoc;
221                     numlocs++;
222                 }
223                 aDLocs[k] = nLoc;
224                 //pointer to Hunspell dictionary - not needed for MAC
225                 //aDicts[k] = NULL;
226                 aDEncs[k] = 0;
227                 // Dictionary file names not valid for Mac Spell
228                 //aDNames[k] = aPathOpt.GetLinguisticPath() + A2OU("/ooo/") + A2OU(postspdict[i]->filename);
229                 k++;
230             }
231 
232             aSuppLocales.realloc(numlocs);
233 
234         } else {
235             /* no dictionary.lst found so register no dictionaries */
236             numdict = 0;
237             //aDicts = NULL;
238                 aDEncs  = NULL;
239                 aDLocs = NULL;
240                 aDNames = NULL;
241 	            aSuppLocales.realloc(0);
242             }
243 
244             /* de-allocation of memory is handled inside the DictMgr */
245             updict = NULL;
246             spdict = NULL;
247 
248         }
249 
250 	return aSuppLocales;
251 }
252 
253 
254 
255 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
256 		throw(RuntimeException)
257 {
258 	MutexGuard	aGuard( GetLinguMutex() );
259 
260 	sal_Bool bRes = sal_False;
261 	if (!aSuppLocales.getLength())
262 		getLocales();
263 
264 	sal_Int32 nLen = aSuppLocales.getLength();
265 	for (sal_Int32 i = 0;  i < nLen;  ++i)
266 	{
267 		const Locale *pLocale = aSuppLocales.getConstArray();
268 		if (rLocale == pLocale[i])
269 		{
270 			bRes = sal_True;
271 			break;
272 		}
273 	}
274 	return bRes;
275 }
276 
277 
278 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
279 {
280     rtl_TextEncoding aEnc;
281 
282 	// initialize a myspell object for each dictionary once
283         // (note: mutex is held higher up in isValid)
284 
285 
286 	sal_Int16 nRes = -1;
287 
288         // first handle smart quotes both single and double
289 	OUStringBuffer rBuf(rWord);
290         sal_Int32 n = rBuf.getLength();
291         sal_Unicode c;
292 	for (sal_Int32 ix=0; ix < n; ix++) {
293 	    c = rBuf.charAt(ix);
294             if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
295             if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
296         }
297         OUString nWord(rBuf.makeStringAndClear());
298 
299 	if (n)
300 	{
301         aEnc = 0;
302 		NSAutoreleasePool* pool	= [[NSAutoreleasePool alloc] init];
303         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
304         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
305         if(rLocale.Country.getLength()>0)
306         {
307             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
308             NSString* aTag = @"_";
309             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
310             [aLang autorelease];
311             aLang = [aLang  stringByAppendingString:aTaggedCountry];
312         }
313 
314         int aCount;
315         NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
316 		int rVal = 0;
317 		if(range.length>0)
318 		{
319 			rVal = -1;
320 		}
321 		else
322 		{
323 			rVal = 1;
324 		}
325         [pool release];
326         if (rVal != 1)
327         {
328             nRes = SpellFailure::SPELLING_ERROR;
329         } else {
330             return -1;
331         }
332     }
333 	return nRes;
334 }
335 
336 
337 
338 sal_Bool SAL_CALL
339 	MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
340 			const PropertyValues& rProperties )
341 		throw(IllegalArgumentException, RuntimeException)
342 {
343 	MutexGuard	aGuard( GetLinguMutex() );
344 
345  	if (rLocale == Locale()  ||  !rWord.getLength())
346 		return sal_True;
347 
348 	if (!hasLocale( rLocale ))
349 #ifdef LINGU_EXCEPTIONS
350 		throw( IllegalArgumentException() );
351 #else
352 		return sal_True;
353 #endif
354 
355 	// Get property values to be used.
356 	// These are be the default values set in the SN_LINGU_PROPERTIES
357 	// PropertySet which are overridden by the supplied ones from the
358 	// last argument.
359 	// You'll probably like to use a simplier solution than the provided
360 	// one using the PropertyHelper_Spell.
361 
362 	PropertyHelper_Spell &rHelper = GetPropHelper();
363 	rHelper.SetTmpPropVals( rProperties );
364 
365 	sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
366 	if (nFailure != -1)
367 	{
368 		sal_Int16 nLang = LocaleToLanguage( rLocale );
369 		// postprocess result for errors that should be ignored
370 		if (   (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang ))
371 			|| (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
372 			|| (!rHelper.IsSpellCapitalization()
373 				&&  nFailure == SpellFailure::CAPTION_ERROR)
374 		)
375 			nFailure = -1;
376 	}
377 
378 	return (nFailure == -1);
379 }
380 
381 
382 Reference< XSpellAlternatives >
383 	MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
384 {
385 	// Retrieves the return values for the 'spell' function call in case
386 	// of a misspelled word.
387 	// Especially it may give a list of suggested (correct) words:
388 
389 	Reference< XSpellAlternatives > xRes;
390         // note: mutex is held by higher up by spell which covers both
391 
392     sal_Int16 nLang = LocaleToLanguage( rLocale );
393 	int count;
394 	Sequence< OUString > aStr( 0 );
395 
396         // first handle smart quotes (single and double)
397 	OUStringBuffer rBuf(rWord);
398         sal_Int32 n = rBuf.getLength();
399         sal_Unicode c;
400 	for (sal_Int32 ix=0; ix < n; ix++) {
401 	     c = rBuf.charAt(ix);
402              if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
403              if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
404         }
405         OUString nWord(rBuf.makeStringAndClear());
406 
407 	if (n)
408 	{
409 		NSAutoreleasePool* pool	= [[NSAutoreleasePool alloc] init];
410         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
411         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
412         if(rLocale.Country.getLength()>0)
413         {
414             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
415             NSString* aTag = @"_";
416             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
417             [aLang autorelease];
418             aLang = [aLang  stringByAppendingString:aTaggedCountry];
419         }
420         [macSpell setLanguage:aLang];
421         NSArray *guesses = [macSpell guessesForWord:aNSStr];
422         count = [guesses count];
423         if (count)
424         {
425            aStr.realloc( count );
426            OUString *pStr = aStr.getArray();
427                for (int ii=0; ii < count; ii++)
428                {
429                   // if needed add: if (suglst[ii] == NULL) continue;
430                   NSString* guess = [guesses objectAtIndex:ii];
431                   OUString cvtwrd((const sal_Unicode*)[guess cStringUsingEncoding:NSUnicodeStringEncoding], (sal_Int32)[guess length]);
432                   pStr[ii] = cvtwrd;
433                }
434         }
435        [pool release];
436 	}
437 
438             // now return an empty alternative for no suggestions or the list of alternatives if some found
439 	    SpellAlternatives *pAlt = new SpellAlternatives;
440             String aTmp(rWord);
441 	    pAlt->SetWordLanguage( aTmp, nLang );
442 	    pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
443 	    pAlt->SetAlternatives( aStr );
444 	    xRes = pAlt;
445         return xRes;
446 
447 }
448 
449 
450 
451 
452 Reference< XSpellAlternatives > SAL_CALL
453 	MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
454 			const PropertyValues& rProperties )
455 		throw(IllegalArgumentException, RuntimeException)
456 {
457 	MutexGuard	aGuard( GetLinguMutex() );
458 
459  	if (rLocale == Locale()  ||  !rWord.getLength())
460 		return NULL;
461 
462 	if (!hasLocale( rLocale ))
463 #ifdef LINGU_EXCEPTIONS
464 		throw( IllegalArgumentException() );
465 #else
466 		return NULL;
467 #endif
468 
469 	Reference< XSpellAlternatives > xAlt;
470 	if (!isValid( rWord, rLocale, rProperties ))
471 	{
472 		xAlt =  GetProposals( rWord, rLocale );
473 	}
474 	return xAlt;
475 }
476 
477 
478 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
479             const Reference< XMultiServiceFactory > & /*rSMgr*/ )
480 		throw(Exception)
481 {
482 
483 	Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
484 	return xService;
485 }
486 
487 
488 sal_Bool SAL_CALL
489 	MacSpellChecker::addLinguServiceEventListener(
490 			const Reference< XLinguServiceEventListener >& rxLstnr )
491 		throw(RuntimeException)
492 {
493 	MutexGuard	aGuard( GetLinguMutex() );
494 
495 	sal_Bool bRes = sal_False;
496 	if (!bDisposing && rxLstnr.is())
497 	{
498 		bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
499 	}
500 	return bRes;
501 }
502 
503 
504 sal_Bool SAL_CALL
505 	MacSpellChecker::removeLinguServiceEventListener(
506 			const Reference< XLinguServiceEventListener >& rxLstnr )
507 		throw(RuntimeException)
508 {
509 	MutexGuard	aGuard( GetLinguMutex() );
510 
511 	sal_Bool bRes = sal_False;
512 	if (!bDisposing && rxLstnr.is())
513 	{
514 		DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
515 		bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
516 	}
517 	return bRes;
518 }
519 
520 
521 OUString SAL_CALL
522     MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
523 		throw(RuntimeException)
524 {
525 	MutexGuard	aGuard( GetLinguMutex() );
526 	return A2OU( "Mac OS X Spell Checker" );
527 }
528 
529 
530 void SAL_CALL
531 	MacSpellChecker::initialize( const Sequence< Any >& rArguments )
532 		throw(Exception, RuntimeException)
533 {
534 	MutexGuard	aGuard( GetLinguMutex() );
535 
536 	if (!pPropHelper)
537 	{
538 		sal_Int32 nLen = rArguments.getLength();
539 		if (2 == nLen)
540 		{
541 			Reference< XPropertySet	>	xPropSet;
542 			rArguments.getConstArray()[0] >>= xPropSet;
543 			//rArguments.getConstArray()[1] >>= xDicList;
544 
545 			//! Pointer allows for access of the non-UNO functions.
546 			//! And the reference to the UNO-functions while increasing
547 			//! the ref-count and will implicitly free the memory
548 			//! when the object is not longer used.
549 			pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
550 			xPropHelper = pPropHelper;
551 			pPropHelper->AddAsPropListener();	//! after a reference is established
552 		}
553 		else
554 			DBG_ERROR( "wrong number of arguments in sequence" );
555 
556 	}
557 }
558 
559 
560 void SAL_CALL
561 	MacSpellChecker::dispose()
562 		throw(RuntimeException)
563 {
564 	MutexGuard	aGuard( GetLinguMutex() );
565 
566 	if (!bDisposing)
567 	{
568 		bDisposing = sal_True;
569 		EventObject	aEvtObj( (XSpellChecker *) this );
570 		aEvtListeners.disposeAndClear( aEvtObj );
571 	}
572 }
573 
574 
575 void SAL_CALL
576 	MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
577 		throw(RuntimeException)
578 {
579 	MutexGuard	aGuard( GetLinguMutex() );
580 
581 	if (!bDisposing && rxListener.is())
582 		aEvtListeners.addInterface( rxListener );
583 }
584 
585 
586 void SAL_CALL
587 	MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
588 		throw(RuntimeException)
589 {
590 	MutexGuard	aGuard( GetLinguMutex() );
591 
592 	if (!bDisposing && rxListener.is())
593 		aEvtListeners.removeInterface( rxListener );
594 }
595 
596 
597 ///////////////////////////////////////////////////////////////////////////
598 // Service specific part
599 //
600 
601 OUString SAL_CALL MacSpellChecker::getImplementationName()
602 		throw(RuntimeException)
603 {
604 	MutexGuard	aGuard( GetLinguMutex() );
605 
606 	return getImplementationName_Static();
607 }
608 
609 
610 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
611 		throw(RuntimeException)
612 {
613 	MutexGuard	aGuard( GetLinguMutex() );
614 
615 	Sequence< OUString > aSNL = getSupportedServiceNames();
616 	const OUString * pArray = aSNL.getConstArray();
617 	for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
618 		if( pArray[i] == ServiceName )
619 			return sal_True;
620 	return sal_False;
621 }
622 
623 
624 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
625 		throw(RuntimeException)
626 {
627 	MutexGuard	aGuard( GetLinguMutex() );
628 
629 	return getSupportedServiceNames_Static();
630 }
631 
632 
633 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
634 		throw()
635 {
636 	MutexGuard	aGuard( GetLinguMutex() );
637 
638 	Sequence< OUString > aSNS( 1 );	// auch mehr als 1 Service moeglich
639 	aSNS.getArray()[0] = A2OU( SN_SPELLCHECKER );
640 	return aSNS;
641 }
642 
643 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
644 			XMultiServiceFactory * pServiceManager, void *  )
645 {
646 	void * pRet = 0;
647 	if ( !MacSpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
648 	{
649 		Reference< XSingleServiceFactory > xFactory =
650 			cppu::createOneInstanceFactory(
651 				pServiceManager,
652 				MacSpellChecker::getImplementationName_Static(),
653 				MacSpellChecker_CreateInstance,
654 				MacSpellChecker::getSupportedServiceNames_Static());
655 		// acquire, because we return an interface pointer instead of a reference
656 		xFactory->acquire();
657 		pRet = xFactory.get();
658 	}
659 	return pRet;
660 }
661 
662 
663 ///////////////////////////////////////////////////////////////////////////
664