Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / sc / source / filter / excel / xetable.cxx
blobc9593400bdfab71a44ba07e4827f4ef11e58b0c7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "xetable.hxx"
22 #include <map>
23 #include <com/sun/star/i18n/ScriptType.hpp>
24 #include "scitems.hxx"
25 #include <svl/intitem.hxx>
26 #include "document.hxx"
27 #include "dociter.hxx"
28 #include "olinetab.hxx"
29 #include "formulacell.hxx"
30 #include "patattr.hxx"
31 #include "attrib.hxx"
32 #include "xehelper.hxx"
33 #include "xecontent.hxx"
34 #include "xeescher.hxx"
35 #include "xeextlst.hxx"
36 #include "tokenarray.hxx"
37 #include <thread>
38 #include <comphelper/threadpool.hxx>
40 #if defined(ANDROID)
41 namespace std
43 template<typename T>
44 T trunc(T x)
46 return ::trunc(x);
49 #endif
51 using namespace ::oox;
53 namespace ApiScriptType = ::com::sun::star::i18n::ScriptType;
55 // Helper records for cell records
57 XclExpStringRec::XclExpStringRec( const XclExpRoot& rRoot, const OUString& rResult ) :
58 XclExpRecord( EXC_ID3_STRING ),
59 mxResult( XclExpStringHelper::CreateString( rRoot, rResult ) )
61 OSL_ENSURE( (rRoot.GetBiff() <= EXC_BIFF5) || (mxResult->Len() > 0),
62 "XclExpStringRec::XclExpStringRec - empty result not allowed in BIFF8+" );
63 SetRecSize( mxResult->GetSize() );
66 void XclExpStringRec::WriteBody( XclExpStream& rStrm )
68 rStrm << *mxResult;
71 // Additional records for special formula ranges ==============================
73 XclExpRangeFmlaBase::XclExpRangeFmlaBase(
74 sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScAddress& rScPos ) :
75 XclExpRecord( nRecId, nRecSize ),
76 maXclRange( ScAddress::UNINITIALIZED ),
77 maBaseXclPos( ScAddress::UNINITIALIZED )
79 maBaseXclPos.Set( static_cast< sal_uInt16 >( rScPos.Col() ), static_cast< sal_uInt16 >( rScPos.Row() ) );
80 maXclRange.maFirst = maXclRange.maLast = maBaseXclPos;
83 XclExpRangeFmlaBase::XclExpRangeFmlaBase(
84 sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScRange& rScRange ) :
85 XclExpRecord( nRecId, nRecSize ),
86 maXclRange( ScAddress::UNINITIALIZED ),
87 maBaseXclPos( ScAddress::UNINITIALIZED )
89 maXclRange.Set(
90 static_cast< sal_uInt16 >( rScRange.aStart.Col() ),
91 static_cast< sal_uInt16 >( rScRange.aStart.Row() ),
92 static_cast< sal_uInt16 >( rScRange.aEnd.Col() ),
93 static_cast< sal_uInt16 >( rScRange.aEnd.Row() ) );
94 maBaseXclPos = maXclRange.maFirst;
97 bool XclExpRangeFmlaBase::IsBasePos( sal_uInt16 nXclCol, sal_uInt32 nXclRow ) const
99 return (maBaseXclPos.mnCol == nXclCol) && (maBaseXclPos.mnRow == nXclRow);
102 void XclExpRangeFmlaBase::Extend( const ScAddress& rScPos )
104 sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() );
105 sal_uInt32 nXclRow = static_cast< sal_uInt32 >( rScPos.Row() );
106 maXclRange.maFirst.mnCol = ::std::min( maXclRange.maFirst.mnCol, nXclCol );
107 maXclRange.maFirst.mnRow = ::std::min( maXclRange.maFirst.mnRow, nXclRow );
108 maXclRange.maLast.mnCol = ::std::max( maXclRange.maLast.mnCol, nXclCol );
109 maXclRange.maLast.mnRow = ::std::max( maXclRange.maLast.mnRow, nXclRow );
112 void XclExpRangeFmlaBase::WriteRangeAddress( XclExpStream& rStrm ) const
114 maXclRange.Write( rStrm, false );
117 // Array formulas =============================================================
119 XclExpArray::XclExpArray( XclTokenArrayRef xTokArr, const ScRange& rScRange ) :
120 XclExpRangeFmlaBase( EXC_ID3_ARRAY, 14 + xTokArr->GetSize(), rScRange ),
121 mxTokArr( xTokArr )
125 XclTokenArrayRef XclExpArray::CreateCellTokenArray( const XclExpRoot& rRoot ) const
127 return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos );
130 bool XclExpArray::IsVolatile() const
132 return mxTokArr->IsVolatile();
135 void XclExpArray::WriteBody( XclExpStream& rStrm )
137 WriteRangeAddress( rStrm );
138 sal_uInt16 nFlags = EXC_ARRAY_DEFAULTFLAGS;
139 ::set_flag( nFlags, EXC_ARRAY_RECALC_ALWAYS, IsVolatile() );
140 rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr;
143 XclExpArrayBuffer::XclExpArrayBuffer( const XclExpRoot& rRoot ) :
144 XclExpRoot( rRoot )
148 XclExpArrayRef XclExpArrayBuffer::CreateArray( const ScTokenArray& rScTokArr, const ScRange& rScRange )
150 const ScAddress& rScPos = rScRange.aStart;
151 XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_MATRIX, rScTokArr, &rScPos );
153 OSL_ENSURE( maRecMap.find( rScPos ) == maRecMap.end(), "XclExpArrayBuffer::CreateArray - array exists already" );
154 XclExpArrayRef& rxRec = maRecMap[ rScPos ];
155 rxRec.reset( new XclExpArray( xTokArr, rScRange ) );
156 return rxRec;
159 XclExpArrayRef XclExpArrayBuffer::FindArray( const ScTokenArray& rScTokArr, const ScAddress& rBasePos ) const
161 XclExpArrayRef xRec;
162 // try to extract a matrix reference token
163 if (rScTokArr.GetLen() != 1)
164 // Must consist of a single reference token.
165 return xRec;
167 const formula::FormulaToken* pToken = rScTokArr.GetArray()[0];
168 if (!pToken || pToken->GetOpCode() != ocMatRef)
169 // not a matrix reference token.
170 return xRec;
172 const ScSingleRefData& rRef = *pToken->GetSingleRef();
173 ScAddress aAbsPos = rRef.toAbs(rBasePos);
174 XclExpArrayMap::const_iterator it = maRecMap.find(aAbsPos);
176 return (it == maRecMap.end()) ? xRec : xRec = it->second;
179 // Shared formulas ============================================================
181 XclExpShrfmla::XclExpShrfmla( XclTokenArrayRef xTokArr, const ScAddress& rScPos ) :
182 XclExpRangeFmlaBase( EXC_ID_SHRFMLA, 10 + xTokArr->GetSize(), rScPos ),
183 mxTokArr( xTokArr ),
184 mnUsedCount( 1 )
188 void XclExpShrfmla::ExtendRange( const ScAddress& rScPos )
190 Extend( rScPos );
191 ++mnUsedCount;
194 XclTokenArrayRef XclExpShrfmla::CreateCellTokenArray( const XclExpRoot& rRoot ) const
196 return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos );
199 bool XclExpShrfmla::IsVolatile() const
201 return mxTokArr->IsVolatile();
204 void XclExpShrfmla::WriteBody( XclExpStream& rStrm )
206 WriteRangeAddress( rStrm );
207 rStrm << sal_uInt8( 0 ) << mnUsedCount << *mxTokArr;
210 XclExpShrfmlaBuffer::XclExpShrfmlaBuffer( const XclExpRoot& rRoot ) :
211 XclExpRoot( rRoot )
215 bool XclExpShrfmlaBuffer::IsValidTokenArray( const ScTokenArray& rArray ) const
217 using namespace formula;
219 FormulaToken** pTokens = rArray.GetArray();
220 sal_uInt16 nLen = rArray.GetLen();
221 for (sal_uInt16 i = 0; i < nLen; ++i)
223 const FormulaToken* p = pTokens[i];
224 switch (p->GetType())
226 case svSingleRef:
228 const ScSingleRefData& rRefData = *p->GetSingleRef();
229 if (!GetFormulaCompiler().IsRef2D(rRefData))
230 // Excel's shared formula cannot include 3D reference.
231 return false;
233 break;
234 case svDoubleRef:
236 const ScComplexRefData& rRefData = *p->GetDoubleRef();
237 if (!GetFormulaCompiler().IsRef2D(rRefData))
238 // Excel's shared formula cannot include 3D reference.
239 return false;
241 break;
242 case svExternalSingleRef:
243 case svExternalDoubleRef:
244 case svExternalName:
245 // External references aren't allowed.
246 return false;
247 default:
251 return true;
254 XclExpShrfmlaRef XclExpShrfmlaBuffer::CreateOrExtendShrfmla(
255 const ScFormulaCell& rScCell, const ScAddress& rScPos )
257 XclExpShrfmlaRef xRec;
258 const ScTokenArray* pShrdScTokArr = rScCell.GetSharedCode();
259 if (!pShrdScTokArr)
260 // This formula cell is not shared formula cell.
261 return xRec;
263 // Check to see if this shared formula contains any tokens that Excel's shared formula cannot handle.
264 if (maBadTokens.count(pShrdScTokArr) > 0)
265 // Already on the black list. Skip it.
266 return xRec;
268 if (!IsValidTokenArray(*pShrdScTokArr))
270 // We can't export this as shared formula.
271 maBadTokens.insert(pShrdScTokArr);
272 return xRec;
275 TokensType::iterator aIt = maRecMap.find(pShrdScTokArr);
276 if( aIt == maRecMap.end() )
278 // create a new record
279 XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_SHARED, *pShrdScTokArr, &rScPos );
280 xRec.reset( new XclExpShrfmla( xTokArr, rScPos ) );
281 maRecMap[ pShrdScTokArr ] = xRec;
283 else
285 // extend existing record
286 OSL_ENSURE( aIt->second, "XclExpShrfmlaBuffer::CreateOrExtendShrfmla - missing record" );
287 xRec = aIt->second;
288 xRec->ExtendRange( rScPos );
291 return xRec;
294 // Multiple operations ========================================================
296 XclExpTableop::XclExpTableop( const ScAddress& rScPos,
297 const XclMultipleOpRefs& rRefs, sal_uInt8 nScMode ) :
298 XclExpRangeFmlaBase( EXC_ID3_TABLEOP, 16, rScPos ),
299 mnLastAppXclCol( static_cast< sal_uInt16 >( rScPos.Col() ) ),
300 mnColInpXclCol( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Col() ) ),
301 mnColInpXclRow( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Row() ) ),
302 mnRowInpXclCol( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Col() ) ),
303 mnRowInpXclRow( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Row() ) ),
304 mnScMode( nScMode ),
305 mbValid( false )
309 bool XclExpTableop::TryExtend( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs )
311 sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() );
312 sal_uInt16 nXclRow = static_cast< sal_uInt16 >( rScPos.Row() );
314 bool bOk = IsAppendable( nXclCol, nXclRow );
315 if( bOk )
317 SCCOL nFirstScCol = static_cast< SCCOL >( maXclRange.maFirst.mnCol );
318 SCROW nFirstScRow = static_cast< SCROW >( maXclRange.maFirst.mnRow );
319 SCCOL nColInpScCol = static_cast< SCCOL >( mnColInpXclCol );
320 SCROW nColInpScRow = static_cast< SCROW >( mnColInpXclRow );
321 SCCOL nRowInpScCol = static_cast< SCCOL >( mnRowInpXclCol );
322 SCROW nRowInpScRow = static_cast< SCROW >( mnRowInpXclRow );
324 bOk = ((mnScMode == 2) == rRefs.mbDblRefMode) &&
325 (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) &&
326 (nColInpScCol == rRefs.maColFirstScPos.Col()) &&
327 (nColInpScRow == rRefs.maColFirstScPos.Row()) &&
328 (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) &&
329 (rScPos.Tab() == rRefs.maColRelScPos.Tab());
331 if( bOk ) switch( mnScMode )
333 case 0:
334 bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col()) &&
335 (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) &&
336 (nFirstScCol == rRefs.maColRelScPos.Col() + 1) &&
337 (rScPos.Row() == rRefs.maColRelScPos.Row());
338 break;
339 case 1:
340 bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) &&
341 (rScPos.Row() == rRefs.maFmlaScPos.Row()) &&
342 (rScPos.Col() == rRefs.maColRelScPos.Col()) &&
343 (nFirstScRow == rRefs.maColRelScPos.Row() + 1);
344 break;
345 case 2:
346 bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) &&
347 (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) &&
348 (nFirstScCol == rRefs.maColRelScPos.Col() + 1) &&
349 (rScPos.Row() == rRefs.maColRelScPos.Row()) &&
350 (nRowInpScCol == rRefs.maRowFirstScPos.Col()) &&
351 (nRowInpScRow == rRefs.maRowFirstScPos.Row()) &&
352 (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) &&
353 (rScPos.Col() == rRefs.maRowRelScPos.Col()) &&
354 (nFirstScRow == rRefs.maRowRelScPos.Row() + 1) &&
355 (rScPos.Tab() == rRefs.maRowRelScPos.Tab());
356 break;
357 default:
358 bOk = false;
361 if( bOk )
363 // extend the cell range
364 OSL_ENSURE( IsAppendable( nXclCol, nXclRow ), "XclExpTableop::TryExtend - wrong cell address" );
365 Extend( rScPos );
366 mnLastAppXclCol = nXclCol;
370 return bOk;
373 void XclExpTableop::Finalize()
375 // is the range complete? (last appended cell is in last column)
376 mbValid = maXclRange.maLast.mnCol == mnLastAppXclCol;
377 // if last row is incomplete, try to shorten the used range
378 if( !mbValid && (maXclRange.maFirst.mnRow < maXclRange.maLast.mnRow) )
380 --maXclRange.maLast.mnRow;
381 mbValid = true;
384 // check if referred cells are outside of own range
385 if( mbValid ) switch( mnScMode )
387 case 0:
388 mbValid = (mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
389 (mnColInpXclRow < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow);
390 break;
391 case 1:
392 mbValid = (mnColInpXclCol < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
393 (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow);
394 break;
395 case 2:
396 mbValid = ((mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
397 (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow)) &&
398 ((mnRowInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnRowInpXclCol > maXclRange.maLast.mnCol) ||
399 (mnRowInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnRowInpXclRow > maXclRange.maLast.mnRow));
400 break;
404 XclTokenArrayRef XclExpTableop::CreateCellTokenArray( const XclExpRoot& rRoot ) const
406 XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler();
407 return mbValid ?
408 rFmlaComp.CreateSpecialRefFormula( EXC_TOKID_TBL, maBaseXclPos ) :
409 rFmlaComp.CreateErrorFormula( EXC_ERR_NA );
412 bool XclExpTableop::IsVolatile() const
414 return true;
417 void XclExpTableop::Save( XclExpStream& rStrm )
419 if( mbValid )
420 XclExpRangeFmlaBase::Save( rStrm );
423 bool XclExpTableop::IsAppendable( sal_uInt16 nXclCol, sal_uInt16 nXclRow ) const
425 return ((nXclCol == mnLastAppXclCol + 1) && (nXclRow == maXclRange.maFirst.mnRow)) ||
426 ((nXclCol == mnLastAppXclCol + 1) && (nXclCol <= maXclRange.maLast.mnCol) && (nXclRow == maXclRange.maLast.mnRow)) ||
427 ((mnLastAppXclCol == maXclRange.maLast.mnCol) && (nXclCol == maXclRange.maFirst.mnCol) && (nXclRow == maXclRange.maLast.mnRow + 1));
430 void XclExpTableop::WriteBody( XclExpStream& rStrm )
432 sal_uInt16 nFlags = EXC_TABLEOP_DEFAULTFLAGS;
433 ::set_flag( nFlags, EXC_TABLEOP_RECALC_ALWAYS, IsVolatile() );
434 switch( mnScMode )
436 case 1: ::set_flag( nFlags, EXC_TABLEOP_ROW ); break;
437 case 2: ::set_flag( nFlags, EXC_TABLEOP_BOTH ); break;
440 WriteRangeAddress( rStrm );
441 rStrm << nFlags;
442 if( mnScMode == 2 )
443 rStrm << mnRowInpXclRow << mnRowInpXclCol << mnColInpXclRow << mnColInpXclCol;
444 else
445 rStrm << mnColInpXclRow << mnColInpXclCol << sal_uInt32( 0 );
448 XclExpTableopBuffer::XclExpTableopBuffer( const XclExpRoot& rRoot ) :
449 XclExpRoot( rRoot )
453 XclExpTableopRef XclExpTableopBuffer::CreateOrExtendTableop(
454 const ScTokenArray& rScTokArr, const ScAddress& rScPos )
456 XclExpTableopRef xRec;
458 // try to extract cell references of a multiple operations formula
459 XclMultipleOpRefs aRefs;
460 if (XclTokenArrayHelper::GetMultipleOpRefs(aRefs, rScTokArr, rScPos))
462 // try to find an existing TABLEOP record for this cell position
463 for( size_t nPos = 0, nSize = maTableopList.GetSize(); !xRec && (nPos < nSize); ++nPos )
465 XclExpTableopRef xTempRec = maTableopList.GetRecord( nPos );
466 if( xTempRec->TryExtend( rScPos, aRefs ) )
467 xRec = xTempRec;
470 // no record found, or found record not extensible
471 if( !xRec )
472 xRec = TryCreate( rScPos, aRefs );
475 return xRec;
478 void XclExpTableopBuffer::Finalize()
480 for( size_t nPos = 0, nSize = maTableopList.GetSize(); nPos < nSize; ++nPos )
481 maTableopList.GetRecord( nPos )->Finalize();
484 XclExpTableopRef XclExpTableopBuffer::TryCreate( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs )
486 sal_uInt8 nScMode = 0;
487 bool bOk = (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) &&
488 (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) &&
489 (rScPos.Tab() == rRefs.maColRelScPos.Tab());
491 if( bOk )
493 if( rRefs.mbDblRefMode )
495 nScMode = 2;
496 bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) &&
497 (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) &&
498 (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) &&
499 (rScPos.Row() == rRefs.maColRelScPos.Row()) &&
500 (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) &&
501 (rScPos.Col() == rRefs.maRowRelScPos.Col()) &&
502 (rScPos.Row() == rRefs.maRowRelScPos.Row() + 1) &&
503 (rScPos.Tab() == rRefs.maRowRelScPos.Tab());
505 else if( (rScPos.Col() == rRefs.maFmlaScPos.Col()) &&
506 (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) &&
507 (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) &&
508 (rScPos.Row() == rRefs.maColRelScPos.Row()) )
510 nScMode = 0;
512 else if( (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) &&
513 (rScPos.Row() == rRefs.maFmlaScPos.Row()) &&
514 (rScPos.Col() == rRefs.maColRelScPos.Col()) &&
515 (rScPos.Row() == rRefs.maColRelScPos.Row() + 1) )
517 nScMode = 1;
519 else
521 bOk = false;
525 XclExpTableopRef xRec;
526 if( bOk )
528 xRec.reset( new XclExpTableop( rScPos, rRefs, nScMode ) );
529 maTableopList.AppendRecord( xRec );
532 return xRec;
535 // Cell records
537 XclExpCellBase::XclExpCellBase(
538 sal_uInt16 nRecId, sal_Size nContSize, const XclAddress& rXclPos ) :
539 XclExpRecord( nRecId, nContSize + 4 ),
540 maXclPos( rXclPos )
544 bool XclExpCellBase::IsMultiLineText() const
546 return false;
549 bool XclExpCellBase::TryMerge( const XclExpCellBase& /*rCell*/ )
551 return false;
554 void XclExpCellBase::GetBlankXFIndexes( ScfUInt16Vec& /*rXFIndexes*/ ) const
556 // default: do nothing
559 void XclExpCellBase::RemoveUnusedBlankCells( const ScfUInt16Vec& /*rXFIndexes*/ )
561 // default: do nothing
564 // Single cell records ========================================================
566 XclExpSingleCellBase::XclExpSingleCellBase(
567 sal_uInt16 nRecId, sal_Size nContSize, const XclAddress& rXclPos, sal_uInt32 nXFId ) :
568 XclExpCellBase( nRecId, 2, rXclPos ),
569 maXFId( nXFId ),
570 mnContSize( nContSize )
574 XclExpSingleCellBase::XclExpSingleCellBase( const XclExpRoot& rRoot,
575 sal_uInt16 nRecId, sal_Size nContSize, const XclAddress& rXclPos,
576 const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForcedXFId ) :
577 XclExpCellBase( nRecId, 2, rXclPos ),
578 maXFId( nForcedXFId ),
579 mnContSize( nContSize )
581 if( GetXFId() == EXC_XFID_NOTFOUND )
582 SetXFId( rRoot.GetXFBuffer().Insert( pPattern, nScript ) );
585 sal_uInt16 XclExpSingleCellBase::GetLastXclCol() const
587 return GetXclCol();
590 sal_uInt32 XclExpSingleCellBase::GetFirstXFId() const
592 return GetXFId();
595 bool XclExpSingleCellBase::IsEmpty() const
597 return false;
600 void XclExpSingleCellBase::ConvertXFIndexes( const XclExpRoot& rRoot )
602 maXFId.ConvertXFIndex( rRoot );
605 void XclExpSingleCellBase::Save( XclExpStream& rStrm )
607 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
608 AddRecSize( mnContSize );
609 XclExpCellBase::Save( rStrm );
612 void XclExpSingleCellBase::WriteBody( XclExpStream& rStrm )
614 rStrm << static_cast<sal_uInt16> (GetXclRow()) << GetXclCol() << maXFId.mnXFIndex;
615 WriteContents( rStrm );
618 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpNumberCell )
620 XclExpNumberCell::XclExpNumberCell(
621 const XclExpRoot& rRoot, const XclAddress& rXclPos,
622 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, double fValue ) :
623 // #i41210# always use latin script for number cells - may look wrong for special number formats...
624 XclExpSingleCellBase( rRoot, EXC_ID3_NUMBER, 8, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ),
625 mfValue( fValue )
629 static OString lcl_GetStyleId( XclExpXmlStream& rStrm, sal_uInt32 nXFIndex )
631 return OString::number( rStrm.GetRoot().GetXFBuffer()
632 .GetXmlCellIndex( nXFIndex ) );
635 static OString lcl_GetStyleId( XclExpXmlStream& rStrm, const XclExpCellBase& rCell )
637 sal_uInt32 nXFId = rCell.GetFirstXFId();
638 sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( nXFId );
639 return lcl_GetStyleId( rStrm, nXFIndex );
642 void XclExpNumberCell::SaveXml( XclExpXmlStream& rStrm )
644 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
645 rWorksheet->startElement( XML_c,
646 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), GetXclPos() ).getStr(),
647 XML_s, lcl_GetStyleId( rStrm, *this ).getStr(),
648 XML_t, "n",
649 // OOXTODO: XML_cm, XML_vm, XML_ph
650 FSEND );
651 rWorksheet->startElement( XML_v, FSEND );
652 rWorksheet->write( mfValue );
653 rWorksheet->endElement( XML_v );
654 rWorksheet->endElement( XML_c );
657 void XclExpNumberCell::WriteContents( XclExpStream& rStrm )
659 rStrm << mfValue;
662 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpBooleanCell )
664 XclExpBooleanCell::XclExpBooleanCell(
665 const XclExpRoot& rRoot, const XclAddress& rXclPos,
666 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, bool bValue ) :
667 // #i41210# always use latin script for boolean cells
668 XclExpSingleCellBase( rRoot, EXC_ID3_BOOLERR, 2, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ),
669 mbValue( bValue )
673 void XclExpBooleanCell::SaveXml( XclExpXmlStream& rStrm )
675 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
676 rWorksheet->startElement( XML_c,
677 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), GetXclPos() ).getStr(),
678 XML_s, lcl_GetStyleId( rStrm, *this ).getStr(),
679 XML_t, "b",
680 // OOXTODO: XML_cm, XML_vm, XML_ph
681 FSEND );
682 rWorksheet->startElement( XML_v, FSEND );
683 rWorksheet->write( mbValue ? "1" : "0" );
684 rWorksheet->endElement( XML_v );
685 rWorksheet->endElement( XML_c );
688 void XclExpBooleanCell::WriteContents( XclExpStream& rStrm )
690 rStrm << sal_uInt16( mbValue ? 1 : 0 ) << EXC_BOOLERR_BOOL;
693 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpLabelCell )
695 XclExpLabelCell::XclExpLabelCell(
696 const XclExpRoot& rRoot, const XclAddress& rXclPos,
697 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, const OUString& rStr ) :
698 XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId )
700 sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN;
701 XclExpStringRef xText = XclExpStringHelper::CreateCellString(
702 rRoot, rStr, pPattern, EXC_STR_DEFAULT, nMaxLen);
703 Init( rRoot, pPattern, xText );
706 XclExpLabelCell::XclExpLabelCell(
707 const XclExpRoot& rRoot, const XclAddress& rXclPos,
708 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId,
709 const EditTextObject* pEditText, XclExpHyperlinkHelper& rLinkHelper ) :
710 XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId )
712 sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN;
714 XclExpStringRef xText;
715 if (pEditText)
716 xText = XclExpStringHelper::CreateCellString(
717 rRoot, *pEditText, pPattern, rLinkHelper, EXC_STR_DEFAULT, nMaxLen);
718 else
719 xText = XclExpStringHelper::CreateCellString(
720 rRoot, EMPTY_OUSTRING, pPattern, EXC_STR_DEFAULT, nMaxLen);
722 Init( rRoot, pPattern, xText );
725 bool XclExpLabelCell::IsMultiLineText() const
727 return mbLineBreak || mxText->IsWrapped();
730 void XclExpLabelCell::Init( const XclExpRoot& rRoot,
731 const ScPatternAttr* pPattern, XclExpStringRef xText )
733 OSL_ENSURE( xText && xText->Len(), "XclExpLabelCell::XclExpLabelCell - empty string passed" );
734 mxText = xText;
735 mnSstIndex = 0;
737 const XclFormatRunVec& rFormats = mxText->GetFormats();
738 // remove formatting of the leading run if the entire string
739 // is equally formatted
740 sal_uInt16 nXclFont = EXC_FONT_NOTFOUND;
741 if( rFormats.size() == 1 )
742 nXclFont = mxText->RemoveLeadingFont();
743 else
744 nXclFont = mxText->GetLeadingFont();
746 // create cell format
747 if( GetXFId() == EXC_XFID_NOTFOUND )
749 OSL_ENSURE( nXclFont != EXC_FONT_NOTFOUND, "XclExpLabelCell::Init - leading font not found" );
750 bool bForceLineBreak = mxText->IsWrapped();
751 SetXFId( rRoot.GetXFBuffer().InsertWithFont( pPattern, ApiScriptType::WEAK, nXclFont, bForceLineBreak ) );
754 // get auto-wrap attribute from cell format
755 const XclExpXF* pXF = rRoot.GetXFBuffer().GetXFById( GetXFId() );
756 mbLineBreak = pXF && pXF->GetAlignmentData().mbLineBreak;
758 // initialize the record contents
759 switch( rRoot.GetBiff() )
761 case EXC_BIFF5:
762 // BIFF5-BIFF7: create a LABEL or RSTRING record
763 OSL_ENSURE( mxText->Len() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::XclExpLabelCell - string too long" );
764 SetContSize( mxText->GetSize() );
765 // formatted string is exported in an RSTRING record
766 if( mxText->IsRich() )
768 OSL_ENSURE( mxText->GetFormatsCount() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::WriteContents - too many formats" );
769 mxText->LimitFormatCount( EXC_LABEL_MAXLEN );
770 SetRecId( EXC_ID_RSTRING );
771 SetContSize( GetContSize() + 1 + 2 * mxText->GetFormatsCount() );
773 break;
774 case EXC_BIFF8:
775 // BIFF8+: create a LABELSST record
776 mnSstIndex = rRoot.GetSst().Insert( xText );
777 SetRecId( EXC_ID_LABELSST );
778 SetContSize( 4 );
779 break;
780 default: DBG_ERROR_BIFF();
784 void XclExpLabelCell::SaveXml( XclExpXmlStream& rStrm )
786 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
787 rWorksheet->startElement( XML_c,
788 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), GetXclPos() ).getStr(),
789 XML_s, lcl_GetStyleId( rStrm, *this ).getStr(),
790 XML_t, "s",
791 // OOXTODO: XML_cm, XML_vm, XML_ph
792 FSEND );
793 rWorksheet->startElement( XML_v, FSEND );
794 rWorksheet->write( (sal_Int32) mnSstIndex );
795 rWorksheet->endElement( XML_v );
796 rWorksheet->endElement( XML_c );
799 void XclExpLabelCell::WriteContents( XclExpStream& rStrm )
801 switch( rStrm.GetRoot().GetBiff() )
803 case EXC_BIFF5:
804 rStrm << *mxText;
805 if( mxText->IsRich() )
807 rStrm << static_cast< sal_uInt8 >( mxText->GetFormatsCount() );
808 mxText->WriteFormats( rStrm );
810 break;
811 case EXC_BIFF8:
812 rStrm << mnSstIndex;
813 break;
814 default: DBG_ERROR_BIFF();
818 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpFormulaCell )
820 XclExpFormulaCell::XclExpFormulaCell(
821 const XclExpRoot& rRoot, const XclAddress& rXclPos,
822 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId,
823 const ScFormulaCell& rScFmlaCell,
824 XclExpArrayBuffer& rArrayBfr,
825 XclExpShrfmlaBuffer& rShrfmlaBfr,
826 XclExpTableopBuffer& rTableopBfr ) :
827 XclExpSingleCellBase( EXC_ID2_FORMULA, 0, rXclPos, nForcedXFId ),
828 mrScFmlaCell( const_cast< ScFormulaCell& >( rScFmlaCell ) )
830 // *** Find result number format overwriting cell number format *** -------
832 if( GetXFId() == EXC_XFID_NOTFOUND )
834 SvNumberFormatter& rFormatter = rRoot.GetFormatter();
835 XclExpNumFmtBuffer& rNumFmtBfr = rRoot.GetNumFmtBuffer();
837 // current cell number format
838 sal_uInt32 nScNumFmt = pPattern ?
839 GETITEMVALUE( pPattern->GetItemSet(), SfxUInt32Item, ATTR_VALUE_FORMAT, sal_uLong ) :
840 rNumFmtBfr.GetStandardFormat();
842 // alternative number format passed to XF buffer
843 sal_uInt32 nAltScNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
844 /* Xcl doesn't know Boolean number formats, we write
845 "TRUE";"FALSE" (language dependent). Don't do it for automatic
846 formula formats, because Excel gets them right. */
847 /* #i8640# Don't set text format, if we have string results. */
848 short nFormatType = mrScFmlaCell.GetFormatType();
849 if( ((nScNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) &&
850 (nFormatType != css::util::NumberFormat::LOGICAL) &&
851 (nFormatType != css::util::NumberFormat::TEXT) )
852 nAltScNumFmt = nScNumFmt;
853 /* If cell number format is Boolean and automatic formula
854 format is Boolean don't write that ugly special format. */
855 else if( (nFormatType == css::util::NumberFormat::LOGICAL) &&
856 (rFormatter.GetType( nScNumFmt ) == css::util::NumberFormat::LOGICAL) )
857 nAltScNumFmt = rNumFmtBfr.GetStandardFormat();
859 // #i41420# find script type according to result type (always latin for numeric results)
860 sal_Int16 nScript = ApiScriptType::LATIN;
861 bool bForceLineBreak = false;
862 if( nFormatType == css::util::NumberFormat::TEXT )
864 OUString aResult = mrScFmlaCell.GetString().getString();
865 bForceLineBreak = mrScFmlaCell.IsMultilineResult();
866 nScript = XclExpStringHelper::GetLeadingScriptType( rRoot, aResult );
868 SetXFId( rRoot.GetXFBuffer().InsertWithNumFmt( pPattern, nScript, nAltScNumFmt, bForceLineBreak ) );
871 // *** Convert the formula token array *** --------------------------------
873 ScAddress aScPos( static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), rRoot.GetCurrScTab() );
874 const ScTokenArray& rScTokArr = *mrScFmlaCell.GetCode();
876 // first try to create multiple operations
877 mxAddRec = rTableopBfr.CreateOrExtendTableop( rScTokArr, aScPos );
879 // no multiple operation found - try to create matrix formula
880 if( !mxAddRec ) switch( static_cast< ScMatrixMode >( mrScFmlaCell.GetMatrixFlag() ) )
882 case MM_FORMULA:
884 // origin of the matrix - find the used matrix range
885 SCCOL nMatWidth;
886 SCROW nMatHeight;
887 mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight );
888 OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" );
889 ScRange aMatScRange( aScPos );
890 ScAddress& rMatEnd = aMatScRange.aEnd;
891 rMatEnd.IncCol( static_cast< SCsCOL >( nMatWidth - 1 ) );
892 rMatEnd.IncRow( static_cast< SCsROW >( nMatHeight - 1 ) );
893 // reduce to valid range (range keeps valid, because start position IS valid)
894 rRoot.GetAddressConverter().ValidateRange( aMatScRange, true );
895 // create the ARRAY record
896 mxAddRec = rArrayBfr.CreateArray( rScTokArr, aMatScRange );
898 break;
899 case MM_REFERENCE:
901 // other formula cell covered by a matrix - find the ARRAY record
902 mxAddRec = rArrayBfr.FindArray(rScTokArr, aScPos);
903 // should always be found, if Calc document is not broken
904 OSL_ENSURE( mxAddRec, "XclExpFormulaCell::XclExpFormulaCell - no matrix found" );
906 break;
907 default:;
910 // no matrix found - try to create shared formula
911 if( !mxAddRec )
912 mxAddRec = rShrfmlaBfr.CreateOrExtendShrfmla(mrScFmlaCell, aScPos);
914 // no shared formula found - create a simple cell formula
915 if( !mxAddRec )
916 mxTokArr = rRoot.GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CELL, rScTokArr, &aScPos );
919 void XclExpFormulaCell::Save( XclExpStream& rStrm )
921 // create token array for FORMULA cells with additional record
922 if( mxAddRec )
923 mxTokArr = mxAddRec->CreateCellTokenArray( rStrm.GetRoot() );
925 // FORMULA record itself
926 OSL_ENSURE( mxTokArr, "XclExpFormulaCell::Save - missing token array" );
927 if( !mxTokArr )
928 mxTokArr = rStrm.GetRoot().GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NA );
929 SetContSize( 16 + mxTokArr->GetSize() );
930 XclExpSingleCellBase::Save( rStrm );
932 // additional record (ARRAY, SHRFMLA, or TABLEOP), only for first FORMULA record
933 if( mxAddRec && mxAddRec->IsBasePos( GetXclCol(), GetXclRow() ) )
934 mxAddRec->Save( rStrm );
936 // STRING record for string result
937 if( mxStringRec )
938 mxStringRec->Save( rStrm );
941 void XclExpFormulaCell::SaveXml( XclExpXmlStream& rStrm )
943 const char* sType = nullptr;
944 OUString sValue;
945 XclXmlUtils::GetFormulaTypeAndValue( mrScFmlaCell, sType, sValue );
946 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
947 rWorksheet->startElement( XML_c,
948 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), GetXclPos() ).getStr(),
949 XML_s, lcl_GetStyleId( rStrm, *this ).getStr(),
950 XML_t, sType,
951 // OOXTODO: XML_cm, XML_vm, XML_ph
952 FSEND );
954 bool bWriteFormula = true;
955 bool bTagStarted = false;
956 ScAddress aScPos( static_cast< SCCOL >( GetXclPos().mnCol ),
957 static_cast< SCROW >( GetXclPos().mnRow ), rStrm.GetRoot().GetCurrScTab() );
959 switch (mrScFmlaCell.GetMatrixFlag())
961 case MM_NONE:
962 break;
963 case MM_REFERENCE:
964 bWriteFormula = false;
965 break;
966 case MM_FORMULA:
968 // origin of the matrix - find the used matrix range
969 SCCOL nMatWidth;
970 SCROW nMatHeight;
971 mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight );
972 OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" );
973 ScRange aMatScRange( aScPos );
974 ScAddress& rMatEnd = aMatScRange.aEnd;
975 rMatEnd.IncCol( static_cast< SCsCOL >( nMatWidth - 1 ) );
976 rMatEnd.IncRow( static_cast< SCsROW >( nMatHeight - 1 ) );
977 // reduce to valid range (range keeps valid, because start position IS valid
978 rStrm.GetRoot().GetAddressConverter().ValidateRange( aMatScRange, true );
980 OStringBuffer sFmlaCellRange;
981 if (ValidRange(aMatScRange))
983 // calculate the cell range.
984 sFmlaCellRange.append( XclXmlUtils::ToOString(
985 rStrm.GetRoot().GetStringBuf(), aMatScRange.aStart ).getStr());
986 sFmlaCellRange.append(":");
987 sFmlaCellRange.append( XclXmlUtils::ToOString(
988 rStrm.GetRoot().GetStringBuf(), aMatScRange.aEnd ).getStr());
991 if ( aMatScRange.aStart.Col() == GetXclPos().mnCol &&
992 aMatScRange.aStart.Row() == static_cast<SCROW>(GetXclPos().mnRow))
994 rWorksheet->startElement( XML_f,
995 XML_aca, XclXmlUtils::ToPsz( (mxTokArr && mxTokArr->IsVolatile()) ||
996 (mxAddRec && mxAddRec->IsVolatile())),
997 XML_t, mxAddRec ? "array" : nullptr,
998 XML_ref, !sFmlaCellRange.isEmpty()? sFmlaCellRange.getStr() : nullptr,
999 // OOXTODO: XML_dt2D, bool
1000 // OOXTODO: XML_dtr, bool
1001 // OOXTODO: XML_del1, bool
1002 // OOXTODO: XML_del2, bool
1003 // OOXTODO: XML_r1, ST_CellRef
1004 // OOXTODO: XML_r2, ST_CellRef
1005 // OOXTODO: XML_ca, bool
1006 // OOXTODO: XML_si, uint
1007 // OOXTODO: XML_bx bool
1008 FSEND );
1009 bTagStarted = true;
1012 break;
1015 if (bWriteFormula)
1017 if (!bTagStarted)
1019 rWorksheet->startElement( XML_f,
1020 XML_aca, XclXmlUtils::ToPsz( (mxTokArr && mxTokArr->IsVolatile()) ||
1021 (mxAddRec && mxAddRec->IsVolatile()) ),
1022 FSEND );
1024 rWorksheet->writeEscaped( XclXmlUtils::ToOUString(
1025 rStrm.GetRoot().GetCompileFormulaContext(), mrScFmlaCell.aPos, mrScFmlaCell.GetCode()));
1026 rWorksheet->endElement( XML_f );
1029 if( strcmp( sType, "inlineStr" ) == 0 )
1031 rWorksheet->startElement( XML_is, FSEND );
1032 rWorksheet->startElement( XML_t, FSEND );
1033 rWorksheet->writeEscaped( sValue );
1034 rWorksheet->endElement( XML_t );
1035 rWorksheet->endElement( XML_is );
1037 else
1039 rWorksheet->startElement( XML_v, FSEND );
1040 rWorksheet->writeEscaped( sValue );
1041 rWorksheet->endElement( XML_v );
1043 rWorksheet->endElement( XML_c );
1046 void XclExpFormulaCell::WriteContents( XclExpStream& rStrm )
1048 sal_uInt16 nScErrCode = mrScFmlaCell.GetErrCode();
1049 if( nScErrCode )
1051 rStrm << EXC_FORMULA_RES_ERROR << sal_uInt8( 0 )
1052 << XclTools::GetXclErrorCode( nScErrCode )
1053 << sal_uInt8( 0 ) << sal_uInt16( 0 )
1054 << sal_uInt16( 0xFFFF );
1056 else
1058 // result of the formula
1059 switch( mrScFmlaCell.GetFormatType() )
1061 case css::util::NumberFormat::NUMBER:
1063 // either value or error code
1064 rStrm << mrScFmlaCell.GetValue();
1066 break;
1068 case css::util::NumberFormat::TEXT:
1070 OUString aResult = mrScFmlaCell.GetString().getString();
1071 if( !aResult.isEmpty() || (rStrm.GetRoot().GetBiff() <= EXC_BIFF5) )
1073 rStrm << EXC_FORMULA_RES_STRING;
1074 mxStringRec.reset( new XclExpStringRec( rStrm.GetRoot(), aResult ) );
1076 else
1077 rStrm << EXC_FORMULA_RES_EMPTY; // BIFF8 only
1078 rStrm << sal_uInt8( 0 ) << sal_uInt32( 0 ) << sal_uInt16( 0xFFFF );
1080 break;
1082 case css::util::NumberFormat::LOGICAL:
1084 sal_uInt8 nXclValue = (mrScFmlaCell.GetValue() == 0.0) ? 0 : 1;
1085 rStrm << EXC_FORMULA_RES_BOOL << sal_uInt8( 0 )
1086 << nXclValue << sal_uInt8( 0 ) << sal_uInt16( 0 )
1087 << sal_uInt16( 0xFFFF );
1089 break;
1091 default:
1092 rStrm << mrScFmlaCell.GetValue();
1096 // flags and formula token array
1097 sal_uInt16 nFlags = EXC_FORMULA_DEFAULTFLAGS;
1098 ::set_flag( nFlags, EXC_FORMULA_RECALC_ALWAYS, mxTokArr->IsVolatile() || (mxAddRec && mxAddRec->IsVolatile()) );
1099 ::set_flag( nFlags, EXC_FORMULA_SHARED, mxAddRec && (mxAddRec->GetRecId() == EXC_ID_SHRFMLA) );
1100 rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr;
1103 // Multiple cell records ======================================================
1105 XclExpMultiCellBase::XclExpMultiCellBase(
1106 sal_uInt16 nRecId, sal_uInt16 nMulRecId, sal_Size nContSize, const XclAddress& rXclPos ) :
1107 XclExpCellBase( nRecId, 0, rXclPos ),
1108 mnMulRecId( nMulRecId ),
1109 mnContSize( nContSize )
1113 sal_uInt16 XclExpMultiCellBase::GetLastXclCol() const
1115 return GetXclCol() + GetCellCount() - 1;
1118 sal_uInt32 XclExpMultiCellBase::GetFirstXFId() const
1120 return maXFIds.empty() ? XclExpXFBuffer::GetDefCellXFId() : maXFIds.front().mnXFId;
1123 bool XclExpMultiCellBase::IsEmpty() const
1125 return maXFIds.empty();
1128 void XclExpMultiCellBase::ConvertXFIndexes( const XclExpRoot& rRoot )
1130 for( XclExpMultiXFIdDeq::iterator aIt = maXFIds.begin(), aEnd = maXFIds.end(); aIt != aEnd; ++aIt )
1131 aIt->ConvertXFIndex( rRoot );
1134 void XclExpMultiCellBase::Save( XclExpStream& rStrm )
1136 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
1138 XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end();
1139 XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin();
1140 XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg;
1141 sal_uInt16 nBegXclCol = GetXclCol();
1142 sal_uInt16 nEndXclCol = nBegXclCol;
1144 while( aRangeEnd != aEnd )
1146 // find begin of next used XF range
1147 aRangeBeg = aRangeEnd;
1148 nBegXclCol = nEndXclCol;
1149 while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) )
1151 nBegXclCol = nBegXclCol + aRangeBeg->mnCount;
1152 ++aRangeBeg;
1154 // find end of next used XF range
1155 aRangeEnd = aRangeBeg;
1156 nEndXclCol = nBegXclCol;
1157 while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) )
1159 nEndXclCol = nEndXclCol + aRangeEnd->mnCount;
1160 ++aRangeEnd;
1163 // export this range as a record
1164 if( aRangeBeg != aRangeEnd )
1166 sal_uInt16 nCount = nEndXclCol - nBegXclCol;
1167 bool bIsMulti = nCount > 1;
1168 sal_Size nTotalSize = GetRecSize() + (2 + mnContSize) * nCount;
1169 if( bIsMulti ) nTotalSize += 2;
1171 rStrm.StartRecord( bIsMulti ? mnMulRecId : GetRecId(), nTotalSize );
1172 rStrm << static_cast<sal_uInt16> (GetXclRow()) << nBegXclCol;
1174 sal_uInt16 nRelCol = nBegXclCol - GetXclCol();
1175 for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt )
1177 for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx )
1179 rStrm << aIt->mnXFIndex;
1180 WriteContents( rStrm, nRelCol );
1181 ++nRelCol;
1184 if( bIsMulti )
1185 rStrm << static_cast< sal_uInt16 >( nEndXclCol - 1 );
1186 rStrm.EndRecord();
1191 void XclExpMultiCellBase::SaveXml( XclExpXmlStream& rStrm )
1193 XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end();
1194 XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin();
1195 XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg;
1196 sal_uInt16 nBegXclCol = GetXclCol();
1197 sal_uInt16 nEndXclCol = nBegXclCol;
1199 while( aRangeEnd != aEnd )
1201 // find begin of next used XF range
1202 aRangeBeg = aRangeEnd;
1203 nBegXclCol = nEndXclCol;
1204 while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) )
1206 nBegXclCol = nBegXclCol + aRangeBeg->mnCount;
1207 ++aRangeBeg;
1209 // find end of next used XF range
1210 aRangeEnd = aRangeBeg;
1211 nEndXclCol = nBegXclCol;
1212 while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) )
1214 nEndXclCol = nEndXclCol + aRangeEnd->mnCount;
1215 ++aRangeEnd;
1218 // export this range as a record
1219 if( aRangeBeg != aRangeEnd )
1221 sal_uInt16 nRelColIdx = nBegXclCol - GetXclCol();
1222 sal_Int32 nRelCol = 0;
1223 for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt )
1225 for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx )
1227 WriteXmlContents(
1228 rStrm,
1229 XclAddress( static_cast<sal_uInt16>(nBegXclCol + nRelCol), GetXclRow() ),
1230 aIt->mnXFIndex,
1231 nRelColIdx );
1232 ++nRelCol;
1233 ++nRelColIdx;
1240 sal_uInt16 XclExpMultiCellBase::GetCellCount() const
1242 sal_uInt16 nCount = 0;
1243 for( XclExpMultiXFIdDeq::const_iterator aIt = maXFIds.begin(), aEnd = maXFIds.end(); aIt != aEnd; ++aIt )
1244 nCount = nCount + aIt->mnCount;
1245 return nCount;
1248 void XclExpMultiCellBase::AppendXFId( const XclExpMultiXFId& rXFId )
1250 if( maXFIds.empty() || (maXFIds.back().mnXFId != rXFId.mnXFId) )
1251 maXFIds.push_back( rXFId );
1252 else
1253 maXFIds.back().mnCount = maXFIds.back().mnCount + rXFId.mnCount;
1256 void XclExpMultiCellBase::AppendXFId( const XclExpRoot& rRoot,
1257 const ScPatternAttr* pPattern, sal_uInt16 nScript, sal_uInt32 nForcedXFId, sal_uInt16 nCount )
1259 sal_uInt32 nXFId = (nForcedXFId == EXC_XFID_NOTFOUND) ?
1260 rRoot.GetXFBuffer().Insert( pPattern, nScript ) : nForcedXFId;
1261 AppendXFId( XclExpMultiXFId( nXFId, nCount ) );
1264 bool XclExpMultiCellBase::TryMergeXFIds( const XclExpMultiCellBase& rCell )
1266 if( GetLastXclCol() + 1 == rCell.GetXclCol() )
1268 maXFIds.insert( maXFIds.end(), rCell.maXFIds.begin(), rCell.maXFIds.end() );
1269 return true;
1271 return false;
1274 void XclExpMultiCellBase::GetXFIndexes( ScfUInt16Vec& rXFIndexes ) const
1276 OSL_ENSURE( GetLastXclCol() < rXFIndexes.size(), "XclExpMultiCellBase::GetXFIndexes - vector too small" );
1277 ScfUInt16Vec::iterator aDestIt = rXFIndexes.begin() + GetXclCol();
1278 for( XclExpMultiXFIdDeq::const_iterator aIt = maXFIds.begin(), aEnd = maXFIds.end(); aIt != aEnd; ++aIt )
1280 ::std::fill( aDestIt, aDestIt + aIt->mnCount, aIt->mnXFIndex );
1281 aDestIt += aIt->mnCount;
1285 void XclExpMultiCellBase::RemoveUnusedXFIndexes( const ScfUInt16Vec& rXFIndexes )
1287 // save last column before calling maXFIds.clear()
1288 sal_uInt16 nLastXclCol = GetLastXclCol();
1289 OSL_ENSURE( nLastXclCol < rXFIndexes.size(), "XclExpMultiCellBase::RemoveUnusedXFIndexes - XF index vector too small" );
1291 // build new XF index vector, containing passed XF indexes
1292 maXFIds.clear();
1293 XclExpMultiXFId aXFId( 0 );
1294 for( ScfUInt16Vec::const_iterator aIt = rXFIndexes.begin() + GetXclCol(), aEnd = rXFIndexes.begin() + nLastXclCol + 1; aIt != aEnd; ++aIt )
1296 // AppendXFId() tests XclExpXFIndex::mnXFId, set it too
1297 aXFId.mnXFId = aXFId.mnXFIndex = *aIt;
1298 AppendXFId( aXFId );
1301 // remove leading and trailing unused XF indexes
1302 if( !maXFIds.empty() && (maXFIds.front().mnXFIndex == EXC_XF_NOTFOUND) )
1304 SetXclCol( GetXclCol() + maXFIds.front().mnCount );
1305 maXFIds.erase(maXFIds.begin(), maXFIds.begin() + 1);
1307 if( !maXFIds.empty() && (maXFIds.back().mnXFIndex == EXC_XF_NOTFOUND) )
1308 maXFIds.pop_back();
1310 // The Save() function will skip all XF indexes equal to EXC_XF_NOTFOUND.
1313 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpBlankCell )
1315 XclExpBlankCell::XclExpBlankCell( const XclAddress& rXclPos, const XclExpMultiXFId& rXFId ) :
1316 XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos )
1318 OSL_ENSURE( rXFId.mnCount > 0, "XclExpBlankCell::XclExpBlankCell - invalid count" );
1319 AppendXFId( rXFId );
1322 XclExpBlankCell::XclExpBlankCell(
1323 const XclExpRoot& rRoot, const XclAddress& rXclPos, sal_uInt16 nLastXclCol,
1324 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId ) :
1325 XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos )
1327 OSL_ENSURE( rXclPos.mnCol <= nLastXclCol, "XclExpBlankCell::XclExpBlankCell - invalid column range" );
1328 // #i46627# use default script type instead of ApiScriptType::WEAK
1329 AppendXFId( rRoot, pPattern, rRoot.GetDefApiScript(), nForcedXFId, nLastXclCol - rXclPos.mnCol + 1 );
1332 bool XclExpBlankCell::TryMerge( const XclExpCellBase& rCell )
1334 const XclExpBlankCell* pBlankCell = dynamic_cast< const XclExpBlankCell* >( &rCell );
1335 return pBlankCell && TryMergeXFIds( *pBlankCell );
1338 void XclExpBlankCell::GetBlankXFIndexes( ScfUInt16Vec& rXFIndexes ) const
1340 GetXFIndexes( rXFIndexes );
1343 void XclExpBlankCell::RemoveUnusedBlankCells( const ScfUInt16Vec& rXFIndexes )
1345 RemoveUnusedXFIndexes( rXFIndexes );
1348 void XclExpBlankCell::WriteContents( XclExpStream& /*rStrm*/, sal_uInt16 /*nRelCol*/ )
1352 void XclExpBlankCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 /* nRelCol */ )
1354 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1355 rWorksheet->singleElement( XML_c,
1356 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), rAddress ).getStr(),
1357 XML_s, lcl_GetStyleId( rStrm, nXFId ).getStr(),
1358 FSEND );
1361 IMPL_FIXEDMEMPOOL_NEWDEL( XclExpRkCell )
1363 XclExpRkCell::XclExpRkCell(
1364 const XclExpRoot& rRoot, const XclAddress& rXclPos,
1365 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, sal_Int32 nRkValue ) :
1366 XclExpMultiCellBase( EXC_ID_RK, EXC_ID_MULRK, 4, rXclPos )
1368 // #i41210# always use latin script for number cells - may look wrong for special number formats...
1369 AppendXFId( rRoot, pPattern, ApiScriptType::LATIN, nForcedXFId );
1370 maRkValues.push_back( nRkValue );
1373 bool XclExpRkCell::TryMerge( const XclExpCellBase& rCell )
1375 const XclExpRkCell* pRkCell = dynamic_cast< const XclExpRkCell* >( &rCell );
1376 if( pRkCell && TryMergeXFIds( *pRkCell ) )
1378 maRkValues.insert( maRkValues.end(), pRkCell->maRkValues.begin(), pRkCell->maRkValues.end() );
1379 return true;
1381 return false;
1384 void XclExpRkCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 nRelCol )
1386 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1387 rWorksheet->startElement( XML_c,
1388 XML_r, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), rAddress ).getStr(),
1389 XML_s, lcl_GetStyleId( rStrm, nXFId ).getStr(),
1390 XML_t, "n",
1391 // OOXTODO: XML_cm, XML_vm, XML_ph
1392 FSEND );
1393 rWorksheet->startElement( XML_v, FSEND );
1394 rWorksheet->write( XclTools::GetDoubleFromRK( maRkValues[ nRelCol ] ) );
1395 rWorksheet->endElement( XML_v );
1396 rWorksheet->endElement( XML_c );
1399 void XclExpRkCell::WriteContents( XclExpStream& rStrm, sal_uInt16 nRelCol )
1401 OSL_ENSURE( nRelCol < maRkValues.size(), "XclExpRkCell::WriteContents - overflow error" );
1402 rStrm << maRkValues[ nRelCol ];
1405 // Rows and Columns
1407 XclExpOutlineBuffer::XclExpOutlineBuffer( const XclExpRoot& rRoot, bool bRows ) :
1408 mpScOLArray( nullptr ),
1409 maLevelInfos( SC_OL_MAXDEPTH ),
1410 mnCurrLevel( 0 ),
1411 mbCurrCollapse( false )
1413 if( const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ) )
1414 mpScOLArray = &(bRows ? pOutlineTable->GetRowArray() : pOutlineTable->GetColArray());
1416 if( mpScOLArray )
1417 for( size_t nLevel = 0; nLevel < SC_OL_MAXDEPTH; ++nLevel )
1418 if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nLevel, 0 ) )
1419 maLevelInfos[ nLevel ].mnScEndPos = pEntry->GetEnd();
1422 void XclExpOutlineBuffer::UpdateColRow( SCCOLROW nScPos )
1424 if( mpScOLArray )
1426 // find open level index for passed position
1427 size_t nNewOpenScLevel = 0; // new open level (0-based Calc index)
1428 sal_uInt8 nNewLevel = 0; // new open level (1-based Excel index)
1430 if( mpScOLArray->FindTouchedLevel( nScPos, nScPos, nNewOpenScLevel ) )
1431 nNewLevel = static_cast< sal_uInt8 >( nNewOpenScLevel + 1 );
1432 // else nNewLevel keeps 0 to show that there are no groups
1434 mbCurrCollapse = false;
1435 if( nNewLevel >= mnCurrLevel )
1437 // new level(s) opened, or no level closed - update all level infos
1438 for( size_t nScLevel = 0; nScLevel <= nNewOpenScLevel; ++nScLevel )
1440 /* In each level: check if a new group is started (there may be
1441 neighbored groups without gap - therefore check ALL levels). */
1442 if( maLevelInfos[ nScLevel ].mnScEndPos < nScPos )
1444 if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nScLevel, nScPos ) )
1446 maLevelInfos[ nScLevel ].mnScEndPos = pEntry->GetEnd();
1447 maLevelInfos[ nScLevel ].mbHidden = pEntry->IsHidden();
1452 else
1454 // level(s) closed - check if any of the closed levels are collapsed
1455 // Calc uses 0-based level indexes
1456 sal_uInt16 nOldOpenScLevel = mnCurrLevel - 1;
1457 for( sal_uInt16 nScLevel = nNewOpenScLevel + 1; !mbCurrCollapse && (nScLevel <= nOldOpenScLevel); ++nScLevel )
1458 mbCurrCollapse = maLevelInfos[ nScLevel ].mbHidden;
1461 // cache new opened level
1462 mnCurrLevel = nNewLevel;
1466 XclExpGuts::XclExpGuts( const XclExpRoot& rRoot ) :
1467 XclExpRecord( EXC_ID_GUTS, 8 ),
1468 mnColLevels( 0 ),
1469 mnColWidth( 0 ),
1470 mnRowLevels( 0 ),
1471 mnRowWidth( 0 )
1473 if( const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ) )
1475 // column outline groups
1476 const ScOutlineArray& rColArray = pOutlineTable->GetColArray();
1477 mnColLevels = ulimit_cast< sal_uInt16 >( rColArray.GetDepth(), EXC_OUTLINE_MAX );
1478 if( mnColLevels )
1480 ++mnColLevels;
1481 mnColWidth = 12 * mnColLevels + 5;
1484 // row outline groups
1485 const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
1486 mnRowLevels = ulimit_cast< sal_uInt16 >( rRowArray.GetDepth(), EXC_OUTLINE_MAX );
1487 if( mnRowLevels )
1489 ++mnRowLevels;
1490 mnRowWidth = 12 * mnRowLevels + 5;
1495 void XclExpGuts::WriteBody( XclExpStream& rStrm )
1497 rStrm << mnRowWidth << mnColWidth << mnRowLevels << mnColLevels;
1500 XclExpDimensions::XclExpDimensions( const XclExpRoot& rRoot ) :
1501 mnFirstUsedXclRow( 0 ),
1502 mnFirstFreeXclRow( 0 ),
1503 mnFirstUsedXclCol( 0 ),
1504 mnFirstFreeXclCol( 0 )
1506 switch( rRoot.GetBiff() )
1508 case EXC_BIFF2: SetRecHeader( EXC_ID2_DIMENSIONS, 8 ); break;
1509 case EXC_BIFF3:
1510 case EXC_BIFF4:
1511 case EXC_BIFF5: SetRecHeader( EXC_ID3_DIMENSIONS, 10 ); break;
1512 case EXC_BIFF8: SetRecHeader( EXC_ID3_DIMENSIONS, 14 ); break;
1513 default: DBG_ERROR_BIFF();
1517 void XclExpDimensions::SetDimensions(
1518 sal_uInt16 nFirstUsedXclCol, sal_uInt32 nFirstUsedXclRow,
1519 sal_uInt16 nFirstFreeXclCol, sal_uInt32 nFirstFreeXclRow )
1521 mnFirstUsedXclRow = nFirstUsedXclRow;
1522 mnFirstFreeXclRow = nFirstFreeXclRow;
1523 mnFirstUsedXclCol = nFirstUsedXclCol;
1524 mnFirstFreeXclCol = nFirstFreeXclCol;
1527 void XclExpDimensions::SaveXml( XclExpXmlStream& rStrm )
1529 ScRange aRange;
1530 aRange.aStart.SetRow( (SCROW) mnFirstUsedXclRow );
1531 aRange.aStart.SetCol( (SCCOL) mnFirstUsedXclCol );
1533 if( mnFirstFreeXclRow != mnFirstUsedXclRow && mnFirstFreeXclCol != mnFirstUsedXclCol )
1535 aRange.aEnd.SetRow( (SCROW) (mnFirstFreeXclRow-1) );
1536 aRange.aEnd.SetCol( (SCCOL) (mnFirstFreeXclCol-1) );
1539 aRange.PutInOrder();
1540 rStrm.GetCurrentStream()->singleElement( XML_dimension,
1541 // To be compatible with MS Office 2007,
1542 // we need full address notation format
1543 // e.g. "A1:AMJ177" and not partial like: "1:177".
1544 XML_ref, XclXmlUtils::ToOString( aRange, true ).getStr(),
1545 FSEND );
1548 void XclExpDimensions::WriteBody( XclExpStream& rStrm )
1550 XclBiff eBiff = rStrm.GetRoot().GetBiff();
1551 if( eBiff == EXC_BIFF8 )
1552 rStrm << mnFirstUsedXclRow << mnFirstFreeXclRow;
1553 else
1554 rStrm << static_cast< sal_uInt16 >( mnFirstUsedXclRow ) << static_cast< sal_uInt16 >( mnFirstFreeXclRow );
1555 rStrm << mnFirstUsedXclCol << mnFirstFreeXclCol;
1556 if( eBiff >= EXC_BIFF3 )
1557 rStrm << sal_uInt16( 0 );
1560 namespace {
1562 double lclGetCorrectedColWidth( const XclExpRoot& rRoot, sal_uInt16 nXclColWidth )
1564 long nFontHt = rRoot.GetFontBuffer().GetAppFontData().mnHeight;
1565 return nXclColWidth - XclTools::GetXclDefColWidthCorrection( nFontHt );
1568 } // namespace
1570 XclExpDefcolwidth::XclExpDefcolwidth( const XclExpRoot& rRoot ) :
1571 XclExpUInt16Record( EXC_ID_DEFCOLWIDTH, EXC_DEFCOLWIDTH_DEF ),
1572 XclExpRoot( rRoot )
1576 bool XclExpDefcolwidth::IsDefWidth( sal_uInt16 nXclColWidth ) const
1578 double fNewColWidth = lclGetCorrectedColWidth( GetRoot(), nXclColWidth );
1579 // This formula is taking number of characters with GetValue()
1580 // and it is translating it into default column width. 0.5 means half character.
1581 // https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.column.aspx
1582 long defaultColumnWidth = static_cast< long >( 256.0 * ( GetValue() + 0.5 ) );
1584 // exactly matched, if difference is less than 1/16 of a character to the left or to the right
1585 return std::abs( defaultColumnWidth - fNewColWidth ) < 16;
1588 void XclExpDefcolwidth::SetDefWidth( sal_uInt16 nXclColWidth )
1590 double fNewColWidth = lclGetCorrectedColWidth( GetRoot(), nXclColWidth );
1591 // This function is taking width and translate it into number of characters
1592 // Next this number of characters are stored. 0.5 means half character.
1593 SetValue( limit_cast< sal_uInt16 >( fNewColWidth / 256.0 - 0.5 ) );
1596 XclExpColinfo::XclExpColinfo( const XclExpRoot& rRoot,
1597 SCCOL nScCol, SCROW nLastScRow, XclExpColOutlineBuffer& rOutlineBfr ) :
1598 XclExpRecord( EXC_ID_COLINFO, 12 ),
1599 XclExpRoot( rRoot ),
1600 mbCustomWidth( false ),
1601 mnWidth( 0 ),
1602 mnScWidth( 0 ),
1603 mnFlags( 0 ),
1604 mnOutlineLevel( 0 ),
1605 mnFirstXclCol( static_cast< sal_uInt16 >( nScCol ) ),
1606 mnLastXclCol( static_cast< sal_uInt16 >( nScCol ) )
1608 ScDocument& rDoc = GetDoc();
1609 SCTAB nScTab = GetCurrScTab();
1611 // column default format
1612 maXFId.mnXFId = GetXFBuffer().Insert(
1613 rDoc.GetMostUsedPattern( nScCol, 0, nLastScRow, nScTab ), GetDefApiScript() );
1615 // column width. If column is hidden then we should return real value (not zero)
1616 sal_uInt16 nScWidth = rDoc.GetColWidth( nScCol, nScTab, false );
1617 mnWidth = XclTools::GetXclColumnWidth( nScWidth, GetCharWidth() );
1618 mnScWidth = sc::TwipsToHMM( nScWidth );
1620 // column flags
1621 ::set_flag( mnFlags, EXC_COLINFO_HIDDEN, rDoc.ColHidden(nScCol, nScTab) );
1623 XclExpDefcolwidth defColWidth = XclExpDefcolwidth( rRoot );
1624 mbCustomWidth = !defColWidth.IsDefWidth( mnWidth );
1625 set_flag(mnFlags, EXC_COLINFO_CUSTOMWIDTH, mbCustomWidth);
1627 // outline data
1628 rOutlineBfr.Update( nScCol );
1629 ::set_flag( mnFlags, EXC_COLINFO_COLLAPSED, rOutlineBfr.IsCollapsed() );
1630 ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 8, 3 );
1631 mnOutlineLevel = rOutlineBfr.GetLevel();
1634 void XclExpColinfo::ConvertXFIndexes()
1636 maXFId.ConvertXFIndex( GetRoot() );
1639 bool XclExpColinfo::IsDefault( const XclExpDefcolwidth& rDefColWidth ) const
1641 return (maXFId.mnXFIndex == EXC_XF_DEFAULTCELL) &&
1642 (mnFlags == 0) &&
1643 (mnOutlineLevel == 0) &&
1644 rDefColWidth.IsDefWidth( mnWidth );
1647 bool XclExpColinfo::TryMerge( const XclExpColinfo& rColInfo )
1649 if( (maXFId.mnXFIndex == rColInfo.maXFId.mnXFIndex) &&
1650 (mnWidth == rColInfo.mnWidth) &&
1651 (mnFlags == rColInfo.mnFlags) &&
1652 (mnOutlineLevel == rColInfo.mnOutlineLevel) &&
1653 (mnLastXclCol + 1 == rColInfo.mnFirstXclCol) )
1655 mnLastXclCol = rColInfo.mnLastXclCol;
1656 return true;
1658 return false;
1661 void XclExpColinfo::WriteBody( XclExpStream& rStrm )
1663 // if last column is equal to last possible column, Excel adds one more
1664 sal_uInt16 nLastXclCol = mnLastXclCol;
1665 if( nLastXclCol == static_cast< sal_uInt16 >( rStrm.GetRoot().GetMaxPos().Col() ) )
1666 ++nLastXclCol;
1668 rStrm << mnFirstXclCol
1669 << nLastXclCol
1670 << mnWidth
1671 << maXFId.mnXFIndex
1672 << mnFlags
1673 << sal_uInt16( 0 );
1676 void XclExpColinfo::SaveXml( XclExpXmlStream& rStrm )
1678 // if last column is equal to last possible column, Excel adds one more
1679 sal_uInt16 nLastXclCol = mnLastXclCol;
1680 if( nLastXclCol == static_cast< sal_uInt16 >( rStrm.GetRoot().GetMaxPos().Col() ) )
1681 ++nLastXclCol;
1683 const double nExcelColumnWidth = mnScWidth / static_cast< double >( sc::TwipsToHMM( GetCharWidth() ) );
1685 // tdf#101363 In MS specification the output value is set with double precision after delimiter:
1686 // =Truncate(({width in pixels} - 5)/{Maximum Digit Width} * 100 + 0.5)/100
1687 // Explanation of magic numbers:
1688 // 5 number - are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.
1689 // It is unknown if it should be applied during LibreOffice export
1690 // 100 number - used to limit precision to 0.01 with formula =Truncate( {value}*100+0.5 ) / 100
1691 // 0.5 number (0.005 to output value) - used to increase value before truncating,
1692 // to avoid situation when 2.997 will be truncated to 2.99 and not to 3.00
1693 const double nTruncatedExcelColumnWidth = std::trunc( nExcelColumnWidth * 100.0 + 0.5 ) / 100.0;
1694 rStrm.GetCurrentStream()->singleElement( XML_col,
1695 // OOXTODO: XML_bestFit,
1696 XML_collapsed, XclXmlUtils::ToPsz( ::get_flag( mnFlags, EXC_COLINFO_COLLAPSED ) ),
1697 XML_customWidth, XclXmlUtils::ToPsz( mbCustomWidth ),
1698 XML_hidden, XclXmlUtils::ToPsz( ::get_flag( mnFlags, EXC_COLINFO_HIDDEN ) ),
1699 XML_outlineLevel, OString::number( mnOutlineLevel ).getStr(),
1700 XML_max, OString::number( nLastXclCol + 1 ).getStr(),
1701 XML_min, OString::number( mnFirstXclCol + 1 ).getStr(),
1702 // OOXTODO: XML_phonetic,
1703 XML_style, lcl_GetStyleId( rStrm, maXFId.mnXFIndex ).getStr(),
1704 XML_width, OString::number( nTruncatedExcelColumnWidth ).getStr(),
1705 FSEND );
1708 XclExpColinfoBuffer::XclExpColinfoBuffer( const XclExpRoot& rRoot ) :
1709 XclExpRoot( rRoot ),
1710 maDefcolwidth( rRoot ),
1711 maOutlineBfr( rRoot ),
1712 maHighestOutlineLevel( 0 )
1716 void XclExpColinfoBuffer::Initialize( SCROW nLastScRow )
1719 for( sal_uInt16 nScCol = 0, nLastScCol = GetMaxPos().Col(); nScCol <= nLastScCol; ++nScCol )
1721 maColInfos.AppendNewRecord( new XclExpColinfo( GetRoot(), nScCol, nLastScRow, maOutlineBfr ) );
1722 if( maOutlineBfr.GetLevel() > maHighestOutlineLevel )
1724 maHighestOutlineLevel = maOutlineBfr.GetLevel();
1729 void XclExpColinfoBuffer::Finalize( ScfUInt16Vec& rXFIndexes )
1731 rXFIndexes.clear();
1732 rXFIndexes.reserve( maColInfos.GetSize() );
1734 size_t nPos, nSize;
1736 // do not cache the record list size, it may change in the loop
1737 for( nPos = 0; nPos < maColInfos.GetSize(); ++nPos )
1739 XclExpColinfoRef xRec = maColInfos.GetRecord( nPos );
1740 xRec->ConvertXFIndexes();
1742 // try to merge with previous record
1743 if( nPos > 0 )
1745 XclExpColinfoRef xPrevRec = maColInfos.GetRecord( nPos - 1 );
1746 if( xPrevRec->TryMerge( *xRec ) )
1747 // adjust nPos to get the next COLINFO record at the same position
1748 maColInfos.RemoveRecord( nPos-- );
1752 // put XF indexes into passed vector, collect use count of all different widths
1753 typedef ::std::map< sal_uInt16, sal_uInt16 > XclExpWidthMap;
1754 XclExpWidthMap aWidthMap;
1755 sal_uInt16 nMaxColCount = 0;
1756 sal_uInt16 nMaxUsedWidth = 0;
1757 for( nPos = 0, nSize = maColInfos.GetSize(); nPos < nSize; ++nPos )
1759 XclExpColinfoRef xRec = maColInfos.GetRecord( nPos );
1760 sal_uInt16 nColCount = xRec->GetColCount();
1762 // add XF index to passed vector
1763 rXFIndexes.resize( rXFIndexes.size() + nColCount, xRec->GetXFIndex() );
1765 // collect use count of column width
1766 sal_uInt16 nWidth = xRec->GetColWidth();
1767 sal_uInt16& rnMapCount = aWidthMap[ nWidth ];
1768 rnMapCount = rnMapCount + nColCount;
1769 if( rnMapCount > nMaxColCount )
1771 nMaxColCount = rnMapCount;
1772 nMaxUsedWidth = nWidth;
1775 maDefcolwidth.SetDefWidth( nMaxUsedWidth );
1777 // remove all default COLINFO records
1778 nPos = 0;
1779 while( nPos < maColInfos.GetSize() )
1781 XclExpColinfoRef xRec = maColInfos.GetRecord( nPos );
1782 if( xRec->IsDefault( maDefcolwidth ) )
1783 maColInfos.RemoveRecord( nPos );
1784 else
1785 ++nPos;
1789 void XclExpColinfoBuffer::Save( XclExpStream& rStrm )
1791 // DEFCOLWIDTH
1792 maDefcolwidth.Save( rStrm );
1793 // COLINFO records
1794 maColInfos.Save( rStrm );
1797 void XclExpColinfoBuffer::SaveXml( XclExpXmlStream& rStrm )
1799 if( maColInfos.IsEmpty() )
1800 return;
1802 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1803 rWorksheet->startElement( XML_cols,
1804 FSEND );
1805 maColInfos.SaveXml( rStrm );
1806 rWorksheet->endElement( XML_cols );
1809 XclExpDefaultRowData::XclExpDefaultRowData() :
1810 mnFlags( EXC_DEFROW_DEFAULTFLAGS ),
1811 mnHeight( EXC_DEFROW_DEFAULTHEIGHT )
1815 XclExpDefaultRowData::XclExpDefaultRowData( const XclExpRow& rRow ) :
1816 mnFlags( EXC_DEFROW_DEFAULTFLAGS ),
1817 mnHeight( rRow.GetHeight() )
1819 ::set_flag( mnFlags, EXC_DEFROW_HIDDEN, rRow.IsHidden() );
1820 ::set_flag( mnFlags, EXC_DEFROW_UNSYNCED, rRow.IsUnsynced() );
1823 bool operator<( const XclExpDefaultRowData& rLeft, const XclExpDefaultRowData& rRight )
1825 return (rLeft.mnHeight < rRight.mnHeight) ||
1826 ((rLeft.mnHeight == rRight.mnHeight) && (rLeft.mnFlags < rRight.mnFlags));
1829 XclExpDefrowheight::XclExpDefrowheight() :
1830 XclExpRecord( EXC_ID3_DEFROWHEIGHT, 4 )
1834 void XclExpDefrowheight::SetDefaultData( const XclExpDefaultRowData& rDefData )
1836 maDefData = rDefData;
1839 void XclExpDefrowheight::WriteBody( XclExpStream& rStrm )
1841 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
1842 rStrm << maDefData.mnFlags << maDefData.mnHeight;
1845 XclExpRow::XclExpRow( const XclExpRoot& rRoot, sal_uInt32 nXclRow,
1846 XclExpRowOutlineBuffer& rOutlineBfr, bool bAlwaysEmpty ) :
1847 XclExpRecord( EXC_ID3_ROW, 16 ),
1848 XclExpRoot( rRoot ),
1849 mnXclRow( nXclRow ),
1850 mnHeight( 0 ),
1851 mnFlags( EXC_ROW_DEFAULTFLAGS ),
1852 mnXFIndex( EXC_XF_DEFAULTCELL ),
1853 mnOutlineLevel( 0 ),
1854 mnXclRowRpt( 1 ),
1855 mnCurrentRow( nXclRow ),
1856 mbAlwaysEmpty( bAlwaysEmpty ),
1857 mbEnabled( true )
1859 SCTAB nScTab = GetCurrScTab();
1860 SCROW nScRow = static_cast< SCROW >( mnXclRow );
1862 // *** Row flags *** ------------------------------------------------------
1864 sal_uInt8 nRowFlags = GetDoc().GetRowFlags( nScRow, nScTab );
1865 bool bUserHeight = ::get_flag< sal_uInt8 >( nRowFlags, CR_MANUALSIZE );
1866 bool bHidden = GetDoc().RowHidden(nScRow, nScTab);
1867 ::set_flag( mnFlags, EXC_ROW_UNSYNCED, bUserHeight );
1868 ::set_flag( mnFlags, EXC_ROW_HIDDEN, bHidden );
1870 // *** Row height *** -----------------------------------------------------
1872 // Always get the actual row height even if the manual size flag is not set,
1873 // to correctly export the heights of rows with wrapped texts.
1875 mnHeight = GetDoc().GetRowHeight(nScRow, nScTab, false);
1877 // *** Outline data *** ---------------------------------------------------
1879 rOutlineBfr.Update( nScRow );
1880 ::set_flag( mnFlags, EXC_ROW_COLLAPSED, rOutlineBfr.IsCollapsed() );
1881 ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 0, 3 );
1882 mnOutlineLevel = rOutlineBfr.GetLevel();
1884 // *** Progress bar *** ---------------------------------------------------
1886 XclExpProgressBar& rProgress = GetProgressBar();
1887 rProgress.IncRowRecordCount();
1888 rProgress.Progress();
1891 void XclExpRow::AppendCell( XclExpCellRef xCell, bool bIsMergedBase )
1893 OSL_ENSURE( !mbAlwaysEmpty, "XclExpRow::AppendCell - row is marked to be always empty" );
1894 // try to merge with last existing cell
1895 InsertCell( xCell, maCellList.GetSize(), bIsMergedBase );
1898 void XclExpRow::Finalize( const ScfUInt16Vec& rColXFIndexes, bool bProgress )
1900 size_t nPos, nSize;
1902 // *** Convert XF identifiers *** -----------------------------------------
1904 // additionally collect the blank XF indexes
1905 size_t nColCount = GetMaxPos().Col() + 1;
1906 OSL_ENSURE( rColXFIndexes.size() == nColCount, "XclExpRow::Finalize - wrong column XF index count" );
1908 ScfUInt16Vec aXFIndexes( nColCount, EXC_XF_NOTFOUND );
1909 for( nPos = 0, nSize = maCellList.GetSize(); nPos < nSize; ++nPos )
1911 XclExpCellRef xCell = maCellList.GetRecord( nPos );
1912 xCell->ConvertXFIndexes( GetRoot() );
1913 xCell->GetBlankXFIndexes( aXFIndexes );
1916 // *** Fill gaps with BLANK/MULBLANK cell records *** ---------------------
1918 /* This is needed because nonexistent cells in Calc are not formatted at all,
1919 but in Excel they would have the column default format. Blank cells that
1920 are equal to the respective column default are removed later in this function. */
1921 if( !mbAlwaysEmpty )
1923 // XF identifier representing default cell XF
1924 XclExpMultiXFId aXFId( XclExpXFBuffer::GetDefCellXFId() );
1925 aXFId.ConvertXFIndex( GetRoot() );
1927 nPos = 0;
1928 while( nPos <= maCellList.GetSize() ) // don't cache list size, may change in the loop
1930 // get column index that follows previous cell
1931 sal_uInt16 nFirstFreeXclCol = (nPos > 0) ? (maCellList.GetRecord( nPos - 1 )->GetLastXclCol() + 1) : 0;
1932 // get own column index
1933 sal_uInt16 nNextUsedXclCol = (nPos < maCellList.GetSize()) ? maCellList.GetRecord( nPos )->GetXclCol() : (GetMaxPos().Col() + 1);
1935 // is there a gap?
1936 if( nFirstFreeXclCol < nNextUsedXclCol )
1938 aXFId.mnCount = nNextUsedXclCol - nFirstFreeXclCol;
1939 XclExpCellRef xNewCell( new XclExpBlankCell( XclAddress( nFirstFreeXclCol, mnXclRow ), aXFId ) );
1940 // insert the cell, InsertCell() may merge it with existing BLANK records
1941 InsertCell( xNewCell, nPos, false );
1942 // insert default XF indexes into aXFIndexes
1943 ::std::fill( aXFIndexes.begin() + nFirstFreeXclCol,
1944 aXFIndexes.begin() + nNextUsedXclCol, aXFId.mnXFIndex );
1945 // don't step forward with nPos, InsertCell() may remove records
1947 else
1948 ++nPos;
1952 // *** Find default row format *** ----------------------------------------
1954 ScfUInt16Vec::iterator aCellBeg = aXFIndexes.begin(), aCellEnd = aXFIndexes.end(), aCellIt;
1955 ScfUInt16Vec::const_iterator aColBeg = rColXFIndexes.begin(), aColIt;
1957 // find most used XF index in the row
1958 typedef ::std::map< sal_uInt16, size_t > XclExpXFIndexMap;
1959 XclExpXFIndexMap aIndexMap;
1960 sal_uInt16 nRowXFIndex = EXC_XF_DEFAULTCELL;
1961 size_t nMaxXFCount = 0;
1962 for( aCellIt = aCellBeg; aCellIt != aCellEnd; ++aCellIt )
1964 if( *aCellIt != EXC_XF_NOTFOUND )
1966 size_t& rnCount = aIndexMap[ *aCellIt ];
1967 ++rnCount;
1968 if( rnCount > nMaxXFCount )
1970 nRowXFIndex = *aCellIt;
1971 nMaxXFCount = rnCount;
1976 // decide whether to use the row default XF index or column default XF indexes
1977 bool bUseColDefXFs = nRowXFIndex == EXC_XF_DEFAULTCELL;
1978 if( !bUseColDefXFs )
1980 // count needed XF indexes for blank cells with and without row default XF index
1981 size_t nXFCountWithRowDefXF = 0;
1982 size_t nXFCountWithoutRowDefXF = 0;
1983 for( aCellIt = aCellBeg, aColIt = aColBeg; aCellIt != aCellEnd; ++aCellIt, ++aColIt )
1985 sal_uInt16 nXFIndex = *aCellIt;
1986 if( nXFIndex != EXC_XF_NOTFOUND )
1988 if( nXFIndex != nRowXFIndex )
1989 ++nXFCountWithRowDefXF; // with row default XF index
1990 if( nXFIndex != *aColIt )
1991 ++nXFCountWithoutRowDefXF; // without row default XF index
1995 // use column XF indexes if this would cause less or equal number of BLANK records
1996 bUseColDefXFs = nXFCountWithoutRowDefXF <= nXFCountWithRowDefXF;
1999 // *** Remove unused BLANK cell records *** -------------------------------
2001 if( bUseColDefXFs )
2003 // use column default XF indexes
2004 // #i194#: remove cell XF indexes equal to column default XF indexes
2005 for( aCellIt = aCellBeg, aColIt = aColBeg; aCellIt != aCellEnd; ++aCellIt, ++aColIt )
2006 if( *aCellIt == *aColIt )
2007 *aCellIt = EXC_XF_NOTFOUND;
2009 else
2011 // use row default XF index
2012 mnXFIndex = nRowXFIndex;
2013 ::set_flag( mnFlags, EXC_ROW_USEDEFXF );
2014 // #98133#, #i194#, #i27407#: remove cell XF indexes equal to row default XF index
2015 for( aCellIt = aCellBeg; aCellIt != aCellEnd; ++aCellIt )
2016 if( *aCellIt == nRowXFIndex )
2017 *aCellIt = EXC_XF_NOTFOUND;
2020 // remove unused parts of BLANK/MULBLANK cell records
2021 nPos = 0;
2022 while( nPos < maCellList.GetSize() ) // do not cache list size, may change in the loop
2024 XclExpCellRef xCell = maCellList.GetRecord( nPos );
2025 xCell->RemoveUnusedBlankCells( aXFIndexes );
2026 if( xCell->IsEmpty() )
2027 maCellList.RemoveRecord( nPos );
2028 else
2029 ++nPos;
2032 // progress bar includes disabled rows; only update it in the lead thread.
2033 if (bProgress)
2034 GetProgressBar().Progress();
2036 sal_uInt16 XclExpRow::GetFirstUsedXclCol() const
2038 return maCellList.IsEmpty() ? 0 : maCellList.GetFirstRecord()->GetXclCol();
2041 sal_uInt16 XclExpRow::GetFirstFreeXclCol() const
2043 return maCellList.IsEmpty() ? 0 : (maCellList.GetLastRecord()->GetLastXclCol() + 1);
2046 bool XclExpRow::IsDefaultable() const
2048 const sal_uInt16 nFlagsAlwaysMarkedAsDefault = EXC_ROW_DEFAULTFLAGS | EXC_ROW_HIDDEN | EXC_ROW_UNSYNCED;
2049 return !::get_flag( mnFlags, static_cast< sal_uInt16 >( ~nFlagsAlwaysMarkedAsDefault ) ) &&
2050 IsEmpty();
2053 void XclExpRow::DisableIfDefault( const XclExpDefaultRowData& rDefRowData )
2055 mbEnabled = !IsDefaultable() ||
2056 (mnHeight != rDefRowData.mnHeight) ||
2057 (IsHidden() != rDefRowData.IsHidden()) ||
2058 (IsUnsynced() != rDefRowData.IsUnsynced());
2061 void XclExpRow::WriteCellList( XclExpStream& rStrm )
2063 OSL_ENSURE( mbEnabled || maCellList.IsEmpty(), "XclExpRow::WriteCellList - cells in disabled row" );
2064 maCellList.Save( rStrm );
2067 void XclExpRow::Save( XclExpStream& rStrm )
2069 if( mbEnabled )
2071 mnCurrentRow = mnXclRow;
2072 for ( sal_uInt32 i = 0; i < mnXclRowRpt; ++i, ++mnCurrentRow )
2073 XclExpRecord::Save( rStrm );
2077 void XclExpRow::InsertCell( XclExpCellRef xCell, size_t nPos, bool bIsMergedBase )
2079 OSL_ENSURE( xCell, "XclExpRow::InsertCell - missing cell" );
2081 /* If we have a multi-line text in a merged cell, and the resulting
2082 row height has not been confirmed, we need to force the EXC_ROW_UNSYNCED
2083 flag to be true to ensure Excel works correctly. */
2084 if( bIsMergedBase && xCell->IsMultiLineText() )
2085 ::set_flag( mnFlags, EXC_ROW_UNSYNCED );
2087 // try to merge with previous cell, insert the new cell if not successful
2088 XclExpCellRef xPrevCell = maCellList.GetRecord( nPos - 1 );
2089 if( xPrevCell && xPrevCell->TryMerge( *xCell ) )
2090 xCell = xPrevCell;
2091 else
2092 maCellList.InsertRecord( xCell, nPos++ );
2093 // nPos points now to following cell
2095 // try to merge with following cell, remove it if successful
2096 XclExpCellRef xNextCell = maCellList.GetRecord( nPos );
2097 if( xNextCell && xCell->TryMerge( *xNextCell ) )
2098 maCellList.RemoveRecord( nPos );
2101 void XclExpRow::WriteBody( XclExpStream& rStrm )
2103 rStrm << static_cast< sal_uInt16 >(mnCurrentRow)
2104 << GetFirstUsedXclCol()
2105 << GetFirstFreeXclCol()
2106 << mnHeight
2107 << sal_uInt32( 0 )
2108 << mnFlags
2109 << mnXFIndex;
2112 void XclExpRow::SaveXml( XclExpXmlStream& rStrm )
2114 if( !mbEnabled )
2115 return;
2116 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2117 bool haveFormat = ::get_flag( mnFlags, EXC_ROW_USEDEFXF );
2118 mnCurrentRow = mnXclRow + 1;
2119 for ( sal_uInt32 i=0; i<mnXclRowRpt; ++i )
2121 rWorksheet->startElement( XML_row,
2122 XML_r, OString::number( (mnCurrentRow++) ).getStr(),
2123 // OOXTODO: XML_spans, optional
2124 XML_s, haveFormat ? lcl_GetStyleId( rStrm, mnXFIndex ).getStr() : nullptr,
2125 XML_customFormat, XclXmlUtils::ToPsz( haveFormat ),
2126 XML_ht, OString::number( (double) mnHeight / 20.0 ).getStr(),
2127 XML_hidden, XclXmlUtils::ToPsz( ::get_flag( mnFlags, EXC_ROW_HIDDEN ) ),
2128 XML_customHeight, XclXmlUtils::ToPsz( ::get_flag( mnFlags, EXC_ROW_UNSYNCED ) ),
2129 XML_outlineLevel, OString::number( mnOutlineLevel ).getStr(),
2130 XML_collapsed, XclXmlUtils::ToPsz( ::get_flag( mnFlags, EXC_ROW_COLLAPSED ) ),
2131 // OOXTODO: XML_thickTop, bool
2132 // OOXTODO: XML_thickBot, bool
2133 // OOXTODO: XML_ph, bool
2134 FSEND );
2135 // OOXTODO: XML_extLst
2136 maCellList.SaveXml( rStrm );
2137 rWorksheet->endElement( XML_row );
2141 XclExpRowBuffer::XclExpRowBuffer( const XclExpRoot& rRoot ) :
2142 XclExpRoot( rRoot ),
2143 maOutlineBfr( rRoot ),
2144 maDimensions( rRoot ),
2145 maHighestOutlineLevel( 0 )
2149 void XclExpRowBuffer::AppendCell( XclExpCellRef xCell, bool bIsMergedBase )
2151 OSL_ENSURE( xCell, "XclExpRowBuffer::AppendCell - missing cell" );
2152 GetOrCreateRow( xCell->GetXclRow(), false ).AppendCell( xCell, bIsMergedBase );
2155 void XclExpRowBuffer::CreateRows( SCROW nFirstFreeScRow )
2157 if( nFirstFreeScRow > 0 )
2158 GetOrCreateRow( ::std::max ( nFirstFreeScRow - 1, GetMaxPos().Row() ), true );
2161 class RowFinalizeTask : public comphelper::ThreadTask
2163 bool mbProgress;
2164 const ScfUInt16Vec& mrColXFIndexes;
2165 std::vector< XclExpRow * > maRows;
2166 public:
2167 RowFinalizeTask( const ScfUInt16Vec& rColXFIndexes,
2168 bool bProgress ) :
2169 mbProgress( bProgress ),
2170 mrColXFIndexes( rColXFIndexes ) {}
2171 virtual ~RowFinalizeTask() {}
2172 void push_back( XclExpRow *pRow ) { maRows.push_back( pRow ); }
2173 virtual void doWork() override
2175 for (XclExpRow* p : maRows)
2176 p->Finalize( mrColXFIndexes, mbProgress );
2180 void XclExpRowBuffer::Finalize( XclExpDefaultRowData& rDefRowData, const ScfUInt16Vec& rColXFIndexes )
2182 // *** Finalize all rows *** ----------------------------------------------
2184 GetProgressBar().ActivateFinalRowsSegment();
2186 #if 1
2187 // This is staggeringly slow, and each element operates only
2188 // on its own data.
2189 const size_t nRows = maRowMap.size();
2190 const size_t nThreads = nRows < 128 ? 1 : comphelper::ThreadPool::getPreferredConcurrency();
2191 #else
2192 const size_t nThreads = 1; // globally disable multi-threading for now.
2193 #endif
2194 if (nThreads == 1)
2196 RowMap::iterator itr, itrBeg = maRowMap.begin(), itrEnd = maRowMap.end();
2197 for (itr = itrBeg; itr != itrEnd; ++itr)
2198 itr->second->Finalize( rColXFIndexes, true );
2200 else
2202 comphelper::ThreadPool &rPool = comphelper::ThreadPool::getSharedOptimalPool();
2203 std::vector<RowFinalizeTask*> aTasks(nThreads, nullptr);
2204 for ( size_t i = 0; i < nThreads; i++ )
2205 aTasks[ i ] = new RowFinalizeTask( rColXFIndexes, i == 0 );
2207 RowMap::iterator itr, itrBeg = maRowMap.begin(), itrEnd = maRowMap.end();
2208 size_t nIdx = 0;
2209 for ( itr = itrBeg; itr != itrEnd; ++itr, ++nIdx )
2210 aTasks[ nIdx % nThreads ]->push_back( itr->second.get() );
2212 for ( size_t i = 1; i < nThreads; i++ )
2213 rPool.pushTask( aTasks[ i ] );
2215 // Progress bar updates must be synchronous to avoid deadlock
2216 aTasks[0]->doWork();
2218 rPool.waitUntilEmpty();
2221 // *** Default row format *** ---------------------------------------------
2223 typedef ::std::map< XclExpDefaultRowData, size_t > XclExpDefRowDataMap;
2224 XclExpDefRowDataMap aDefRowMap;
2226 XclExpDefaultRowData aMaxDefData;
2227 size_t nMaxDefCount = 0;
2228 // only look for default format in existing rows, if there are more than unused
2229 XclExpRow* pPrev = nullptr;
2230 typedef std::vector< XclExpRow* > XclRepeatedRows;
2231 XclRepeatedRows aRepeated;
2232 RowMap::iterator itr, itrBeg = maRowMap.begin(), itrEnd = maRowMap.end();
2233 for (itr = itrBeg; itr != itrEnd; ++itr)
2235 const RowRef& rRow = itr->second;
2236 if ( rRow->IsDefaultable() )
2238 XclExpDefaultRowData aDefData( *rRow );
2239 size_t& rnDefCount = aDefRowMap[ aDefData ];
2240 ++rnDefCount;
2241 if( rnDefCount > nMaxDefCount )
2243 nMaxDefCount = rnDefCount;
2244 aMaxDefData = aDefData;
2247 if ( pPrev )
2249 if ( pPrev->IsDefaultable() )
2251 // if the previous row we processed is not
2252 // defaultable then afaict the rows in between are
2253 // not used ( and not repeatable )
2254 sal_uInt32 nRpt = rRow->GetXclRow() - pPrev->GetXclRow();
2255 if ( nRpt > 1 )
2256 aRepeated.push_back( pPrev );
2257 pPrev->SetXclRowRpt( nRpt );
2258 XclExpDefaultRowData aDefData( *pPrev );
2259 size_t& rnDefCount = aDefRowMap[ aDefData ];
2260 rnDefCount += ( pPrev->GetXclRowRpt() - 1 );
2261 if( rnDefCount > nMaxDefCount )
2263 nMaxDefCount = rnDefCount;
2264 aMaxDefData = aDefData;
2268 pPrev = rRow.get();
2270 // return the default row format to caller
2271 rDefRowData = aMaxDefData;
2273 // now disable repeating extra (empty) rows that are equal to
2274 // default row height
2275 for ( XclRepeatedRows::iterator it = aRepeated.begin(), it_end = aRepeated.end(); it != it_end; ++it)
2277 if ( (*it)->GetXclRowRpt() > 1 && (*it)->GetHeight() == rDefRowData.mnHeight )
2278 (*it)->SetXclRowRpt( 1 );
2281 // *** Disable unused ROW records, find used area *** ---------------------
2283 sal_uInt16 nFirstUsedXclCol = SAL_MAX_UINT16;
2284 sal_uInt16 nFirstFreeXclCol = 0;
2285 sal_uInt32 nFirstUsedXclRow = SAL_MAX_UINT32;
2286 sal_uInt32 nFirstFreeXclRow = 0;
2288 for (itr = itrBeg; itr != itrEnd; ++itr)
2290 const RowRef& rRow = itr->second;
2291 // disable unused rows
2292 rRow->DisableIfDefault( aMaxDefData );
2294 // find used column range
2295 if( !rRow->IsEmpty() ) // empty rows return (0...0) as used range
2297 nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, rRow->GetFirstUsedXclCol() );
2298 nFirstFreeXclCol = ::std::max( nFirstFreeXclCol, rRow->GetFirstFreeXclCol() );
2301 // find used row range
2302 if( rRow->IsEnabled() )
2304 sal_uInt16 nXclRow = rRow->GetXclRow();
2305 nFirstUsedXclRow = ::std::min< sal_uInt32 >( nFirstUsedXclRow, nXclRow );
2306 nFirstFreeXclRow = ::std::max< sal_uInt32 >( nFirstFreeXclRow, nXclRow + 1 );
2310 // adjust start position, if there are no or only empty/disabled ROW records
2311 nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, nFirstFreeXclCol );
2312 nFirstUsedXclRow = ::std::min( nFirstUsedXclRow, nFirstFreeXclRow );
2314 // initialize the DIMENSIONS record
2315 maDimensions.SetDimensions(
2316 nFirstUsedXclCol, nFirstUsedXclRow, nFirstFreeXclCol, nFirstFreeXclRow );
2319 void XclExpRowBuffer::Save( XclExpStream& rStrm )
2321 // DIMENSIONS record
2322 maDimensions.Save( rStrm );
2324 // save in blocks of 32 rows, each block contains first all ROWs, then all cells
2325 size_t nSize = maRowMap.size();
2326 RowMap::iterator itr, itrBeg = maRowMap.begin(), itrEnd = maRowMap.end();
2327 RowMap::iterator itrBlkStart = maRowMap.begin(), itrBlkEnd = maRowMap.begin();
2328 sal_uInt16 nStartXclRow = (nSize == 0) ? 0 : itrBeg->second->GetXclRow();
2330 for (itr = itrBeg; itr != itrEnd; ++itr)
2332 // find end of row block
2333 while( (itrBlkEnd != itrEnd) && (itrBlkEnd->second->GetXclRow() - nStartXclRow < EXC_ROW_ROWBLOCKSIZE) )
2334 ++itrBlkEnd;
2336 // write the ROW records
2337 RowMap::iterator itRow;
2338 for( itRow = itrBlkStart; itRow != itrBlkEnd; ++itRow )
2339 itRow->second->Save( rStrm );
2341 // write the cell records
2342 for( itRow = itrBlkStart; itRow != itrBlkEnd; ++itRow )
2343 itRow->second->WriteCellList( rStrm );
2345 itrBlkStart = (itrBlkEnd == itrEnd) ? itrBlkEnd : itrBlkEnd++;
2346 nStartXclRow += EXC_ROW_ROWBLOCKSIZE;
2350 void XclExpRowBuffer::SaveXml( XclExpXmlStream& rStrm )
2352 sal_Int32 nNonEmpty = 0;
2353 RowMap::iterator itr = maRowMap.begin(), itrEnd = maRowMap.end();
2354 for (; itr != itrEnd; ++itr)
2355 if (itr->second->IsEnabled())
2356 ++nNonEmpty;
2358 if (nNonEmpty == 0)
2360 rStrm.GetCurrentStream()->singleElement( XML_sheetData, FSEND );
2361 return;
2364 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2365 rWorksheet->startElement( XML_sheetData, FSEND );
2366 for (itr = maRowMap.begin(); itr != itrEnd; ++itr)
2367 itr->second->SaveXml(rStrm);
2368 rWorksheet->endElement( XML_sheetData );
2371 XclExpRow& XclExpRowBuffer::GetOrCreateRow( sal_uInt32 nXclRow, bool bRowAlwaysEmpty )
2373 RowMap::iterator itr = maRowMap.begin();
2374 ScDocument& rDoc = GetRoot().GetDoc();
2375 SCTAB nScTab = GetRoot().GetCurrScTab();
2376 for ( size_t nFrom = maRowMap.size(); nFrom <= nXclRow; ++nFrom )
2378 itr = maRowMap.find(nFrom);
2379 if ( itr == maRowMap.end() )
2381 // only create RowMap entries if it is first row in spreadsheet,
2382 // if it is the desired row, for rows that height differ from previous,
2383 // if row is collapsed, or has outline level (tdf#100347).
2384 if ( !nFrom || ( nFrom == nXclRow ) ||
2385 ( rDoc.GetRowHeight(nFrom, nScTab, false) != rDoc.GetRowHeight(nFrom - 1, nScTab, false) ) ||
2386 ( maOutlineBfr.IsCollapsed() ) ||
2387 ( maOutlineBfr.GetLevel() != 0 ) )
2389 if( maOutlineBfr.GetLevel() > maHighestOutlineLevel )
2391 maHighestOutlineLevel = maOutlineBfr.GetLevel();
2393 RowRef p(new XclExpRow(GetRoot(), nFrom, maOutlineBfr, bRowAlwaysEmpty));
2394 maRowMap.insert(RowMap::value_type(nFrom, p));
2398 itr = maRowMap.find(nXclRow);
2399 return *itr->second;
2403 // Cell Table
2405 XclExpCellTable::XclExpCellTable( const XclExpRoot& rRoot ) :
2406 XclExpRoot( rRoot ),
2407 maColInfoBfr( rRoot ),
2408 maRowBfr( rRoot ),
2409 maArrayBfr( rRoot ),
2410 maShrfmlaBfr( rRoot ),
2411 maTableopBfr( rRoot ),
2412 mxDefrowheight( new XclExpDefrowheight ),
2413 mxGuts( new XclExpGuts( rRoot ) ),
2414 mxNoteList( new XclExpNoteList ),
2415 mxMergedcells( new XclExpMergedcells( rRoot ) ),
2416 mxHyperlinkList( new XclExpHyperlinkList ),
2417 mxDval( new XclExpDval( rRoot ) ),
2418 mxExtLst( new XclExtLst( rRoot ) )
2420 ScDocument& rDoc = GetDoc();
2421 SCTAB nScTab = GetCurrScTab();
2422 SvNumberFormatter& rFormatter = GetFormatter();
2424 // maximum sheet limits
2425 SCCOL nMaxScCol = GetMaxPos().Col();
2426 SCROW nMaxScRow = GetMaxPos().Row();
2428 // find used area (non-empty cells)
2429 SCCOL nLastUsedScCol;
2430 SCROW nLastUsedScRow;
2431 rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow );
2433 if(nLastUsedScCol > nMaxScCol)
2434 nLastUsedScCol = nMaxScCol;
2436 if(nLastUsedScRow > nMaxScRow)
2437 nLastUsedScRow = nMaxScRow;
2439 ScRange aUsedRange( 0, 0, nScTab, nLastUsedScCol, nLastUsedScRow, nScTab );
2440 GetAddressConverter().ValidateRange( aUsedRange, true );
2441 nLastUsedScRow = aUsedRange.aEnd.Row();
2443 // first row without any set attributes (height/hidden/...)
2444 SCROW nFirstUnflaggedScRow = rDoc.GetLastFlaggedRow( nScTab ) + 1;
2446 // find range of outlines
2447 SCROW nFirstUngroupedScRow = 0;
2448 if( const ScOutlineTable* pOutlineTable = rDoc.GetOutlineTable( nScTab ) )
2450 SCCOLROW nScStartPos, nScEndPos;
2451 const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
2452 rRowArray.GetRange( nScStartPos, nScEndPos );
2453 // +1 because open/close button is in next row in Excel, +1 for "end->first unused"
2454 nFirstUngroupedScRow = static_cast< SCROW >( nScEndPos + 2 );
2457 // column settings
2458 /* #i30411# Files saved with SO7/OOo1.x with nonstandard default column
2459 formatting cause big Excel files, because all rows from row 1 to row
2460 32000 are exported. Now, if the used area goes exactly to row 32000,
2461 use this row as default and ignore all rows >32000.
2462 #i59220# Tolerance of +-128 rows for inserted/removed rows. */
2463 if( (31871 <= nLastUsedScRow) && (nLastUsedScRow <= 32127) && (nFirstUnflaggedScRow < nLastUsedScRow) && (nFirstUngroupedScRow <= nLastUsedScRow) )
2464 nMaxScRow = nLastUsedScRow;
2465 maColInfoBfr.Initialize( nMaxScRow );
2467 // range for cell iterator
2468 SCCOL nLastIterScCol = nMaxScCol;
2469 SCROW nLastIterScRow = ulimit_cast< SCROW >( nLastUsedScRow, nMaxScRow );
2470 ScUsedAreaIterator aIt( &rDoc, nScTab, 0, 0, nLastIterScCol, nLastIterScRow );
2472 // activate the correct segment and sub segment at the progress bar
2473 GetProgressBar().ActivateCreateRowsSegment();
2475 for( bool bIt = aIt.GetNext(); bIt; bIt = aIt.GetNext() )
2477 SCCOL nScCol = aIt.GetStartCol();
2478 SCROW nScRow = aIt.GetRow();
2479 SCCOL nLastScCol = aIt.GetEndCol();
2480 ScAddress aScPos( nScCol, nScRow, nScTab );
2482 XclAddress aXclPos( static_cast< sal_uInt16 >( nScCol ), static_cast< sal_uInt32 >( nScRow ) );
2483 sal_uInt16 nLastXclCol = static_cast< sal_uInt16 >( nLastScCol );
2485 const ScRefCellValue& rScCell = aIt.GetCell();
2486 XclExpCellRef xCell;
2488 const ScPatternAttr* pPattern = aIt.GetPattern();
2490 // handle overlapped merged cells before creating the cell record
2491 sal_uInt32 nMergeBaseXFId = EXC_XFID_NOTFOUND;
2492 bool bIsMergedBase = false;
2493 if( pPattern )
2495 const SfxItemSet& rItemSet = pPattern->GetItemSet();
2496 // base cell in a merged range
2497 const ScMergeAttr& rMergeItem = GETITEM( rItemSet, ScMergeAttr, ATTR_MERGE );
2498 bIsMergedBase = rMergeItem.IsMerged();
2499 /* overlapped cell in a merged range; in Excel all merged cells
2500 must contain same XF index, for correct border */
2501 const ScMergeFlagAttr& rMergeFlagItem = GETITEM( rItemSet, ScMergeFlagAttr, ATTR_MERGE_FLAG );
2502 if( rMergeFlagItem.IsOverlapped() )
2503 nMergeBaseXFId = mxMergedcells->GetBaseXFId( aScPos );
2506 OUString aAddNoteText; // additional text to be appended to a note
2508 switch (rScCell.meType)
2510 case CELLTYPE_VALUE:
2512 double fValue = rScCell.mfValue;
2514 // try to create a Boolean cell
2515 if( pPattern && ((fValue == 0.0) || (fValue == 1.0)) )
2517 sal_uLong nScNumFmt = GETITEMVALUE( pPattern->GetItemSet(), SfxUInt32Item, ATTR_VALUE_FORMAT, sal_uLong );
2518 if( rFormatter.GetType( nScNumFmt ) == css::util::NumberFormat::LOGICAL )
2519 xCell.reset( new XclExpBooleanCell(
2520 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue != 0.0 ) );
2523 // try to create an RK value (compressed floating-point number)
2524 sal_Int32 nRkValue;
2525 if( !xCell && XclTools::GetRKFromDouble( nRkValue, fValue ) )
2526 xCell.reset( new XclExpRkCell(
2527 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, nRkValue ) );
2529 // else: simple floating-point number cell
2530 if( !xCell )
2531 xCell.reset( new XclExpNumberCell(
2532 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue ) );
2534 break;
2536 case CELLTYPE_STRING:
2538 xCell.reset(new XclExpLabelCell(
2539 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpString->getString()));
2541 break;
2543 case CELLTYPE_EDIT:
2545 XclExpHyperlinkHelper aLinkHelper( GetRoot(), aScPos );
2546 xCell.reset(new XclExpLabelCell(
2547 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpEditText, aLinkHelper));
2549 // add a single created HLINK record to the record list
2550 if( aLinkHelper.HasLinkRecord() )
2551 mxHyperlinkList->AppendRecord( aLinkHelper.GetLinkRecord() );
2552 // add list of multiple URLs to the additional cell note text
2553 if( aLinkHelper.HasMultipleUrls() )
2554 aAddNoteText = ScGlobal::addToken( aAddNoteText, aLinkHelper.GetUrlList(), '\n', 2 );
2556 break;
2558 case CELLTYPE_FORMULA:
2560 xCell.reset(new XclExpFormulaCell(
2561 GetRoot(), aXclPos, pPattern, nMergeBaseXFId,
2562 *rScCell.mpFormula, maArrayBfr, maShrfmlaBfr, maTableopBfr));
2564 break;
2566 default:
2567 OSL_FAIL( "XclExpCellTable::XclExpCellTable - unknown cell type" );
2568 SAL_FALLTHROUGH;
2569 case CELLTYPE_NONE:
2571 xCell.reset( new XclExpBlankCell(
2572 GetRoot(), aXclPos, nLastXclCol, pPattern, nMergeBaseXFId ) );
2574 break;
2577 // insert the cell into the current row
2578 if( xCell )
2579 maRowBfr.AppendCell( xCell, bIsMergedBase );
2581 if ( !aAddNoteText.isEmpty() )
2582 mxNoteList->AppendNewRecord( new XclExpNote( GetRoot(), aScPos, nullptr, aAddNoteText ) );
2584 // other sheet contents
2585 if( pPattern )
2587 const SfxItemSet& rItemSet = pPattern->GetItemSet();
2589 // base cell in a merged range
2590 if( bIsMergedBase )
2592 const ScMergeAttr& rMergeItem = GETITEM( rItemSet, ScMergeAttr, ATTR_MERGE );
2593 ScRange aScRange( aScPos );
2594 aScRange.aEnd.IncCol( rMergeItem.GetColMerge() - 1 );
2595 aScRange.aEnd.IncRow( rMergeItem.GetRowMerge() - 1 );
2596 sal_uInt32 nXFId = xCell ? xCell->GetFirstXFId() : EXC_XFID_NOTFOUND;
2597 // blank cells merged vertically may occur repeatedly
2598 OSL_ENSURE( (aScRange.aStart.Col() == aScRange.aEnd.Col()) || (nScCol == nLastScCol),
2599 "XclExpCellTable::XclExpCellTable - invalid repeated blank merged cell" );
2600 for( SCCOL nIndex = nScCol; nIndex <= nLastScCol; ++nIndex )
2602 mxMergedcells->AppendRange( aScRange, nXFId );
2603 aScRange.aStart.IncCol();
2604 aScRange.aEnd.IncCol();
2608 // data validation
2609 if( ScfTools::CheckItem( rItemSet, ATTR_VALIDDATA, false ) )
2611 sal_uLong nScHandle = GETITEMVALUE( rItemSet, SfxUInt32Item, ATTR_VALIDDATA, sal_uLong );
2612 ScRange aScRange( aScPos );
2613 aScRange.aEnd.SetCol( nLastScCol );
2614 mxDval->InsertCellRange( aScRange, nScHandle );
2619 // create missing row settings for rows anyhow flagged or with outlines
2620 maRowBfr.CreateRows( ::std::max( nFirstUnflaggedScRow, nFirstUngroupedScRow ) );
2623 void XclExpCellTable::Finalize()
2625 // Finalize multiple operations.
2626 maTableopBfr.Finalize();
2628 /* Finalize column buffer. This calculates column default XF indexes from
2629 the XF identifiers and fills a vector with these XF indexes. */
2630 ScfUInt16Vec aColXFIndexes;
2631 maColInfoBfr.Finalize( aColXFIndexes );
2633 /* Finalize row buffer. This calculates all cell XF indexes from the XF
2634 identifiers. Then the XF index vector aColXFIndexes (filled above) is
2635 used to calculate the row default formats. With this, all unneeded blank
2636 cell records (equal to row default or column default) will be removed.
2637 The function returns the (most used) default row format in aDefRowData. */
2638 XclExpDefaultRowData aDefRowData;
2639 maRowBfr.Finalize( aDefRowData, aColXFIndexes );
2641 // Initialize the DEFROWHEIGHT record.
2642 mxDefrowheight->SetDefaultData( aDefRowData );
2645 XclExpRecordRef XclExpCellTable::CreateRecord( sal_uInt16 nRecId ) const
2647 XclExpRecordRef xRec;
2648 switch( nRecId )
2650 case EXC_ID3_DIMENSIONS: xRec.reset( new XclExpDelegatingRecord( &const_cast<XclExpRowBuffer*>(&maRowBfr)->GetDimensions() ) ); break;
2651 case EXC_ID2_DEFROWHEIGHT: xRec = mxDefrowheight; break;
2652 case EXC_ID_GUTS: xRec = mxGuts; break;
2653 case EXC_ID_NOTE: xRec = mxNoteList; break;
2654 case EXC_ID_MERGEDCELLS: xRec = mxMergedcells; break;
2655 case EXC_ID_HLINK: xRec = mxHyperlinkList; break;
2656 case EXC_ID_DVAL: xRec = mxDval; break;
2657 case EXC_ID_EXTLST: xRec = mxExtLst; break;
2658 default: OSL_FAIL( "XclExpCellTable::CreateRecord - unknown record id" );
2660 return xRec;
2663 void XclExpCellTable::Save( XclExpStream& rStrm )
2665 // DEFCOLWIDTH and COLINFOs
2666 maColInfoBfr.Save( rStrm );
2667 // ROWs and cell records
2668 maRowBfr.Save( rStrm );
2671 void XclExpCellTable::SaveXml( XclExpXmlStream& rStrm )
2673 // DEFAULT row height
2674 XclExpDefaultRowData& rDefData = mxDefrowheight->GetDefaultData();
2675 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2676 rWorksheet->startElement( XML_sheetFormatPr,
2677 // OOXTODO: XML_baseColWidth
2678 // OOXTODO: XML_defaultColWidth
2679 // OOXTODO: XML_customHeight
2680 // OOXTODO: XML_zeroHeight
2681 // OOXTODO: XML_thickTop
2682 // OOXTODO: XML_thickBottom
2683 XML_defaultRowHeight, OString::number( static_cast< double> ( rDefData.mnHeight ) / 20.0 ).getStr(),
2684 XML_outlineLevelRow, OString::number( maRowBfr.GetHighestOutlineLevel() ).getStr(),
2685 XML_outlineLevelCol, OString::number( maColInfoBfr.GetHighestOutlineLevel() ).getStr(),
2686 FSEND );
2687 rWorksheet->endElement( XML_sheetFormatPr );
2689 maColInfoBfr.SaveXml( rStrm );
2690 maRowBfr.SaveXml( rStrm );
2691 mxExtLst->SaveXml( rStrm );
2694 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */