xref: /trunk/main/sc/source/filter/lotus/tool.cxx (revision b77af630)
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_scfilt.hxx"
26 
27 
28 
29 //------------------------------------------------------------------------
30 
31 #include "scitems.hxx"
32 #include <svx/algitem.hxx>
33 #include <svl/zforlist.hxx>
34 #include <tools/solar.h>
35 
36 #include "cell.hxx"
37 #include "rangenam.hxx"
38 #include "compiler.hxx"
39 
40 #include "tool.h"
41 #include "decl.h"
42 #include "root.hxx"
43 #include "lotrange.hxx"
44 #include "namebuff.hxx"
45 #include "ftools.hxx"
46 
47 #include <math.h>
48 
49 #ifdef _MSC_VER
50 #pragma optimize("",off)
51 #endif
52 
53 //--------------------------------------------------------- EXTERNE VARIABLEN -
54 extern WKTYP				eTyp;			// -> filter.cxx, aktueller Dateityp
55 extern ScDocument*			pDoc;			// -> filter.cxx, Aufhaenger zum Dokumentzugriff
56 
57 //--------------------------------------------------------- GLOBALE VARIABLEN -
58 sal_uInt8						nDefaultFormat;	// -> op.cpp, Standard-Zellenformat
59 
60 extern SvxHorJustifyItem	*pAttrRight, *pAttrLeft, *pAttrCenter, *pAttrRepeat, *pAttrStandard;
61 extern ScProtectionAttr*	pAttrUnprot;
62 extern SfxUInt32Item**		pAttrValForms;
63 
64 SvxHorJustifyItem			*pAttrRight, *pAttrLeft, *pAttrCenter, *pAttrRepeat, *pAttrStandard;
65 													// -> in memory.cxx initialisiert
66 ScProtectionAttr*			pAttrUnprot;			// ->  " memory.cxx    "
67 
68 extern FormCache*			pValueFormCache;		// -> in memory.cxx initialisiert
69 FormCache*					pValueFormCache;
70 
71 SCCOL						LotusRangeList::nEingCol;
72 SCROW						LotusRangeList::nEingRow;
73 
74 
75 
76 
PutFormString(SCCOL nCol,SCROW nRow,SCTAB nTab,sal_Char * pString)77 void PutFormString( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_Char* pString )
78 {
79 	// Label-Format-Auswertung
80 	DBG_ASSERT( pString != NULL, "PutFormString(): pString == NULL" );
81     if (!pString)
82         return;
83 
84 	sal_Char			cForm;
85 	SvxHorJustifyItem*	pJustify = NULL;
86 
87 	cForm = *pString;
88 
89 	switch( cForm )
90 	{
91 		case '"':   // rechtsbuendig
92 			pJustify = pAttrRight;
93 			pString++;
94 			break;
95 		case '\'':  // linksbuendig
96 			pJustify = pAttrLeft;
97 			pString++;
98 			break;
99 		case '^':   // zentriert
100 			pJustify = pAttrCenter;
101 			pString++;
102 			break;
103 		case '|':   // printer command
104 			pString = NULL;
105 			break;
106 		case '\\':  // Wiederholung
107 			pJustify = pAttrRepeat;
108 			pString++;
109 			break;
110 		default:    // kenn' ich nicht!
111 			pJustify = pAttrStandard;
112 	}
113 
114     pDoc->ApplyAttr( nCol, nRow, nTab, *pJustify );
115     ScStringCell*	pZelle = new ScStringCell( String( pString, pLotusRoot->eCharsetQ ) );
116     pDoc->PutCell( nCol, nRow, nTab, pZelle, ( sal_Bool ) sal_True );
117 }
118 
119 
120 
121 
SetFormat(SCCOL nCol,SCROW nRow,SCTAB nTab,sal_uInt8 nFormat,sal_uInt8 nSt)122 void SetFormat( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt8 nFormat, sal_uInt8 nSt )
123 {
124 	//  PREC:   nSt = Standard-Dezimalstellenanzahl
125 	pDoc->ApplyAttr( nCol, nRow, nTab, *( pValueFormCache->GetAttr( nFormat, nSt ) ) );
126 
127 	ScProtectionAttr aAttr;
128 
129 	aAttr.SetProtection( nFormat & 0x80 );
130 
131 	pDoc->ApplyAttr( nCol, nRow, nTab, aAttr );
132 }
133 
InitPage(void)134 void InitPage( void )
135 {	// Seitenformat initialisieren, d.h. Default-Werte von SC holen
136 	//scGetPageFormat( 0, &aPage );
137 }
138 
139 
SnumToDouble(sal_Int16 nVal)140 double SnumToDouble( sal_Int16 nVal )
141 {
142 	const double pFacts[ 8 ] = {
143 		5000.0,
144 		500.0,
145 		0.05,
146 		0.005,
147 		0.0005,
148 		0.00005,
149 		0.0625,
150 		0.015625 };
151 
152 	double		fVal;
153 
154 	if( nVal & 0x0001 )
155 	{
156 		fVal = pFacts[ ( nVal >> 1 ) & 0x0007 ];
157 		fVal *= ( sal_Int16 ) ( nVal >> 4 );
158 	}
159 	else
160 		fVal = ( sal_Int16 ) ( nVal >> 1 );
161 
162 	return fVal;
163 }
164 
Snum32ToDouble(sal_uInt32 nValue)165 double Snum32ToDouble( sal_uInt32 nValue )
166 {
167 	double fValue, temp;
168 
169 	fValue = nValue >> 6;
170 	temp = nValue & 0x0f;
171 	if (temp)
172 	{
173 	    if (nValue & 0x00000010)
174                 fValue /= pow((double)10, temp);
175 	    else
176 		fValue *= pow((double)10, temp);
177 	}
178 
179 	if ((nValue & 0x00000020))
180 		fValue = -fValue;
181 	return fValue;
182 }
183 
184 
FormCache(ScDocument * pDoc1,sal_uInt8 nNewDefaultFormat)185 FormCache::FormCache( ScDocument* pDoc1, sal_uInt8 nNewDefaultFormat )
186 {	// Default-Format ist 'Default'
187 	nDefaultFormat = nNewDefaultFormat;
188     pFormTable = pDoc1->GetFormatTable();
189 	for( sal_uInt16 nC = 0 ; nC < __nSize ; nC++ )
190 		bValid[ nC ] = sal_False;
191 	eLanguage = ScGlobal::eLnge;
192 }
193 
194 
~FormCache()195 FormCache::~FormCache()
196 {
197 	for( sal_uInt16 nC = 0 ; nC < __nSize ; nC++ )
198 		delete aIdents[ nC ].GetAttr();
199 }
200 
201 
NewAttr(sal_uInt8 nFormat,sal_uInt8 nSt)202 SfxUInt32Item* FormCache::NewAttr( sal_uInt8 nFormat, sal_uInt8 nSt )
203 {
204 	// neues Format erzeugen
205 	sal_uInt8		nL, nH; // Low-/High-Nibble
206 	sal_uInt8		nForm = nFormat;
207 	String		aFormString;
208     const sal_Char* pFormString = 0;
209 	sal_Int16		eType = NUMBERFORMAT_ALL;
210     sal_uInt32      nIndex1;
211 	sal_uInt32		nHandle;
212 	sal_Bool		bDefault = sal_False;
213 	//void GenerateFormat( aFormString, eType, COUNTRY_SYSTEM, LANGUAGE_SYSTEM,
214 	//  sal_Bool bThousand, sal_Bool IsRed, sal_uInt16 nPrecision, sal_uInt16 nAnzLeading );
215 
216 	if( nForm == 0xFF ) // Default-Format?
217 		nForm = nDefaultFormat;
218 
219 	// Aufdroeseln in Low- und High-Nibble
220 	nL = nFormat & 0x0F;
221 	nH = ( nFormat & 0xF0 ) / 16;
222 
223 	nH &= 0x07;     // Bits 4-6 'rausziehen
224 	switch( nH )
225 	{
226 		case 0x00:  // Festkommaformat (fixed)
227 			//fStandard;nL;
228             nIndex1 = pFormTable->GetStandardFormat(
229 				NUMBERFORMAT_NUMBER, eLanguage );
230             pFormTable->GenerateFormat( aFormString, nIndex1,
231 				eLanguage, sal_False, sal_False, nL, 1 );
232 			break;
233 		case 0x01:  // Exponentdarstellung (scientific notation)
234 			//fExponent;nL;
235             nIndex1 = pFormTable->GetStandardFormat(
236 				NUMBERFORMAT_SCIENTIFIC, eLanguage );
237             pFormTable->GenerateFormat( aFormString, nIndex1,
238 				eLanguage, sal_False, sal_False, nL, 1 );
239 			break;
240 		case 0x02:  // Waehrungsdarstellung (currency)
241 			//fMoney;nL;
242             nIndex1 = pFormTable->GetStandardFormat(
243 				NUMBERFORMAT_CURRENCY, eLanguage );
244             pFormTable->GenerateFormat( aFormString, nIndex1,
245 				eLanguage, sal_False, sal_False, nL, 1 );
246 			break;
247 		case 0x03:  // Prozent
248 			//fPercent;nL;
249             nIndex1 = pFormTable->GetStandardFormat(
250 				NUMBERFORMAT_PERCENT, eLanguage );
251             pFormTable->GenerateFormat( aFormString, nIndex1,
252 				eLanguage, sal_False, sal_False, nL, 1 );
253 			break;
254 		case 0x04:  // Komma
255 			//fStandard;nL;
256             nIndex1 = pFormTable->GetStandardFormat(
257 				NUMBERFORMAT_NUMBER, eLanguage );
258             pFormTable->GenerateFormat( aFormString, nIndex1,
259 				eLanguage, sal_True, sal_False, nL, 1 );
260 			break;
261 		case 0x05:  // frei
262 			//fStandard;nL;
263             nIndex1 = pFormTable->GetStandardFormat(
264 				NUMBERFORMAT_NUMBER, eLanguage );
265             pFormTable->GenerateFormat( aFormString, nIndex1,
266 				eLanguage, sal_False, sal_False, nL, 1 );
267 			break;
268 		case 0x06:  // frei
269 			//fStandard;nL;
270             nIndex1 = pFormTable->GetStandardFormat(
271 				NUMBERFORMAT_NUMBER, eLanguage );
272             pFormTable->GenerateFormat( aFormString, nIndex1,
273 				eLanguage, sal_False, sal_False, nL, 1 );
274             nIndex1 = 0;
275 			break;
276 		case 0x07:  // Spezialformat
277 			switch( nL )
278 			{
279 				case 0x00:  // +/-
280 					//fStandard;nSt;
281                     nIndex1 = pFormTable->GetStandardFormat(
282 						NUMBERFORMAT_NUMBER, eLanguage );
283                     pFormTable->GenerateFormat( aFormString, nIndex1,
284 						eLanguage, sal_False, sal_True, nSt, 1 );
285 					break;
286 				case 0x01:  // generelles Format
287 					//fStandard;nSt;
288                     nIndex1 = pFormTable->GetStandardFormat(
289 						NUMBERFORMAT_NUMBER, eLanguage );
290                     pFormTable->GenerateFormat( aFormString, nIndex1,
291 						eLanguage, sal_False, sal_False, nSt, 1 );
292 					break;
293 				case 0x02:  // Datum: Tag, Monat, Jahr
294 					//fDate;dfDayMonthYearLong;
295 					eType = NUMBERFORMAT_DATE;
296 					pFormString = "TT.MM.JJJJ";
297 					break;
298 				case 0x03:  // Datum: Tag, Monat
299 					//fDate;dfDayMonthLong;
300 					eType = NUMBERFORMAT_DATE;
301 					pFormString = "TT.MMMM";
302 					break;
303 				case 0x04:  // Datum: Monat, Jahr
304 					//fDate;dfMonthYearLong;
305 					eType = NUMBERFORMAT_DATE;
306 					pFormString = "MM.JJJJ";
307 					break;
308 				case 0x05:  // Textformate
309 					//fString;nSt;
310 					eType = NUMBERFORMAT_TEXT;
311 					pFormString = "@";
312 					break;
313 				case 0x06:  // versteckt
314 					//wFlag |= paHideAll;bSetFormat = sal_False;
315 					eType = NUMBERFORMAT_NUMBER;
316 					pFormString = "";
317 					break;
318 				case 0x07:  // Time: hour, min, sec
319 					//fTime;tfHourMinSec24;
320 					eType = NUMBERFORMAT_TIME;
321 					pFormString = "HH:MM:SS";
322 					break;
323 				case 0x08:  // Time: hour, min
324 					//fTime;tfHourMin24;
325 					eType = NUMBERFORMAT_TIME;
326 					pFormString = "HH:MM";
327 					break;
328 				case 0x09:  // Date, intern sal_Int32 1
329 					//fDate;dfDayMonthYearLong;
330 					eType = NUMBERFORMAT_DATE;
331 					pFormString = "TT.MM.JJJJ";
332 					break;
333 				case 0x0A:  // Date, intern sal_Int32 2
334 					//fDate;dfDayMonthYearLong;
335 					eType = NUMBERFORMAT_DATE;
336 					pFormString = "TT.MM.JJJJ";
337 					break;
338 				case 0x0B:  // Time, intern sal_Int32 1
339 					//fTime;tfHourMinSec24;
340 					eType = NUMBERFORMAT_TIME;
341 					pFormString = "HH:MM:SS";
342 					break;
343 				case 0x0C:  // Time, intern sal_Int32 2
344 					//fTime;tfHourMinSec24;
345 					eType = NUMBERFORMAT_TIME;
346 					pFormString = "HH:MM:SS";
347 					break;
348 				case 0x0F:  // Standardeinstellung
349 					//fStandard;nSt;
350 					bDefault = sal_True;
351 					break;
352 				default:
353 					//fStandard;nSt;
354 					bDefault = sal_True;
355 					break;
356 			}
357 			break;
358 		default:
359 			//fStandard;nL;
360             nIndex1 = pFormTable->GetStandardFormat(
361 				NUMBERFORMAT_NUMBER, eLanguage );
362             pFormTable->GenerateFormat( aFormString, nIndex1,
363 				eLanguage, sal_False, sal_False, nL, 1 );
364             nIndex1 = 0;
365 			break;
366 	}
367 
368 	// Format in Table schieben
369 	if( bDefault )
370 		nHandle = 0;
371 	else
372 	{
373 		if( pFormString )
374 			aFormString.AssignAscii( pFormString );
375 
376 		xub_StrLen	nDummy;
377 		pFormTable->PutEntry( aFormString, nDummy, eType, nHandle, eLanguage );
378 	}
379 
380 	return new SfxUInt32Item( ATTR_VALUE_FORMAT, ( sal_uInt32 ) nHandle );
381 }
382 
383 
384 
385 
MakeHash(void)386 void LotusRange::MakeHash( void )
387 {
388 	// 33222222222211111111110000000000
389 	// 10987654321098765432109876543210
390 	//                         ******** nColS
391 	//                   ********       nColE
392 	//     ****************             nRowS
393 	// ****************                 nRowE
394 	nHash =  static_cast<sal_uInt32>(nColStart);
395 	nHash += static_cast<sal_uInt32>(nColEnd) << 6;
396 	nHash += static_cast<sal_uInt32>(nRowStart) << 12;
397 	nHash += static_cast<sal_uInt32>(nRowEnd ) << 16;
398 }
399 
400 
LotusRange(SCCOL nCol,SCROW nRow)401 LotusRange::LotusRange( SCCOL nCol, SCROW nRow )
402 {
403 	nColStart = nColEnd = nCol;
404 	nRowStart = nRowEnd = nRow;
405 	nId = ID_FAIL;
406 	MakeHash();
407 }
408 
409 
LotusRange(SCCOL nCS,SCROW nRS,SCCOL nCE,SCROW nRE)410 LotusRange::LotusRange( SCCOL nCS, SCROW nRS, SCCOL nCE, SCROW nRE )
411 {
412 	nColStart = nCS;
413 	nColEnd = nCE;
414 	nRowStart = nRS;
415 	nRowEnd = nRE;
416 	nId = ID_FAIL;
417 	MakeHash();
418 }
419 
420 
LotusRange(const LotusRange & rCpy)421 LotusRange::LotusRange( const LotusRange& rCpy )
422 {
423 	Copy( rCpy );
424 }
425 
426 
427 
428 
429 
LotusRangeList(void)430 LotusRangeList::LotusRangeList( void )
431 {
432 	aComplRef.InitFlags();
433 
434 	ScSingleRefData*	pSingRef;
435 	nIdCnt = 1;
436 
437 	pSingRef = &aComplRef.Ref1;
438 	pSingRef->nTab = pSingRef->nRelTab = 0;
439 	pSingRef->SetColRel( sal_False );
440 	pSingRef->SetRowRel( sal_False );
441 	pSingRef->SetTabRel( sal_True );
442 	pSingRef->SetFlag3D( sal_False );
443 
444 	pSingRef = &aComplRef.Ref2;
445 	pSingRef->nTab = pSingRef->nRelTab = 0;
446 	pSingRef->SetColRel( sal_False );
447 	pSingRef->SetRowRel( sal_False );
448 	pSingRef->SetTabRel( sal_True );
449 	pSingRef->SetFlag3D( sal_False );
450 }
451 
452 
~LotusRangeList(void)453 LotusRangeList::~LotusRangeList( void )
454 	{
455 	LotusRange *pDel = ( LotusRange * ) List::First();
456 
457 	while( pDel )
458 		{
459 		delete pDel;
460 		pDel = ( LotusRange * ) List::Next();
461 		}
462 	}
463 
464 
GetIndex(const LotusRange & rRef)465 LR_ID LotusRangeList::GetIndex( const LotusRange &rRef )
466 {
467 	LotusRange*		pComp = ( LotusRange* ) List::First();
468 
469 	while( pComp )
470 	{
471 		if( *pComp == rRef )
472 			return pComp->nId;
473 		pComp = ( LotusRange* ) List::Next();
474 	}
475 
476 	return ID_FAIL;
477 }
478 
479 
Append(LotusRange * pLR,const String & rName)480 void LotusRangeList::Append( LotusRange* pLR, const String& rName )
481 {
482 	DBG_ASSERT( pLR, "*LotusRangeList::Append(): das wird nichts!" );
483 	List::Insert( pLR, CONTAINER_APPEND );
484 
485 	ScTokenArray	aTokArray;
486 
487 	ScSingleRefData*	pSingRef = &aComplRef.Ref1;
488 
489 	pSingRef->nCol = pLR->nColStart;
490 	pSingRef->nRow = pLR->nRowStart;
491 
492 	if( pLR->IsSingle() )
493 		aTokArray.AddSingleReference( *pSingRef );
494 	else
495 	{
496 		pSingRef = &aComplRef.Ref2;
497 		pSingRef->nCol = pLR->nColEnd;
498 		pSingRef->nRow = pLR->nRowEnd;
499 		aTokArray.AddDoubleReference( aComplRef );
500 	}
501 
502 	ScRangeData*	pData = new ScRangeData(
503 		pLotusRoot->pDoc, rName, aTokArray );
504 
505 	pLotusRoot->pScRangeName->Insert( pData );
506 
507 	pLR->SetId( nIdCnt );
508 
509 	nIdCnt++;
510 }
511 
512 
513 
514 
RangeNameBufferWK3(void)515 RangeNameBufferWK3::RangeNameBufferWK3( void )
516 {
517 	pScTokenArray = new ScTokenArray;
518 	nIntCount = 1;
519 }
520 
521 
~RangeNameBufferWK3()522 RangeNameBufferWK3::~RangeNameBufferWK3()
523 {
524 	ENTRY*		pDel = ( ENTRY* ) List::First();
525 
526 	while( pDel )
527 	{
528 		delete pDel;
529 		pDel = ( ENTRY* ) List::Next();
530 	}
531 
532 	delete pScTokenArray;
533 }
534 
535 
Add(const String & rOrgName,const ScComplexRefData & rCRD)536 void RangeNameBufferWK3::Add( const String& rOrgName, const ScComplexRefData& rCRD )
537 {
538 	String				aScName( rOrgName );
539     ScfTools::ConvertToScDefinedName( aScName );
540 
541 	register ENTRY*		pInsert = new ENTRY( rOrgName, aScName, rCRD );
542 
543 	List::Insert( pInsert, CONTAINER_APPEND );
544 
545 	pScTokenArray->Clear();
546 
547 	register const ScSingleRefData&	rRef1 = rCRD.Ref1;
548 	register const ScSingleRefData&	rRef2 = rCRD.Ref2;
549 
550 	if( rRef1.nCol == rRef2.nCol && rRef1.nRow == rRef2.nRow && rRef1.nTab == rRef2.nTab )
551 	{
552 		pScTokenArray->AddSingleReference( rCRD.Ref1 );
553 		pInsert->bSingleRef = sal_True;
554 	}
555 	else
556 	{
557 		pScTokenArray->AddDoubleReference( rCRD );
558 		pInsert->bSingleRef = sal_False;
559 	}
560 
561 	ScRangeData*		pData = new ScRangeData( pLotusRoot->pDoc, aScName, *pScTokenArray );
562 
563 	pInsert->nRelInd = nIntCount;
564 	pData->SetIndex( nIntCount );
565 	nIntCount++;
566 
567 	pLotusRoot->pScRangeName->Insert( pData );
568 }
569 
570 
FindRel(const String & rRef,sal_uInt16 & rIndex)571 sal_Bool RangeNameBufferWK3::FindRel( const String& rRef, sal_uInt16& rIndex )
572 {
573 	StringHashEntry		aRef( rRef );
574 
575 	ENTRY*				pFind = ( ENTRY* ) List::First();
576 
577 	while( pFind )
578 	{
579 		if( aRef == pFind->aStrHashEntry )
580 		{
581 			rIndex = pFind->nRelInd;
582 			return sal_True;
583 		}
584 		pFind = ( ENTRY* ) List::Next();
585 	}
586 
587 	return sal_False;
588 }
589 
590 
FindAbs(const String & rRef,sal_uInt16 & rIndex)591 sal_Bool RangeNameBufferWK3::FindAbs( const String& rRef, sal_uInt16& rIndex )
592 {
593 	String				aTmp( rRef );
594 	StringHashEntry		aRef( aTmp.Erase( 0, 1 ) );	// ohne '$' suchen!
595 
596 	ENTRY*				pFind = ( ENTRY* ) List::First();
597 
598 	while( pFind )
599 	{
600 		if( aRef == pFind->aStrHashEntry )
601 		{
602 			// eventuell neuen Range Name aufbauen
603 			if( pFind->nAbsInd )
604 				rIndex = pFind->nAbsInd;
605 			else
606 			{
607 				ScSingleRefData*		pRef = &pFind->aScComplexRefDataRel.Ref1;
608 				pScTokenArray->Clear();
609 
610 				pRef->SetColRel( sal_False );
611 				pRef->SetRowRel( sal_False );
612 				pRef->SetTabRel( sal_True );
613 
614 				if( pFind->bSingleRef )
615 					pScTokenArray->AddSingleReference( *pRef );
616 				else
617 				{
618 					pRef = &pFind->aScComplexRefDataRel.Ref2;
619 					pRef->SetColRel( sal_False );
620 					pRef->SetRowRel( sal_False );
621 					pRef->SetTabRel( sal_True );
622 					pScTokenArray->AddDoubleReference( pFind->aScComplexRefDataRel );
623 				}
624 
625 				ScRangeData*	pData = new ScRangeData( pLotusRoot->pDoc, pFind->aScAbsName, *pScTokenArray );
626 
627 				rIndex = pFind->nAbsInd = nIntCount;
628 				pData->SetIndex( rIndex );
629 				nIntCount++;
630 
631 				pLotusRoot->pScRangeName->Insert( pData );
632 			}
633 
634 			return sal_True;
635 		}
636 		pFind = ( ENTRY* ) List::Next();
637 	}
638 
639 	return sal_False;
640 }
641 
642 
643