xref: /trunk/main/sfx2/source/appl/linkmgr2.cxx (revision 963c6022)
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 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_sfx2.hxx"
24 
25 #include <sfx2/linkmgr.hxx>
26 #include <com/sun/star/document/UpdateDocMode.hpp>
27 #include <sfx2/objsh.hxx>
28 #include <svl/urihelper.hxx>
29 #include <sot/formats.hxx>
30 #include <tools/urlobj.hxx>
31 #include <sot/exchange.hxx>
32 #include <tools/debug.hxx>
33 #include <vcl/msgbox.hxx>
34 #include <sfx2/lnkbase.hxx>
35 #include <sfx2/app.hxx>
36 #include <vcl/graph.hxx>
37 #include <svl/stritem.hxx>
38 #include <svl/eitem.hxx>
39 #include <svl/intitem.hxx>
40 #include <unotools/localfilehelper.hxx>
41 #include <i18npool/mslangid.hxx>
42 #include <sfx2/request.hxx>
43 #include <vcl/dibtools.hxx>
44 
45 #include "fileobj.hxx"
46 #include "impldde.hxx"
47 #include "app.hrc"
48 #include "sfx2/sfxresid.hxx"
49 
50 #define _SVSTDARR_STRINGSDTOR
51 #include <svl/svstdarr.hxx>
52 
53 namespace sfx2
54 {
55 
56 class SvxInternalLink : public sfx2::SvLinkSource
57 {
58 public:
59 	SvxInternalLink() {}
60 
61     virtual sal_Bool Connect( sfx2::SvBaseLink* );
62 };
63 
64 
65 SV_IMPL_PTRARR( SvBaseLinks, SvBaseLinkRefPtr )
66 
67 LinkManager::LinkManager(SfxObjectShell* p)
68 	: pPersist( p )
69 {
70 }
71 
72 
73 LinkManager::~LinkManager()
74 {
75 	SvBaseLinkRef** ppRef = (SvBaseLinkRef**)aLinkTbl.GetData();
76 	for( sal_uInt16 n = aLinkTbl.Count(); n; --n, ++ppRef )
77 	{
78 		if( (*ppRef)->Is() )
79 		{
80 			(*(*ppRef))->Disconnect();
81             (*(*ppRef))->SetLinkManager( NULL );
82 		}
83 		delete *ppRef;
84 	}
85 }
86 
87 
88 /************************************************************************
89 |*    LinkManager::Remove()
90 |*
91 |*    Beschreibung
92 *************************************************************************/
93 
94 void LinkManager::Remove( SvBaseLink *pLink )
95 {
96 	// keine Links doppelt einfuegen
97 	int bFound = sal_False;
98 	SvBaseLinkRef** ppRef = (SvBaseLinkRef**)aLinkTbl.GetData();
99 	for( sal_uInt16 n = aLinkTbl.Count(); n; --n, ++ppRef )
100 	{
101 		if( pLink == *(*ppRef) )
102 		{
103 			(*(*ppRef))->Disconnect();
104             (*(*ppRef))->SetLinkManager( NULL );
105 			(*(*ppRef)).Clear();
106 			bFound = sal_True;
107 		}
108 
109 		// falls noch leere rum stehen sollten, weg damit
110 		if( !(*ppRef)->Is() )
111 		{
112 			delete *ppRef;
113 			aLinkTbl.Remove( aLinkTbl.Count() - n, 1 );
114 			if( bFound )
115 				return ;
116 			--ppRef;
117 		}
118 	}
119 }
120 
121 
122 void LinkManager::Remove( sal_uInt16 nPos, sal_uInt16 nCnt )
123 {
124 	if( nCnt && nPos < aLinkTbl.Count() )
125 	{
126 		if( nPos + nCnt > aLinkTbl.Count() )
127 			nCnt = aLinkTbl.Count() - nPos;
128 
129 		SvBaseLinkRef** ppRef = (SvBaseLinkRef**)aLinkTbl.GetData() + nPos;
130 		for( sal_uInt16 n = nCnt; n; --n, ++ppRef )
131 		{
132 			if( (*ppRef)->Is() )
133 			{
134 				(*(*ppRef))->Disconnect();
135                 (*(*ppRef))->SetLinkManager( NULL );
136 			}
137 			delete *ppRef;
138 		}
139 		aLinkTbl.Remove( nPos, nCnt );
140 	}
141 }
142 
143 
144 sal_Bool LinkManager::Insert( SvBaseLink* pLink )
145 {
146 	// keine Links doppelt einfuegen
147 	for( sal_uInt16 n = 0; n < aLinkTbl.Count(); ++n )
148 	{
149 		SvBaseLinkRef* pTmp = aLinkTbl[ n ];
150 		if( !pTmp->Is() )
151 			aLinkTbl.DeleteAndDestroy( n-- );
152 
153 		if( pLink == *pTmp )
154 			return sal_False;
155 	}
156 
157 	SvBaseLinkRef* pTmp = new SvBaseLinkRef( pLink );
158     pLink->SetLinkManager( this );
159 	aLinkTbl.Insert( pTmp, aLinkTbl.Count() );
160 	return sal_True;
161 }
162 
163 
164 sal_Bool LinkManager::InsertLink( SvBaseLink * pLink,
165 								sal_uInt16 nObjType,
166 								sal_uInt16 nUpdateMode,
167 								const String* pName )
168 {
169 	// unbedingt zuerst
170 	pLink->SetObjType( nObjType );
171 	if( pName )
172 		pLink->SetName( *pName );
173 	pLink->SetUpdateMode( nUpdateMode );
174 	return Insert( pLink );
175 }
176 
177 
178 sal_Bool LinkManager::InsertDDELink( SvBaseLink * pLink,
179 									const String& rServer,
180 									const String& rTopic,
181 									const String& rItem )
182 {
183 	if( !( OBJECT_CLIENT_SO & pLink->GetObjType() ) )
184 		return sal_False;
185 
186 	String sCmd;
187 	::sfx2::MakeLnkName( sCmd, &rServer, rTopic, rItem );
188 
189 	pLink->SetObjType( OBJECT_CLIENT_DDE );
190 	pLink->SetName( sCmd );
191 	return Insert( pLink );
192 }
193 
194 
195 sal_Bool LinkManager::InsertDDELink( SvBaseLink * pLink )
196 {
197 	DBG_ASSERT( OBJECT_CLIENT_SO & pLink->GetObjType(), "no OBJECT_CLIENT_SO" );
198 	if( !( OBJECT_CLIENT_SO & pLink->GetObjType() ) )
199 		return sal_False;
200 
201 	if( pLink->GetObjType() == OBJECT_CLIENT_SO )
202 		pLink->SetObjType( OBJECT_CLIENT_DDE );
203 
204 	return Insert( pLink );
205 }
206 
207 
208 // erfrage die Strings fuer den Dialog
209 sal_Bool LinkManager::GetDisplayNames( const SvBaseLink * pLink,
210 										String* pType,
211 										String* pFile,
212 										String* pLinkStr,
213 										String* pFilter ) const
214 {
215 	sal_Bool bRet = sal_False;
216 	const String sLNm( pLink->GetLinkSourceName() );
217 	if( sLNm.Len() )
218 	{
219 		switch( pLink->GetObjType() )
220 		{
221 		    case OBJECT_CLIENT_FILE:
222 		    case OBJECT_CLIENT_GRF:
223 		    case OBJECT_CLIENT_OLE:
224 			    {
225 				    sal_uInt16 nPos = 0;
226 				    String sFile( sLNm.GetToken( 0, ::sfx2::cTokenSeperator, nPos ) );
227 				    String sRange( sLNm.GetToken( 0, ::sfx2::cTokenSeperator, nPos ) );
228 
229 				    if( pFile )
230 					    *pFile = sFile;
231 				    if( pLinkStr )
232 					    *pLinkStr = sRange;
233 				    if( pFilter )
234 					    *pFilter = sLNm.Copy( nPos );
235 
236 				    if( pType )
237 				    {
238 					    sal_uInt16 nObjType = pLink->GetObjType();
239 					    *pType = String( SfxResId(
240 								    ( OBJECT_CLIENT_FILE == nObjType || OBJECT_CLIENT_OLE == nObjType )
241 										    ? RID_SVXSTR_FILELINK
242 										    : RID_SVXSTR_GRAFIKLINK ));
243 				    }
244 				    bRet = sal_True;
245 			    }
246 			    break;
247 		    case OBJECT_CLIENT_DDE:
248 	            {
249 		            sal_uInt16 nTmp = 0;
250 		            String sCmd( sLNm );
251 		            String sServer( sCmd.GetToken( 0, cTokenSeperator, nTmp ) );
252 		            String sTopic( sCmd.GetToken( 0, cTokenSeperator, nTmp ) );
253 
254 		            if( pType )
255 			            *pType = sServer;
256 		            if( pFile )
257 			            *pFile = sTopic;
258 		            if( pLinkStr )
259 			            *pLinkStr = sCmd.Copy( nTmp );
260 		            bRet = sal_True;
261 		        }
262 		        break;
263 	        default:
264 	            break;
265 	    }
266 	}
267 
268 	return bRet;
269 }
270 
271 
272 void LinkManager::UpdateAllLinks(
273     sal_Bool bAskUpdate,
274     sal_Bool /*bCallErrHdl*/,
275     sal_Bool bUpdateGrfLinks,
276     Window* pParentWin )
277 {
278 	SvStringsDtor aApps, aTopics, aItems;
279 	String sApp, sTopic, sItem;
280 
281 	// erstmal eine Kopie vom Array machen, damit sich updatende Links in
282 	// Links in ... nicht dazwischen funken!!
283 	SvPtrarr aTmpArr( 255, 50 );
284 	sal_uInt16 n;
285 	for( n = 0; n < aLinkTbl.Count(); ++n )
286 	{
287 		SvBaseLink* pLink = *aLinkTbl[ n ];
288 		if( !pLink )
289 		{
290 			Remove( n-- );
291 			continue;
292 		}
293 		aTmpArr.Insert( pLink, aTmpArr.Count() );
294 	}
295 
296 	for( n = 0; n < aTmpArr.Count(); ++n )
297 	{
298 		SvBaseLink* pLink = (SvBaseLink*)aTmpArr[ n ];
299 
300 		// suche erstmal im Array nach dem Eintrag
301 		sal_uInt16 nFndPos = USHRT_MAX;
302 		for( sal_uInt16 i = 0; i < aLinkTbl.Count(); ++i )
303 			if( pLink == *aLinkTbl[ i ] )
304 			{
305 				nFndPos = i;
306 				break;
307 			}
308 
309 		if( USHRT_MAX == nFndPos )
310 			continue;					// war noch nicht vorhanden!
311 
312 		// Graphic-Links noch nicht updaten
313 		if( !pLink->IsVisible() ||
314 			( !bUpdateGrfLinks && OBJECT_CLIENT_GRF == pLink->GetObjType() ))
315 			continue;
316 
317 		if( bAskUpdate )
318 		{
319             int nRet = QueryBox( pParentWin, WB_YES_NO | WB_DEF_YES, SfxResId( STR_QUERY_UPDATE_LINKS ) ).Execute();
320 			if( RET_YES != nRet )
321             {
322                 SfxObjectShell* pShell = pLink->GetLinkManager()->GetPersist();
323 
324                 if(pShell)
325                 {
326                     comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = pShell->getEmbeddedObjectContainer();
327                     rEmbeddedObjectContainer.setUserAllowsLinkUpdate(false);
328                 }
329 
330 				return ;		// es soll nichts geupdatet werden
331             }
332 			bAskUpdate = sal_False;		// einmal reicht
333 		}
334 
335 		pLink->Update();
336 	}
337 }
338 
339 /************************************************************************
340 |*    SvBaseLink::CreateObject()
341 |*
342 |*    Beschreibung
343 *************************************************************************/
344 
345 SvLinkSourceRef LinkManager::CreateObj( SvBaseLink * pLink )
346 {
347 	switch( pLink->GetObjType() )
348 	{
349 	    case OBJECT_CLIENT_FILE:
350 	    case OBJECT_CLIENT_GRF:
351 	    case OBJECT_CLIENT_OLE:
352 		    return new SvFileObject;
353 	    case OBJECT_INTERN:
354 		    return new SvxInternalLink;
355         case OBJECT_CLIENT_DDE:
356 		    return new SvDDEObject;
357 	    default:
358         	return SvLinkSourceRef();
359    	}
360 }
361 
362 sal_Bool LinkManager::InsertServer( SvLinkSource* pObj )
363 {
364 	// keine doppelt einfuegen
365 	if( !pObj || USHRT_MAX != aServerTbl.GetPos( pObj ) )
366 		return sal_False;
367 
368 	aServerTbl.Insert( pObj, aServerTbl.Count() );
369 	return sal_True;
370 }
371 
372 
373 void LinkManager::RemoveServer( SvLinkSource* pObj )
374 {
375 	sal_uInt16 nPos = aServerTbl.GetPos( pObj );
376 	if( USHRT_MAX != nPos )
377 		aServerTbl.Remove( nPos, 1 );
378 }
379 
380 
381 void MakeLnkName( String& rName, const String* pType, const String& rFile,
382 					const String& rLink, const String* pFilter )
383 {
384 	if( pType )
385 		(rName = *pType).EraseLeadingChars().EraseTrailingChars() += cTokenSeperator;
386 	else if( rName.Len() )
387 		rName.Erase();
388 
389 	((rName += rFile).EraseLeadingChars().EraseTrailingChars() +=
390 		cTokenSeperator ).EraseLeadingChars().EraseTrailingChars() += rLink;
391 	if( pFilter )
392 		((rName += cTokenSeperator ) += *pFilter).EraseLeadingChars().EraseTrailingChars();
393 }
394 
395 sal_Bool LinkManager::InsertFileLink( sfx2::SvBaseLink& rLink,
396 									sal_uInt16 nFileType,
397 									const String& rFileNm,
398 									const String* pFilterNm,
399 									const String* pRange )
400 {
401 	if( !( OBJECT_CLIENT_SO & rLink.GetObjType() ))
402 		return sal_False;
403 
404 	String sCmd( rFileNm );
405 	sCmd += ::sfx2::cTokenSeperator;
406 	if( pRange )
407 		sCmd += *pRange;
408 	if( pFilterNm )
409 		( sCmd += ::sfx2::cTokenSeperator ) += *pFilterNm;
410 
411 	return InsertLink( &rLink, nFileType, sfx2::LINKUPDATE_ONCALL, &sCmd );
412 }
413 
414 sal_Bool LinkManager::InsertFileLink( sfx2::SvBaseLink& rLink )
415 {
416 	if( OBJECT_CLIENT_FILE == ( OBJECT_CLIENT_FILE & rLink.GetObjType() ))
417 		return InsertLink( &rLink, rLink.GetObjType(), sfx2::LINKUPDATE_ONCALL );
418 	return sal_False;
419 }
420 
421 // eine Uebertragung wird abgebrochen, also alle DownloadMedien canceln
422 // (ist zur Zeit nur fuer die FileLinks interressant!)
423 void LinkManager::CancelTransfers()
424 {
425 	SvFileObject* pFileObj;
426 	sfx2::SvBaseLink* pLnk;
427 
428 	const sfx2::SvBaseLinks& rLnks = GetLinks();
429 	for( sal_uInt16 n = rLnks.Count(); n; )
430 		if( 0 != ( pLnk = &(*rLnks[ --n ])) &&
431 			OBJECT_CLIENT_FILE == (OBJECT_CLIENT_FILE & pLnk->GetObjType()) &&
432 			0 != ( pFileObj = (SvFileObject*)pLnk->GetObj() ) )
433 //			0 != ( pFileObj = (SvFileObject*)SvFileObject::ClassFactory()->
434 //									CastAndAddRef( pLnk->GetObj() )) )
435 			pFileObj->CancelTransfers();
436 }
437 
438 	// um Status Informationen aus dem FileObject an den BaseLink zu
439 	// senden, gibt es eine eigene ClipBoardId. Das SvData-Object hat
440 	// dann die entsprechenden Informationen als String.
441 	// Wird zur Zeit fuer FileObject in Verbindung mit JavaScript benoetigt
442 	// - das braucht Informationen ueber Load/Abort/Error
443 sal_uIntPtr LinkManager::RegisterStatusInfoId()
444 {
445 	static sal_uIntPtr nFormat = 0;
446 
447 	if( !nFormat )
448 	{
449 // wie sieht die neue Schnittstelle aus?
450 //		nFormat = Exchange::RegisterFormatName( "StatusInfo vom SvxInternalLink" );
451 		nFormat = SotExchange::RegisterFormatName(
452 					String::CreateFromAscii( RTL_CONSTASCII_STRINGPARAM(
453 								"StatusInfo vom SvxInternalLink" )));
454 	}
455 	return nFormat;
456 }
457 
458 // ----------------------------------------------------------------------
459 
460 sal_Bool LinkManager::GetGraphicFromAny( const String& rMimeType,
461 								const ::com::sun::star::uno::Any & rValue,
462 								Graphic& rGrf )
463 {
464 	sal_Bool bRet = sal_False;
465 	::com::sun::star::uno::Sequence< sal_Int8 > aSeq;
466 	if( rValue.hasValue() && ( rValue >>= aSeq ) )
467 	{
468 		SvMemoryStream aMemStm( (void*)aSeq.getConstArray(), aSeq.getLength(),
469 								STREAM_READ );
470 		aMemStm.Seek( 0 );
471 
472 		switch( SotExchange::GetFormatIdFromMimeType( rMimeType ) )
473 		{
474 		case SOT_FORMATSTR_ID_SVXB:
475 			{
476 				aMemStm >> rGrf;
477 				bRet = sal_True;
478 			}
479 			break;
480 		case FORMAT_GDIMETAFILE:
481 			{
482 				GDIMetaFile aMtf;
483 				aMtf.Read( aMemStm );
484 				rGrf = aMtf;
485 				bRet = sal_True;
486 			}
487 			break;
488 		case FORMAT_BITMAP:
489 			{
490 				Bitmap aBmp;
491                 ReadDIB(aBmp, aMemStm, true);
492 				rGrf = aBmp;
493 				bRet = sal_True;
494 			}
495 			break;
496 		}
497 	}
498 	return bRet;
499 }
500 
501 
502 // ----------------------------------------------------------------------
503 String lcl_DDE_RelToAbs( const String& rTopic, const String& rBaseURL )
504 {
505 	String sRet;
506 	INetURLObject aURL( rTopic );
507 	if( INET_PROT_NOT_VALID == aURL.GetProtocol() )
508         utl::LocalFileHelper::ConvertSystemPathToURL( rTopic, rBaseURL, sRet );
509 	if( !sRet.Len() )
510         sRet = URIHelper::SmartRel2Abs( INetURLObject(rBaseURL), rTopic, URIHelper::GetMaybeFileHdl(), true );
511 	return sRet;
512 }
513 
514 sal_Bool SvxInternalLink::Connect( sfx2::SvBaseLink* pLink )
515 {
516 	SfxObjectShell* pFndShell = 0;
517 	sal_uInt16 nUpdateMode = com::sun::star::document::UpdateDocMode::NO_UPDATE;
518 	String sTopic, sItem, sReferer;
519 	if( pLink->GetLinkManager() &&
520 		pLink->GetLinkManager()->GetDisplayNames( pLink, 0, &sTopic, &sItem )
521 		&& sTopic.Len() )
522 	{
523 		// erstmal nur ueber die DocumentShells laufen und die mit dem
524 		// Namen heraussuchen:
525 
526 	    com::sun::star::lang::Locale aLocale;
527 	    MsLangId::convertLanguageToLocale( LANGUAGE_SYSTEM, aLocale );
528 		CharClass aCC( aLocale );
529 
530         String sNm( sTopic ), sTmp;
531 		aCC.toLower( sNm );
532 
533 		TypeId aType( TYPE(SfxObjectShell) );
534 
535 		sal_Bool bFirst = sal_True;
536         SfxObjectShell* pShell = pLink->GetLinkManager()->GetPersist();
537         if( pShell && pShell->GetMedium() )
538 		{
539             sReferer = pShell->GetMedium()->GetBaseURL();
540 			SFX_ITEMSET_ARG( pShell->GetMedium()->GetItemSet(), pItem, SfxUInt16Item, SID_UPDATEDOCMODE, sal_False );
541 			if ( pItem )
542 				nUpdateMode = pItem->GetValue();
543 		}
544 
545         String sNmURL( lcl_DDE_RelToAbs( sTopic, sReferer ) );
546 		aCC.toLower( sNmURL );
547 
548 		if ( !pShell )
549 		{
550 			bFirst = sal_False;
551             pShell = SfxObjectShell::GetFirst( &aType, sal_False );
552 		}
553 
554 		while( pShell )
555 		{
556 			if( !sTmp.Len() )
557 			{
558 				sTmp = pShell->GetTitle( SFX_TITLE_FULLNAME );
559                 sTmp = lcl_DDE_RelToAbs(sTmp, sReferer );
560 			}
561 
562 
563 			aCC.toLower( sTmp );
564 			if( sTmp == sNmURL )		// die wollen wir haben
565 			{
566 				pFndShell = pShell;
567 				break;
568 			}
569 
570 			if( bFirst )
571 			{
572 				bFirst = sal_False;
573                 pShell = SfxObjectShell::GetFirst( &aType, sal_False );
574 			}
575 			else
576                 pShell = SfxObjectShell::GetNext( *pShell, &aType, sal_False );
577 
578 			sTmp.Erase();
579 		}
580 	}
581 
582 	// empty topics are not allowed - which document is it
583 	if( !sTopic.Len() )
584 		return sal_False;
585 
586 	if( !pFndShell )
587 	{
588 		// dann versuche die Datei zu laden:
589 		INetURLObject aURL( sTopic );
590 		INetProtocol eOld = aURL.GetProtocol();
591         aURL.SetURL( sTopic = lcl_DDE_RelToAbs( sTopic, sReferer ) );
592 		if( INET_PROT_NOT_VALID != eOld ||
593 			INET_PROT_HTTP != aURL.GetProtocol() )
594 		{
595 			SfxStringItem aName( SID_FILE_NAME, sTopic );
596             SfxBoolItem aMinimized(SID_MINIMIZED, sal_True);
597             SfxBoolItem aHidden(SID_HIDDEN, sal_True);
598             SfxStringItem aTarget( SID_TARGETNAME, String::CreateFromAscii("_blank") );
599 			SfxStringItem aReferer( SID_REFERER, sReferer );
600 			SfxUInt16Item aUpdate( SID_UPDATEDOCMODE, nUpdateMode );
601             SfxBoolItem aReadOnly(SID_DOC_READONLY, sal_True);
602 
603             // #i14200# (DDE-link crashes wordprocessor)
604             SfxAllItemSet aArgs( SFX_APP()->GetPool() );
605             aArgs.Put(aReferer);
606             aArgs.Put(aTarget);
607             aArgs.Put(aHidden);
608             aArgs.Put(aMinimized);
609             aArgs.Put(aName);
610 			aArgs.Put(aUpdate);
611 			aArgs.Put(aReadOnly);
612             pFndShell = SfxObjectShell::CreateAndLoadObject( aArgs );
613 		}
614 	}
615 
616 	sal_Bool bRet = sal_False;
617 	if( pFndShell )
618 	{
619 		sfx2::SvLinkSource* pNewSrc = pFndShell->DdeCreateLinkSource( sItem );
620 		if( pNewSrc )
621 		{
622 			bRet = sal_True;
623 
624 			::com::sun::star::datatransfer::DataFlavor aFl;
625 			SotExchange::GetFormatDataFlavor( pLink->GetContentType(), aFl );
626 
627 			pLink->SetObj( pNewSrc );
628 			pNewSrc->AddDataAdvise( pLink, aFl.MimeType,
629 								sfx2::LINKUPDATE_ONCALL == pLink->GetUpdateMode()
630 									? ADVISEMODE_ONLYONCE
631 									: 0 );
632 		}
633 	}
634 	return bRet;
635 }
636 
637 
638 }
639 
640 
641 
642