1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <xetable.hxx>
21
22 #include <map>
23 #include <numeric>
24 #include <com/sun/star/i18n/ScriptType.hpp>
25 #include <scitems.hxx>
26 #include <svl/intitem.hxx>
27 #include <svl/numformat.hxx>
28 #include <svl/stritem.hxx>
29 #include <tools/UnitConversion.hxx>
30 #include <editeng/flditem.hxx>
31 #include <document.hxx>
32 #include <dociter.hxx>
33 #include <olinetab.hxx>
34 #include <formulacell.hxx>
35 #include <patattr.hxx>
36 #include <attrib.hxx>
37 #include <xehelper.hxx>
38 #include <xecontent.hxx>
39 #include <xeescher.hxx>
40 #include <xeextlst.hxx>
41 #include <xeformula.hxx>
42 #include <xlcontent.hxx>
43 #include <xltools.hxx>
44 #include <tokenarray.hxx>
45 #include <formula/errorcodes.hxx>
46 #include <comphelper/threadpool.hxx>
47 #include <oox/token/tokens.hxx>
48 #include <oox/export/utils.hxx>
49
50 using namespace ::oox;
51
52 namespace ApiScriptType = ::com::sun::star::i18n::ScriptType;
53
54 // Helper records for cell records
55
XclExpStringRec(const XclExpRoot & rRoot,const OUString & rResult)56 XclExpStringRec::XclExpStringRec( const XclExpRoot& rRoot, const OUString& rResult ) :
57 XclExpRecord( EXC_ID3_STRING ),
58 mxResult( XclExpStringHelper::CreateString( rRoot, rResult ) )
59 {
60 OSL_ENSURE( (rRoot.GetBiff() <= EXC_BIFF5) || (mxResult->Len() > 0),
61 "XclExpStringRec::XclExpStringRec - empty result not allowed in BIFF8+" );
62 SetRecSize( mxResult->GetSize() );
63 }
64
WriteBody(XclExpStream & rStrm)65 void XclExpStringRec::WriteBody( XclExpStream& rStrm )
66 {
67 rStrm << *mxResult;
68 }
69
70 // Additional records for special formula ranges ==============================
71
XclExpRangeFmlaBase(sal_uInt16 nRecId,sal_uInt32 nRecSize,const ScAddress & rScPos)72 XclExpRangeFmlaBase::XclExpRangeFmlaBase(
73 sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScAddress& rScPos ) :
74 XclExpRecord( nRecId, nRecSize ),
75 maXclRange( ScAddress::UNINITIALIZED ),
76 maBaseXclPos( ScAddress::UNINITIALIZED )
77 {
78 maBaseXclPos.Set( static_cast< sal_uInt16 >( rScPos.Col() ), static_cast< sal_uInt16 >( rScPos.Row() ) );
79 maXclRange.maFirst = maXclRange.maLast = maBaseXclPos;
80 }
81
XclExpRangeFmlaBase(sal_uInt16 nRecId,sal_uInt32 nRecSize,const ScRange & rScRange)82 XclExpRangeFmlaBase::XclExpRangeFmlaBase(
83 sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScRange& rScRange ) :
84 XclExpRecord( nRecId, nRecSize ),
85 maXclRange( ScAddress::UNINITIALIZED ),
86 maBaseXclPos( ScAddress::UNINITIALIZED )
87 {
88 maXclRange.Set(
89 static_cast< sal_uInt16 >( rScRange.aStart.Col() ),
90 static_cast< sal_uInt16 >( rScRange.aStart.Row() ),
91 static_cast< sal_uInt16 >( rScRange.aEnd.Col() ),
92 static_cast< sal_uInt16 >( rScRange.aEnd.Row() ) );
93 maBaseXclPos = maXclRange.maFirst;
94 }
95
IsBasePos(sal_uInt16 nXclCol,sal_uInt32 nXclRow) const96 bool XclExpRangeFmlaBase::IsBasePos( sal_uInt16 nXclCol, sal_uInt32 nXclRow ) const
97 {
98 return (maBaseXclPos.mnCol == nXclCol) && (maBaseXclPos.mnRow == nXclRow);
99 }
100
Extend(const ScAddress & rScPos)101 void XclExpRangeFmlaBase::Extend( const ScAddress& rScPos )
102 {
103 sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() );
104 sal_uInt32 nXclRow = static_cast< sal_uInt32 >( rScPos.Row() );
105 maXclRange.maFirst.mnCol = ::std::min( maXclRange.maFirst.mnCol, nXclCol );
106 maXclRange.maFirst.mnRow = ::std::min( maXclRange.maFirst.mnRow, nXclRow );
107 maXclRange.maLast.mnCol = ::std::max( maXclRange.maLast.mnCol, nXclCol );
108 maXclRange.maLast.mnRow = ::std::max( maXclRange.maLast.mnRow, nXclRow );
109 }
110
WriteRangeAddress(XclExpStream & rStrm) const111 void XclExpRangeFmlaBase::WriteRangeAddress( XclExpStream& rStrm ) const
112 {
113 maXclRange.Write( rStrm, false );
114 }
115
116 // Array formulas =============================================================
117
XclExpArray(const XclTokenArrayRef & xTokArr,const ScRange & rScRange)118 XclExpArray::XclExpArray( const XclTokenArrayRef& xTokArr, const ScRange& rScRange ) :
119 XclExpRangeFmlaBase( EXC_ID3_ARRAY, 14 + xTokArr->GetSize(), rScRange ),
120 mxTokArr( xTokArr )
121 {
122 }
123
CreateCellTokenArray(const XclExpRoot & rRoot) const124 XclTokenArrayRef XclExpArray::CreateCellTokenArray( const XclExpRoot& rRoot ) const
125 {
126 return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos );
127 }
128
IsVolatile() const129 bool XclExpArray::IsVolatile() const
130 {
131 return mxTokArr->IsVolatile();
132 }
133
WriteBody(XclExpStream & rStrm)134 void XclExpArray::WriteBody( XclExpStream& rStrm )
135 {
136 WriteRangeAddress( rStrm );
137 sal_uInt16 nFlags = EXC_ARRAY_DEFAULTFLAGS;
138 ::set_flag( nFlags, EXC_ARRAY_RECALC_ALWAYS, IsVolatile() );
139 rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr;
140 }
141
XclExpArrayBuffer(const XclExpRoot & rRoot)142 XclExpArrayBuffer::XclExpArrayBuffer( const XclExpRoot& rRoot ) :
143 XclExpRoot( rRoot )
144 {
145 }
146
CreateArray(const ScTokenArray & rScTokArr,const ScRange & rScRange)147 XclExpArrayRef XclExpArrayBuffer::CreateArray( const ScTokenArray& rScTokArr, const ScRange& rScRange )
148 {
149 const ScAddress& rScPos = rScRange.aStart;
150 XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_MATRIX, rScTokArr, &rScPos );
151
152 OSL_ENSURE( maRecMap.find( rScPos ) == maRecMap.end(), "XclExpArrayBuffer::CreateArray - array exists already" );
153 XclExpArrayRef& rxRec = maRecMap[ rScPos ];
154 rxRec = new XclExpArray( xTokArr, rScRange );
155 return rxRec;
156 }
157
FindArray(const ScTokenArray & rScTokArr,const ScAddress & rBasePos) const158 XclExpArrayRef XclExpArrayBuffer::FindArray( const ScTokenArray& rScTokArr, const ScAddress& rBasePos ) const
159 {
160 XclExpArrayRef xRec;
161 // try to extract a matrix reference token
162 if (rScTokArr.GetLen() != 1)
163 // Must consist of a single reference token.
164 return xRec;
165
166 const formula::FormulaToken* pToken = rScTokArr.GetArray()[0];
167 if (!pToken || pToken->GetOpCode() != ocMatRef)
168 // not a matrix reference token.
169 return xRec;
170
171 const ScSingleRefData& rRef = *pToken->GetSingleRef();
172 ScAddress aAbsPos = rRef.toAbs(GetRoot().GetDoc(), rBasePos);
173 XclExpArrayMap::const_iterator it = maRecMap.find(aAbsPos);
174
175 if (it != maRecMap.end())
176 xRec = it->second;
177 return xRec;
178 }
179
180 // Shared formulas ============================================================
181
XclExpShrfmla(const XclTokenArrayRef & xTokArr,const ScAddress & rScPos)182 XclExpShrfmla::XclExpShrfmla( const XclTokenArrayRef& xTokArr, const ScAddress& rScPos ) :
183 XclExpRangeFmlaBase( EXC_ID_SHRFMLA, 10 + xTokArr->GetSize(), rScPos ),
184 mxTokArr( xTokArr ),
185 mnUsedCount( 1 )
186 {
187 }
188
ExtendRange(const ScAddress & rScPos)189 void XclExpShrfmla::ExtendRange( const ScAddress& rScPos )
190 {
191 Extend( rScPos );
192 ++mnUsedCount;
193 }
194
CreateCellTokenArray(const XclExpRoot & rRoot) const195 XclTokenArrayRef XclExpShrfmla::CreateCellTokenArray( const XclExpRoot& rRoot ) const
196 {
197 return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos );
198 }
199
IsVolatile() const200 bool XclExpShrfmla::IsVolatile() const
201 {
202 return mxTokArr->IsVolatile();
203 }
204
WriteBody(XclExpStream & rStrm)205 void XclExpShrfmla::WriteBody( XclExpStream& rStrm )
206 {
207 WriteRangeAddress( rStrm );
208 rStrm << sal_uInt8( 0 ) << mnUsedCount << *mxTokArr;
209 }
210
XclExpShrfmlaBuffer(const XclExpRoot & rRoot)211 XclExpShrfmlaBuffer::XclExpShrfmlaBuffer( const XclExpRoot& rRoot ) :
212 XclExpRoot( rRoot )
213 {
214 }
215
IsValidTokenArray(const ScTokenArray & rArray) const216 bool XclExpShrfmlaBuffer::IsValidTokenArray( const ScTokenArray& rArray ) const
217 {
218 using namespace formula;
219
220 FormulaToken** pTokens = rArray.GetArray();
221 sal_uInt16 nLen = rArray.GetLen();
222 for (sal_uInt16 i = 0; i < nLen; ++i)
223 {
224 const FormulaToken* p = pTokens[i];
225 switch (p->GetType())
226 {
227 case svSingleRef:
228 {
229 const ScSingleRefData& rRefData = *p->GetSingleRef();
230 if (!GetFormulaCompiler().IsRef2D(rRefData))
231 // Excel's shared formula cannot include 3D reference.
232 return false;
233 }
234 break;
235 case svDoubleRef:
236 {
237 const ScComplexRefData& rRefData = *p->GetDoubleRef();
238 if (!GetFormulaCompiler().IsRef2D(rRefData))
239 // Excel's shared formula cannot include 3D reference.
240 return false;
241 }
242 break;
243 case svExternalSingleRef:
244 case svExternalDoubleRef:
245 case svExternalName:
246 // External references aren't allowed.
247 return false;
248 default:
249 ;
250 }
251 }
252 return true;
253 }
254
CreateOrExtendShrfmla(const ScFormulaCell & rScCell,const ScAddress & rScPos)255 XclExpShrfmlaRef XclExpShrfmlaBuffer::CreateOrExtendShrfmla(
256 const ScFormulaCell& rScCell, const ScAddress& rScPos )
257 {
258 XclExpShrfmlaRef xRec;
259 const ScTokenArray* pShrdScTokArr = rScCell.GetSharedCode();
260 if (!pShrdScTokArr)
261 // This formula cell is not shared formula cell.
262 return xRec;
263
264 // Check to see if this shared formula contains any tokens that Excel's shared formula cannot handle.
265 if (maBadTokens.count(pShrdScTokArr) > 0)
266 // Already on the black list. Skip it.
267 return xRec;
268
269 if (!IsValidTokenArray(*pShrdScTokArr))
270 {
271 // We can't export this as shared formula.
272 maBadTokens.insert(pShrdScTokArr);
273 return xRec;
274 }
275
276 TokensType::iterator aIt = maRecMap.find(pShrdScTokArr);
277 if( aIt == maRecMap.end() )
278 {
279 // create a new record
280 XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_SHARED, *pShrdScTokArr, &rScPos );
281 xRec = new XclExpShrfmla( xTokArr, rScPos );
282 maRecMap[ pShrdScTokArr ] = xRec;
283 }
284 else
285 {
286 // extend existing record
287 OSL_ENSURE( aIt->second, "XclExpShrfmlaBuffer::CreateOrExtendShrfmla - missing record" );
288 xRec = aIt->second;
289 xRec->ExtendRange( rScPos );
290 }
291
292 return xRec;
293 }
294
295 // Multiple operations ========================================================
296
XclExpTableop(const ScAddress & rScPos,const XclMultipleOpRefs & rRefs,sal_uInt8 nScMode)297 XclExpTableop::XclExpTableop( const ScAddress& rScPos,
298 const XclMultipleOpRefs& rRefs, sal_uInt8 nScMode ) :
299 XclExpRangeFmlaBase( EXC_ID3_TABLEOP, 16, rScPos ),
300 mnLastAppXclCol( static_cast< sal_uInt16 >( rScPos.Col() ) ),
301 mnColInpXclCol( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Col() ) ),
302 mnColInpXclRow( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Row() ) ),
303 mnRowInpXclCol( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Col() ) ),
304 mnRowInpXclRow( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Row() ) ),
305 mnScMode( nScMode ),
306 mbValid( false )
307 {
308 }
309
TryExtend(const ScAddress & rScPos,const XclMultipleOpRefs & rRefs)310 bool XclExpTableop::TryExtend( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs )
311 {
312 sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() );
313 sal_uInt16 nXclRow = static_cast< sal_uInt16 >( rScPos.Row() );
314
315 bool bOk = IsAppendable( nXclCol, nXclRow );
316 if( bOk )
317 {
318 SCCOL nFirstScCol = static_cast< SCCOL >( maXclRange.maFirst.mnCol );
319 SCROW nFirstScRow = static_cast< SCROW >( maXclRange.maFirst.mnRow );
320 SCCOL nColInpScCol = static_cast< SCCOL >( mnColInpXclCol );
321 SCROW nColInpScRow = static_cast< SCROW >( mnColInpXclRow );
322 SCCOL nRowInpScCol = static_cast< SCCOL >( mnRowInpXclCol );
323 SCROW nRowInpScRow = static_cast< SCROW >( mnRowInpXclRow );
324
325 bOk = ((mnScMode == 2) == rRefs.mbDblRefMode) &&
326 (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) &&
327 (nColInpScCol == rRefs.maColFirstScPos.Col()) &&
328 (nColInpScRow == rRefs.maColFirstScPos.Row()) &&
329 (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) &&
330 (rScPos.Tab() == rRefs.maColRelScPos.Tab());
331
332 if( bOk ) switch( mnScMode )
333 {
334 case 0:
335 bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col()) &&
336 (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) &&
337 (nFirstScCol == rRefs.maColRelScPos.Col() + 1) &&
338 (rScPos.Row() == rRefs.maColRelScPos.Row());
339 break;
340 case 1:
341 bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) &&
342 (rScPos.Row() == rRefs.maFmlaScPos.Row()) &&
343 (rScPos.Col() == rRefs.maColRelScPos.Col()) &&
344 (nFirstScRow == rRefs.maColRelScPos.Row() + 1);
345 break;
346 case 2:
347 bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) &&
348 (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) &&
349 (nFirstScCol == rRefs.maColRelScPos.Col() + 1) &&
350 (rScPos.Row() == rRefs.maColRelScPos.Row()) &&
351 (nRowInpScCol == rRefs.maRowFirstScPos.Col()) &&
352 (nRowInpScRow == rRefs.maRowFirstScPos.Row()) &&
353 (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) &&
354 (rScPos.Col() == rRefs.maRowRelScPos.Col()) &&
355 (nFirstScRow == rRefs.maRowRelScPos.Row() + 1) &&
356 (rScPos.Tab() == rRefs.maRowRelScPos.Tab());
357 break;
358 default:
359 bOk = false;
360 }
361
362 if( bOk )
363 {
364 // extend the cell range
365 OSL_ENSURE( IsAppendable( nXclCol, nXclRow ), "XclExpTableop::TryExtend - wrong cell address" );
366 Extend( rScPos );
367 mnLastAppXclCol = nXclCol;
368 }
369 }
370
371 return bOk;
372 }
373
Finalize()374 void XclExpTableop::Finalize()
375 {
376 // is the range complete? (last appended cell is in last column)
377 mbValid = maXclRange.maLast.mnCol == mnLastAppXclCol;
378 // if last row is incomplete, try to shorten the used range
379 if( !mbValid && (maXclRange.maFirst.mnRow < maXclRange.maLast.mnRow) )
380 {
381 --maXclRange.maLast.mnRow;
382 mbValid = true;
383 }
384
385 // check if referred cells are outside of own range
386 if( !mbValid )
387 return;
388
389 switch( mnScMode )
390 {
391 case 0:
392 mbValid = (mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
393 (mnColInpXclRow < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow);
394 break;
395 case 1:
396 mbValid = (mnColInpXclCol < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
397 (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow);
398 break;
399 case 2:
400 mbValid = ((mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) ||
401 (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow)) &&
402 ((mnRowInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnRowInpXclCol > maXclRange.maLast.mnCol) ||
403 (mnRowInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnRowInpXclRow > maXclRange.maLast.mnRow));
404 break;
405 }
406 }
407
CreateCellTokenArray(const XclExpRoot & rRoot) const408 XclTokenArrayRef XclExpTableop::CreateCellTokenArray( const XclExpRoot& rRoot ) const
409 {
410 XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler();
411 return mbValid ?
412 rFmlaComp.CreateSpecialRefFormula( EXC_TOKID_TBL, maBaseXclPos ) :
413 rFmlaComp.CreateErrorFormula( EXC_ERR_NA );
414 }
415
IsVolatile() const416 bool XclExpTableop::IsVolatile() const
417 {
418 return true;
419 }
420
Save(XclExpStream & rStrm)421 void XclExpTableop::Save( XclExpStream& rStrm )
422 {
423 if( mbValid )
424 XclExpRangeFmlaBase::Save( rStrm );
425 }
426
IsAppendable(sal_uInt16 nXclCol,sal_uInt16 nXclRow) const427 bool XclExpTableop::IsAppendable( sal_uInt16 nXclCol, sal_uInt16 nXclRow ) const
428 {
429 return ((nXclCol == mnLastAppXclCol + 1) && (nXclRow == maXclRange.maFirst.mnRow)) ||
430 ((nXclCol == mnLastAppXclCol + 1) && (nXclCol <= maXclRange.maLast.mnCol) && (nXclRow == maXclRange.maLast.mnRow)) ||
431 ((mnLastAppXclCol == maXclRange.maLast.mnCol) && (nXclCol == maXclRange.maFirst.mnCol) && (nXclRow == maXclRange.maLast.mnRow + 1));
432 }
433
WriteBody(XclExpStream & rStrm)434 void XclExpTableop::WriteBody( XclExpStream& rStrm )
435 {
436 sal_uInt16 nFlags = EXC_TABLEOP_DEFAULTFLAGS;
437 ::set_flag( nFlags, EXC_TABLEOP_RECALC_ALWAYS, IsVolatile() );
438 switch( mnScMode )
439 {
440 case 1: ::set_flag( nFlags, EXC_TABLEOP_ROW ); break;
441 case 2: ::set_flag( nFlags, EXC_TABLEOP_BOTH ); break;
442 }
443
444 WriteRangeAddress( rStrm );
445 rStrm << nFlags;
446 if( mnScMode == 2 )
447 rStrm << mnRowInpXclRow << mnRowInpXclCol << mnColInpXclRow << mnColInpXclCol;
448 else
449 rStrm << mnColInpXclRow << mnColInpXclCol << sal_uInt32( 0 );
450 }
451
XclExpTableopBuffer(const XclExpRoot & rRoot)452 XclExpTableopBuffer::XclExpTableopBuffer( const XclExpRoot& rRoot ) :
453 XclExpRoot( rRoot )
454 {
455 }
456
CreateOrExtendTableop(const ScTokenArray & rScTokArr,const ScAddress & rScPos)457 XclExpTableopRef XclExpTableopBuffer::CreateOrExtendTableop(
458 const ScTokenArray& rScTokArr, const ScAddress& rScPos )
459 {
460 XclExpTableopRef xRec;
461
462 // try to extract cell references of a multiple operations formula
463 XclMultipleOpRefs aRefs;
464 if (XclTokenArrayHelper::GetMultipleOpRefs(GetDoc(), aRefs, rScTokArr, rScPos))
465 {
466 // try to find an existing TABLEOP record for this cell position
467 for( size_t nPos = 0, nSize = maTableopList.GetSize(); !xRec && (nPos < nSize); ++nPos )
468 {
469 XclExpTableop* xTempRec = maTableopList.GetRecord( nPos );
470 if( xTempRec->TryExtend( rScPos, aRefs ) )
471 xRec = xTempRec;
472 }
473
474 // no record found, or found record not extensible
475 if( !xRec )
476 xRec = TryCreate( rScPos, aRefs );
477 }
478
479 return xRec;
480 }
481
Finalize()482 void XclExpTableopBuffer::Finalize()
483 {
484 for( size_t nPos = 0, nSize = maTableopList.GetSize(); nPos < nSize; ++nPos )
485 maTableopList.GetRecord( nPos )->Finalize();
486 }
487
TryCreate(const ScAddress & rScPos,const XclMultipleOpRefs & rRefs)488 XclExpTableopRef XclExpTableopBuffer::TryCreate( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs )
489 {
490 sal_uInt8 nScMode = 0;
491 bool bOk = (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) &&
492 (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) &&
493 (rScPos.Tab() == rRefs.maColRelScPos.Tab());
494
495 if( bOk )
496 {
497 if( rRefs.mbDblRefMode )
498 {
499 nScMode = 2;
500 bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) &&
501 (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) &&
502 (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) &&
503 (rScPos.Row() == rRefs.maColRelScPos.Row()) &&
504 (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) &&
505 (rScPos.Col() == rRefs.maRowRelScPos.Col()) &&
506 (rScPos.Row() == rRefs.maRowRelScPos.Row() + 1) &&
507 (rScPos.Tab() == rRefs.maRowRelScPos.Tab());
508 }
509 else if( (rScPos.Col() == rRefs.maFmlaScPos.Col()) &&
510 (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) &&
511 (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) &&
512 (rScPos.Row() == rRefs.maColRelScPos.Row()) )
513 {
514 nScMode = 0;
515 }
516 else if( (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) &&
517 (rScPos.Row() == rRefs.maFmlaScPos.Row()) &&
518 (rScPos.Col() == rRefs.maColRelScPos.Col()) &&
519 (rScPos.Row() == rRefs.maColRelScPos.Row() + 1) )
520 {
521 nScMode = 1;
522 }
523 else
524 {
525 bOk = false;
526 }
527 }
528
529 XclExpTableopRef xRec;
530 if( bOk )
531 {
532 xRec = new XclExpTableop( rScPos, rRefs, nScMode );
533 maTableopList.AppendRecord( xRec );
534 }
535
536 return xRec;
537 }
538
539 // Cell records
540
XclExpCellBase(sal_uInt16 nRecId,std::size_t nContSize,const XclAddress & rXclPos)541 XclExpCellBase::XclExpCellBase(
542 sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos ) :
543 XclExpRecord( nRecId, nContSize + 4 ),
544 maXclPos( rXclPos )
545 {
546 }
547
IsMultiLineText() const548 bool XclExpCellBase::IsMultiLineText() const
549 {
550 return false;
551 }
552
TryMerge(const XclExpCellBase &)553 bool XclExpCellBase::TryMerge( const XclExpCellBase& /*rCell*/ )
554 {
555 return false;
556 }
557
GetBlankXFIndexes(ScfUInt16Vec &) const558 void XclExpCellBase::GetBlankXFIndexes( ScfUInt16Vec& /*rXFIndexes*/ ) const
559 {
560 // default: do nothing
561 }
562
RemoveUnusedBlankCells(const ScfUInt16Vec &,size_t)563 void XclExpCellBase::RemoveUnusedBlankCells( const ScfUInt16Vec& /*rXFIndexes*/, size_t /*nStartAllNotFound*/ )
564 {
565 // default: do nothing
566 }
567
568 // Single cell records ========================================================
569
XclExpSingleCellBase(sal_uInt16 nRecId,std::size_t nContSize,const XclAddress & rXclPos,sal_uInt32 nXFId)570 XclExpSingleCellBase::XclExpSingleCellBase(
571 sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos, sal_uInt32 nXFId ) :
572 XclExpCellBase( nRecId, 2, rXclPos ),
573 maXFId( nXFId ),
574 mnContSize( nContSize )
575 {
576 }
577
XclExpSingleCellBase(const XclExpRoot & rRoot,sal_uInt16 nRecId,std::size_t nContSize,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_Int16 nScript,sal_uInt32 nForcedXFId)578 XclExpSingleCellBase::XclExpSingleCellBase( const XclExpRoot& rRoot,
579 sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos,
580 const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForcedXFId ) :
581 XclExpCellBase( nRecId, 2, rXclPos ),
582 maXFId( nForcedXFId ),
583 mnContSize( nContSize )
584 {
585 if( GetXFId() == EXC_XFID_NOTFOUND )
586 SetXFId( rRoot.GetXFBuffer().Insert( pPattern, nScript ) );
587 }
588
GetLastXclCol() const589 sal_uInt16 XclExpSingleCellBase::GetLastXclCol() const
590 {
591 return GetXclCol();
592 }
593
GetFirstXFId() const594 sal_uInt32 XclExpSingleCellBase::GetFirstXFId() const
595 {
596 return GetXFId();
597 }
598
IsEmpty() const599 bool XclExpSingleCellBase::IsEmpty() const
600 {
601 return false;
602 }
603
ConvertXFIndexes(const XclExpRoot & rRoot)604 void XclExpSingleCellBase::ConvertXFIndexes( const XclExpRoot& rRoot )
605 {
606 maXFId.ConvertXFIndex( rRoot );
607 }
608
Save(XclExpStream & rStrm)609 void XclExpSingleCellBase::Save( XclExpStream& rStrm )
610 {
611 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
612 AddRecSize( mnContSize );
613 XclExpCellBase::Save( rStrm );
614 }
615
WriteBody(XclExpStream & rStrm)616 void XclExpSingleCellBase::WriteBody( XclExpStream& rStrm )
617 {
618 rStrm << static_cast<sal_uInt16> (GetXclRow()) << GetXclCol() << maXFId.mnXFIndex;
619 WriteContents( rStrm );
620 }
621
XclExpNumberCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,double fValue)622 XclExpNumberCell::XclExpNumberCell(
623 const XclExpRoot& rRoot, const XclAddress& rXclPos,
624 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, double fValue ) :
625 // #i41210# always use latin script for number cells - may look wrong for special number formats...
626 XclExpSingleCellBase( rRoot, EXC_ID3_NUMBER, 8, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ),
627 mfValue( fValue )
628 {
629 }
630
lcl_GetStyleId(const XclExpXmlStream & rStrm,sal_uInt32 nXFIndex)631 static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, sal_uInt32 nXFIndex )
632 {
633 return OString::number( rStrm.GetRoot().GetXFBuffer()
634 .GetXmlCellIndex( nXFIndex ) );
635 }
636
lcl_GetStyleId(const XclExpXmlStream & rStrm,const XclExpCellBase & rCell)637 static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, const XclExpCellBase& rCell )
638 {
639 sal_uInt32 nXFId = rCell.GetFirstXFId();
640 sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( nXFId );
641 return lcl_GetStyleId( rStrm, nXFIndex );
642 }
643
SaveXml(XclExpXmlStream & rStrm)644 void XclExpNumberCell::SaveXml( XclExpXmlStream& rStrm )
645 {
646 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
647 rWorksheet->startElement( XML_c,
648 XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(),
649 XML_s, lcl_GetStyleId(rStrm, *this),
650 XML_t, "n"
651 // OOXTODO: XML_cm, XML_vm, XML_ph
652 );
653 rWorksheet->startElement(XML_v);
654 rWorksheet->write( mfValue );
655 rWorksheet->endElement( XML_v );
656 rWorksheet->endElement( XML_c );
657 }
658
WriteContents(XclExpStream & rStrm)659 void XclExpNumberCell::WriteContents( XclExpStream& rStrm )
660 {
661 rStrm << mfValue;
662 }
663
XclExpBooleanCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,bool bValue)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 )
670 {
671 }
672
SaveXml(XclExpXmlStream & rStrm)673 void XclExpBooleanCell::SaveXml( XclExpXmlStream& rStrm )
674 {
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),
679 XML_t, "b"
680 // OOXTODO: XML_cm, XML_vm, XML_ph
681 );
682 rWorksheet->startElement( XML_v );
683 rWorksheet->write( mbValue ? "1" : "0" );
684 rWorksheet->endElement( XML_v );
685 rWorksheet->endElement( XML_c );
686 }
687
WriteContents(XclExpStream & rStrm)688 void XclExpBooleanCell::WriteContents( XclExpStream& rStrm )
689 {
690 rStrm << sal_uInt16( mbValue ? 1 : 0 ) << EXC_BOOLERR_BOOL;
691 }
692
XclExpLabelCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,const OUString & rStr)693 XclExpLabelCell::XclExpLabelCell(
694 const XclExpRoot& rRoot, const XclAddress& rXclPos,
695 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, const OUString& rStr ) :
696 XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId )
697 {
698 sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN;
699 XclExpStringRef xText = XclExpStringHelper::CreateCellString(
700 rRoot, rStr, pPattern, XclStrFlags::NONE, nMaxLen);
701 Init( rRoot, pPattern, xText );
702 }
703
XclExpLabelCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,const EditTextObject * pEditText,XclExpHyperlinkHelper & rLinkHelper)704 XclExpLabelCell::XclExpLabelCell(
705 const XclExpRoot& rRoot, const XclAddress& rXclPos,
706 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId,
707 const EditTextObject* pEditText, XclExpHyperlinkHelper& rLinkHelper ) :
708 XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId )
709 {
710 sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN;
711
712 XclExpStringRef xText;
713 if (pEditText)
714 xText = XclExpStringHelper::CreateCellString(
715 rRoot, *pEditText, pPattern, rLinkHelper, XclStrFlags::NONE, nMaxLen);
716 else
717 xText = XclExpStringHelper::CreateCellString(
718 rRoot, OUString(), pPattern, XclStrFlags::NONE, nMaxLen);
719
720 Init( rRoot, pPattern, xText );
721 }
722
IsMultiLineText() const723 bool XclExpLabelCell::IsMultiLineText() const
724 {
725 return mbLineBreak || mxText->HasNewline();
726 }
727
Init(const XclExpRoot & rRoot,const ScPatternAttr * pPattern,XclExpStringRef const & xText)728 void XclExpLabelCell::Init( const XclExpRoot& rRoot,
729 const ScPatternAttr* pPattern, XclExpStringRef const & xText )
730 {
731 OSL_ENSURE( xText && xText->Len(), "XclExpLabelCell::XclExpLabelCell - empty string passed" );
732 mxText = xText;
733 mnSstIndex = 0;
734
735 const XclFormatRunVec& rFormats = mxText->GetFormats();
736 // remove formatting of the leading run if the entire string
737 // is equally formatted
738 sal_uInt16 nXclFont = EXC_FONT_NOTFOUND;
739 if( rFormats.size() == 1 )
740 nXclFont = mxText->RemoveLeadingFont();
741 else
742 nXclFont = mxText->GetLeadingFont();
743
744 // create cell format
745 if( GetXFId() == EXC_XFID_NOTFOUND )
746 {
747 OSL_ENSURE(nXclFont != EXC_FONT_NOTFOUND, "XclExpLabelCell::Init - leading font not found");
748
749 // Buggy Excel behaviour - newlines are ignored unless wrap-text is enabled,
750 // so always force text-wrapping (unless it was imported that way and not modified).
751 bool bForceLineBreak = mxText->HasNewline() && !mxText->IsSingleLineForMultipleParagraphs();
752 SetXFId(rRoot.GetXFBuffer().InsertWithFont(
753 pPattern, ApiScriptType::WEAK, nXclFont, bForceLineBreak));
754 }
755
756 // get auto-wrap attribute from cell format
757 const XclExpXF* pXF = rRoot.GetXFBuffer().GetXFById( GetXFId() );
758 mbLineBreak = pXF && pXF->GetAlignmentData().mbLineBreak;
759
760 // initialize the record contents
761 switch( rRoot.GetBiff() )
762 {
763 case EXC_BIFF5:
764 // BIFF5-BIFF7: create a LABEL or RSTRING record
765 OSL_ENSURE( mxText->Len() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::XclExpLabelCell - string too long" );
766 SetContSize( mxText->GetSize() );
767 // formatted string is exported in an RSTRING record
768 if( mxText->IsRich() )
769 {
770 OSL_ENSURE( mxText->GetFormatsCount() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::WriteContents - too many formats" );
771 mxText->LimitFormatCount( EXC_LABEL_MAXLEN );
772 SetRecId( EXC_ID_RSTRING );
773 SetContSize( GetContSize() + 1 + 2 * mxText->GetFormatsCount() );
774 }
775 break;
776 case EXC_BIFF8:
777 // BIFF8+: create a LABELSST record
778 mnSstIndex = rRoot.GetSst().Insert( xText );
779 SetRecId( EXC_ID_LABELSST );
780 SetContSize( 4 );
781 break;
782 default: DBG_ERROR_BIFF();
783 }
784 }
785
SaveXml(XclExpXmlStream & rStrm)786 void XclExpLabelCell::SaveXml( XclExpXmlStream& rStrm )
787 {
788 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
789 rWorksheet->startElement( XML_c,
790 XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(),
791 XML_s, lcl_GetStyleId(rStrm, *this),
792 XML_t, "s"
793 // OOXTODO: XML_cm, XML_vm, XML_ph
794 );
795 rWorksheet->startElement( XML_v );
796 rWorksheet->write( static_cast<sal_Int32>(mnSstIndex) );
797 rWorksheet->endElement( XML_v );
798 rWorksheet->endElement( XML_c );
799 }
800
WriteContents(XclExpStream & rStrm)801 void XclExpLabelCell::WriteContents( XclExpStream& rStrm )
802 {
803 switch( rStrm.GetRoot().GetBiff() )
804 {
805 case EXC_BIFF5:
806 rStrm << *mxText;
807 if( mxText->IsRich() )
808 {
809 rStrm << static_cast< sal_uInt8 >( mxText->GetFormatsCount() );
810 mxText->WriteFormats( rStrm );
811 }
812 break;
813 case EXC_BIFF8:
814 rStrm << mnSstIndex;
815 break;
816 default: DBG_ERROR_BIFF();
817 }
818 }
819
XclExpFormulaCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,const ScFormulaCell & rScFmlaCell,XclExpArrayBuffer & rArrayBfr,XclExpShrfmlaBuffer & rShrfmlaBfr,XclExpTableopBuffer & rTableopBfr)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 ) )
829 {
830 // *** Find result number format overwriting cell number format *** -------
831
832 if( GetXFId() == EXC_XFID_NOTFOUND )
833 {
834 SvNumberFormatter& rFormatter = rRoot.GetFormatter();
835 XclExpNumFmtBuffer& rNumFmtBfr = rRoot.GetNumFmtBuffer();
836
837 // current cell number format
838 sal_uInt32 nScNumFmt = pPattern ?
839 pPattern->GetItem( ATTR_VALUE_FORMAT ).GetValue() :
840 rNumFmtBfr.GetStandardFormat();
841
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 SvNumFormatType nFormatType = mrScFmlaCell.GetFormatType();
849 if( ((nScNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) &&
850 (nFormatType != SvNumFormatType::LOGICAL) &&
851 (nFormatType != SvNumFormatType::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 == SvNumFormatType::LOGICAL) &&
856 (rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL) )
857 nAltScNumFmt = rNumFmtBfr.GetStandardFormat();
858
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 == SvNumFormatType::TEXT )
863 {
864 OUString aResult = mrScFmlaCell.GetString().getString();
865 bForceLineBreak = mrScFmlaCell.IsMultilineResult();
866 nScript = XclExpStringHelper::GetLeadingScriptType( rRoot, aResult );
867 }
868 SetXFId( rRoot.GetXFBuffer().InsertWithNumFmt( pPattern, nScript, nAltScNumFmt, bForceLineBreak ) );
869 }
870
871 // *** Convert the formula token array *** --------------------------------
872
873 ScAddress aScPos( static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), rRoot.GetCurrScTab() );
874 const ScTokenArray& rScTokArr = *mrScFmlaCell.GetCode();
875
876 // first try to create multiple operations
877 mxAddRec = rTableopBfr.CreateOrExtendTableop( rScTokArr, aScPos );
878
879 // no multiple operation found - try to create matrix formula
880 if( !mxAddRec )
881 switch( mrScFmlaCell.GetMatrixFlag() )
882 {
883 case ScMatrixMode::Formula:
884 {
885 // origin of the matrix - find the used matrix range
886 SCCOL nMatWidth;
887 SCROW nMatHeight;
888 mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight );
889 OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" );
890 ScRange aMatScRange( aScPos );
891 ScAddress& rMatEnd = aMatScRange.aEnd;
892 rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) );
893 rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) );
894 // reduce to valid range (range keeps valid, because start position IS valid)
895 rRoot.GetAddressConverter().ValidateRange( aMatScRange, true );
896 // create the ARRAY record
897 mxAddRec = rArrayBfr.CreateArray( rScTokArr, aMatScRange );
898 }
899 break;
900 case ScMatrixMode::Reference:
901 {
902 // other formula cell covered by a matrix - find the ARRAY record
903 mxAddRec = rArrayBfr.FindArray(rScTokArr, aScPos);
904 // should always be found, if Calc document is not broken
905 OSL_ENSURE( mxAddRec, "XclExpFormulaCell::XclExpFormulaCell - no matrix found" );
906 }
907 break;
908 default:;
909 }
910
911 // no matrix found - try to create shared formula
912 if( !mxAddRec )
913 mxAddRec = rShrfmlaBfr.CreateOrExtendShrfmla(mrScFmlaCell, aScPos);
914
915 // no shared formula found - create a simple cell formula
916 if( !mxAddRec )
917 mxTokArr = rRoot.GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CELL, rScTokArr, &aScPos );
918 }
919
Save(XclExpStream & rStrm)920 void XclExpFormulaCell::Save( XclExpStream& rStrm )
921 {
922 // create token array for FORMULA cells with additional record
923 if( mxAddRec )
924 mxTokArr = mxAddRec->CreateCellTokenArray( rStrm.GetRoot() );
925
926 // FORMULA record itself
927 OSL_ENSURE( mxTokArr, "XclExpFormulaCell::Save - missing token array" );
928 if( !mxTokArr )
929 mxTokArr = rStrm.GetRoot().GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NA );
930 SetContSize( 16 + mxTokArr->GetSize() );
931 XclExpSingleCellBase::Save( rStrm );
932
933 // additional record (ARRAY, SHRFMLA, or TABLEOP), only for first FORMULA record
934 if( mxAddRec && mxAddRec->IsBasePos( GetXclCol(), GetXclRow() ) )
935 mxAddRec->Save( rStrm );
936
937 // STRING record for string result
938 if( mxStringRec )
939 mxStringRec->Save( rStrm );
940 }
941
SaveXml(XclExpXmlStream & rStrm)942 void XclExpFormulaCell::SaveXml( XclExpXmlStream& rStrm )
943 {
944 const char* sType = nullptr;
945 OUString sValue;
946 XclXmlUtils::GetFormulaTypeAndValue( mrScFmlaCell, sType, sValue );
947 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
948 rWorksheet->startElement( XML_c,
949 XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(),
950 XML_s, lcl_GetStyleId(rStrm, *this),
951 XML_t, sType
952 // OOXTODO: XML_cm, XML_vm, XML_ph
953 );
954
955 bool bWriteFormula = true;
956 bool bTagStarted = false;
957 ScAddress aScPos( static_cast< SCCOL >( GetXclPos().mnCol ),
958 static_cast< SCROW >( GetXclPos().mnRow ), rStrm.GetRoot().GetCurrScTab() );
959
960 switch (mrScFmlaCell.GetMatrixFlag())
961 {
962 case ScMatrixMode::NONE:
963 break;
964 case ScMatrixMode::Reference:
965 bWriteFormula = false;
966 break;
967 case ScMatrixMode::Formula:
968 {
969 // origin of the matrix - find the used matrix range
970 SCCOL nMatWidth;
971 SCROW nMatHeight;
972 mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight );
973 OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" );
974 ScRange aMatScRange( aScPos );
975 ScAddress& rMatEnd = aMatScRange.aEnd;
976 rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) );
977 rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) );
978 // reduce to valid range (range keeps valid, because start position IS valid
979 rStrm.GetRoot().GetAddressConverter().ValidateRange( aMatScRange, true );
980
981 OStringBuffer sFmlaCellRange;
982 if (rStrm.GetRoot().GetDoc().ValidRange(aMatScRange))
983 {
984 // calculate the cell range.
985 sFmlaCellRange.append( XclXmlUtils::ToOString(
986 rStrm.GetRoot().GetStringBuf(), aMatScRange.aStart )
987 + OString::Concat(":"));
988 sFmlaCellRange.append( XclXmlUtils::ToOString(
989 rStrm.GetRoot().GetStringBuf(), aMatScRange.aEnd ));
990 }
991
992 if ( aMatScRange.aStart.Col() == GetXclPos().mnCol &&
993 aMatScRange.aStart.Row() == static_cast<SCROW>(GetXclPos().mnRow))
994 {
995 rWorksheet->startElement( XML_f,
996 XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) ||
997 (mxAddRec && mxAddRec->IsVolatile())),
998 XML_t, mxAddRec ? "array" : nullptr,
999 XML_ref, !sFmlaCellRange.isEmpty()? sFmlaCellRange.getStr() : nullptr
1000 // OOXTODO: XML_dt2D, bool
1001 // OOXTODO: XML_dtr, bool
1002 // OOXTODO: XML_del1, bool
1003 // OOXTODO: XML_del2, bool
1004 // OOXTODO: XML_r1, ST_CellRef
1005 // OOXTODO: XML_r2, ST_CellRef
1006 // OOXTODO: XML_ca, bool
1007 // OOXTODO: XML_si, uint
1008 // OOXTODO: XML_bx bool
1009 );
1010 bTagStarted = true;
1011 }
1012 }
1013 break;
1014 }
1015
1016 if (bWriteFormula)
1017 {
1018 if (!bTagStarted)
1019 {
1020 rWorksheet->startElement( XML_f,
1021 XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) ||
1022 (mxAddRec && mxAddRec->IsVolatile()) ) );
1023 }
1024 rWorksheet->writeEscaped( XclXmlUtils::ToOUString(
1025 rStrm.GetRoot().GetCompileFormulaContext(), mrScFmlaCell.aPos, mrScFmlaCell.GetCode(),
1026 mrScFmlaCell.GetErrCode()));
1027 rWorksheet->endElement( XML_f );
1028 }
1029
1030 if( strcmp( sType, "inlineStr" ) == 0 )
1031 {
1032 rWorksheet->startElement(XML_is);
1033 rWorksheet->startElement(XML_t);
1034 rWorksheet->writeEscaped( sValue );
1035 rWorksheet->endElement( XML_t );
1036 rWorksheet->endElement( XML_is );
1037 }
1038 else
1039 {
1040 rWorksheet->startElement(XML_v);
1041 rWorksheet->writeEscaped( sValue );
1042 rWorksheet->endElement( XML_v );
1043 }
1044 rWorksheet->endElement( XML_c );
1045 }
1046
WriteContents(XclExpStream & rStrm)1047 void XclExpFormulaCell::WriteContents( XclExpStream& rStrm )
1048 {
1049 FormulaError nScErrCode = mrScFmlaCell.GetErrCode();
1050 if( nScErrCode != FormulaError::NONE )
1051 {
1052 rStrm << EXC_FORMULA_RES_ERROR << sal_uInt8( 0 )
1053 << XclTools::GetXclErrorCode( nScErrCode )
1054 << sal_uInt8( 0 ) << sal_uInt16( 0 )
1055 << sal_uInt16( 0xFFFF );
1056 }
1057 else
1058 {
1059 // result of the formula
1060 switch( mrScFmlaCell.GetFormatType() )
1061 {
1062 case SvNumFormatType::NUMBER:
1063 {
1064 // either value or error code
1065 rStrm << mrScFmlaCell.GetValue();
1066 }
1067 break;
1068
1069 case SvNumFormatType::TEXT:
1070 {
1071 OUString aResult = mrScFmlaCell.GetString().getString();
1072 if( !aResult.isEmpty() || (rStrm.GetRoot().GetBiff() <= EXC_BIFF5) )
1073 {
1074 rStrm << EXC_FORMULA_RES_STRING;
1075 mxStringRec = new XclExpStringRec( rStrm.GetRoot(), aResult );
1076 }
1077 else
1078 rStrm << EXC_FORMULA_RES_EMPTY; // BIFF8 only
1079 rStrm << sal_uInt8( 0 ) << sal_uInt32( 0 ) << sal_uInt16( 0xFFFF );
1080 }
1081 break;
1082
1083 case SvNumFormatType::LOGICAL:
1084 {
1085 sal_uInt8 nXclValue = (mrScFmlaCell.GetValue() == 0.0) ? 0 : 1;
1086 rStrm << EXC_FORMULA_RES_BOOL << sal_uInt8( 0 )
1087 << nXclValue << sal_uInt8( 0 ) << sal_uInt16( 0 )
1088 << sal_uInt16( 0xFFFF );
1089 }
1090 break;
1091
1092 default:
1093 rStrm << mrScFmlaCell.GetValue();
1094 }
1095 }
1096
1097 // flags and formula token array
1098 sal_uInt16 nFlags = EXC_FORMULA_DEFAULTFLAGS;
1099 ::set_flag( nFlags, EXC_FORMULA_RECALC_ALWAYS, mxTokArr->IsVolatile() || (mxAddRec && mxAddRec->IsVolatile()) );
1100 ::set_flag( nFlags, EXC_FORMULA_SHARED, mxAddRec && (mxAddRec->GetRecId() == EXC_ID_SHRFMLA) );
1101 rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr;
1102 }
1103
1104 // Multiple cell records ======================================================
1105
XclExpMultiCellBase(sal_uInt16 nRecId,sal_uInt16 nMulRecId,std::size_t nContSize,const XclAddress & rXclPos)1106 XclExpMultiCellBase::XclExpMultiCellBase(
1107 sal_uInt16 nRecId, sal_uInt16 nMulRecId, std::size_t nContSize, const XclAddress& rXclPos ) :
1108 XclExpCellBase( nRecId, 0, rXclPos ),
1109 mnMulRecId( nMulRecId ),
1110 mnContSize( nContSize )
1111 {
1112 }
1113
GetLastXclCol() const1114 sal_uInt16 XclExpMultiCellBase::GetLastXclCol() const
1115 {
1116 return GetXclCol() + GetCellCount() - 1;
1117 }
1118
GetFirstXFId() const1119 sal_uInt32 XclExpMultiCellBase::GetFirstXFId() const
1120 {
1121 return maXFIds.empty() ? XclExpXFBuffer::GetDefCellXFId() : maXFIds.front().mnXFId;
1122 }
1123
IsEmpty() const1124 bool XclExpMultiCellBase::IsEmpty() const
1125 {
1126 return maXFIds.empty();
1127 }
1128
ConvertXFIndexes(const XclExpRoot & rRoot)1129 void XclExpMultiCellBase::ConvertXFIndexes( const XclExpRoot& rRoot )
1130 {
1131 for( auto& rXFId : maXFIds )
1132 rXFId.ConvertXFIndex( rRoot );
1133 }
1134
Save(XclExpStream & rStrm)1135 void XclExpMultiCellBase::Save( XclExpStream& rStrm )
1136 {
1137 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
1138
1139 XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end();
1140 XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin();
1141 XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg;
1142 sal_uInt16 nBegXclCol = GetXclCol();
1143 sal_uInt16 nEndXclCol = nBegXclCol;
1144
1145 while( aRangeEnd != aEnd )
1146 {
1147 // find begin of next used XF range
1148 aRangeBeg = aRangeEnd;
1149 nBegXclCol = nEndXclCol;
1150 while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) )
1151 {
1152 nBegXclCol = nBegXclCol + aRangeBeg->mnCount;
1153 ++aRangeBeg;
1154 }
1155 // find end of next used XF range
1156 aRangeEnd = aRangeBeg;
1157 nEndXclCol = nBegXclCol;
1158 while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) )
1159 {
1160 nEndXclCol = nEndXclCol + aRangeEnd->mnCount;
1161 ++aRangeEnd;
1162 }
1163
1164 // export this range as a record
1165 if( aRangeBeg != aRangeEnd )
1166 {
1167 sal_uInt16 nCount = nEndXclCol - nBegXclCol;
1168 bool bIsMulti = nCount > 1;
1169 std::size_t nTotalSize = GetRecSize() + (2 + mnContSize) * nCount;
1170 if( bIsMulti ) nTotalSize += 2;
1171
1172 rStrm.StartRecord( bIsMulti ? mnMulRecId : GetRecId(), nTotalSize );
1173 rStrm << static_cast<sal_uInt16> (GetXclRow()) << nBegXclCol;
1174
1175 sal_uInt16 nRelCol = nBegXclCol - GetXclCol();
1176 for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt )
1177 {
1178 for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx )
1179 {
1180 rStrm << aIt->mnXFIndex;
1181 WriteContents( rStrm, nRelCol );
1182 ++nRelCol;
1183 }
1184 }
1185 if( bIsMulti )
1186 rStrm << static_cast< sal_uInt16 >( nEndXclCol - 1 );
1187 rStrm.EndRecord();
1188 }
1189 }
1190 }
1191
SaveXml(XclExpXmlStream & rStrm)1192 void XclExpMultiCellBase::SaveXml( XclExpXmlStream& rStrm )
1193 {
1194 XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end();
1195 XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin();
1196 XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg;
1197 sal_uInt16 nBegXclCol = GetXclCol();
1198 sal_uInt16 nEndXclCol = nBegXclCol;
1199
1200 while( aRangeEnd != aEnd )
1201 {
1202 // find begin of next used XF range
1203 aRangeBeg = aRangeEnd;
1204 nBegXclCol = nEndXclCol;
1205 while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) )
1206 {
1207 nBegXclCol = nBegXclCol + aRangeBeg->mnCount;
1208 ++aRangeBeg;
1209 }
1210 // find end of next used XF range
1211 aRangeEnd = aRangeBeg;
1212 nEndXclCol = nBegXclCol;
1213 while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) )
1214 {
1215 nEndXclCol = nEndXclCol + aRangeEnd->mnCount;
1216 ++aRangeEnd;
1217 }
1218
1219 // export this range as a record
1220 if( aRangeBeg != aRangeEnd )
1221 {
1222 sal_uInt16 nRelColIdx = nBegXclCol - GetXclCol();
1223 sal_Int32 nRelCol = 0;
1224 for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt )
1225 {
1226 for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx )
1227 {
1228 WriteXmlContents(
1229 rStrm,
1230 XclAddress( static_cast<sal_uInt16>(nBegXclCol + nRelCol), GetXclRow() ),
1231 aIt->mnXFIndex,
1232 nRelColIdx );
1233 ++nRelCol;
1234 ++nRelColIdx;
1235 }
1236 }
1237 }
1238 }
1239 }
1240
GetCellCount() const1241 sal_uInt16 XclExpMultiCellBase::GetCellCount() const
1242 {
1243 return std::accumulate(maXFIds.begin(), maXFIds.end(), sal_uInt16(0),
1244 [](const sal_uInt16& rSum, const XclExpMultiXFId& rXFId) { return rSum + rXFId.mnCount; });
1245 }
1246
AppendXFId(const XclExpMultiXFId & rXFId)1247 void XclExpMultiCellBase::AppendXFId( const XclExpMultiXFId& rXFId )
1248 {
1249 if( maXFIds.empty() || (maXFIds.back().mnXFId != rXFId.mnXFId) )
1250 maXFIds.push_back( rXFId );
1251 else
1252 maXFIds.back().mnCount += rXFId.mnCount;
1253 }
1254
AppendXFId(const XclExpRoot & rRoot,const ScPatternAttr * pPattern,sal_uInt16 nScript,sal_uInt32 nForcedXFId,sal_uInt16 nCount)1255 void XclExpMultiCellBase::AppendXFId( const XclExpRoot& rRoot,
1256 const ScPatternAttr* pPattern, sal_uInt16 nScript, sal_uInt32 nForcedXFId, sal_uInt16 nCount )
1257 {
1258 sal_uInt32 nXFId = (nForcedXFId == EXC_XFID_NOTFOUND) ?
1259 rRoot.GetXFBuffer().Insert( pPattern, nScript ) : nForcedXFId;
1260 AppendXFId( XclExpMultiXFId( nXFId, nCount ) );
1261 }
1262
TryMergeXFIds(const XclExpMultiCellBase & rCell)1263 bool XclExpMultiCellBase::TryMergeXFIds( const XclExpMultiCellBase& rCell )
1264 {
1265 if( GetLastXclCol() + 1 == rCell.GetXclCol() )
1266 {
1267 maXFIds.insert( maXFIds.end(), rCell.maXFIds.begin(), rCell.maXFIds.end() );
1268 return true;
1269 }
1270 return false;
1271 }
1272
GetXFIndexes(ScfUInt16Vec & rXFIndexes) const1273 void XclExpMultiCellBase::GetXFIndexes( ScfUInt16Vec& rXFIndexes ) const
1274 {
1275 OSL_ENSURE( GetLastXclCol() < rXFIndexes.size(), "XclExpMultiCellBase::GetXFIndexes - vector too small" );
1276 ScfUInt16Vec::iterator aDestIt = rXFIndexes.begin() + GetXclCol();
1277 for( const auto& rXFId : maXFIds )
1278 {
1279 ::std::fill( aDestIt, aDestIt + rXFId.mnCount, rXFId.mnXFIndex );
1280 aDestIt += rXFId.mnCount;
1281 }
1282 }
1283
RemoveUnusedXFIndexes(const ScfUInt16Vec & rXFIndexes,size_t nStartAllNotFound)1284 void XclExpMultiCellBase::RemoveUnusedXFIndexes( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound )
1285 {
1286 // save last column before calling maXFIds.clear()
1287 sal_uInt16 nLastXclCol = GetLastXclCol();
1288 OSL_ENSURE( nLastXclCol < rXFIndexes.size(), "XclExpMultiCellBase::RemoveUnusedXFIndexes - XF index vector too small" );
1289
1290 // build new XF index vector, containing passed XF indexes
1291 maXFIds.clear();
1292 // Process only all that possibly are not EXC_XF_NOTFOUND.
1293 size_t nEnd = std::min<size_t>(nLastXclCol + 1, nStartAllNotFound);
1294 for( size_t i = GetXclCol(); i < nEnd; ++i )
1295 {
1296 XclExpMultiXFId aXFId( 0 );
1297 // AppendXFId() tests XclExpXFIndex::mnXFId, set it too
1298 aXFId.mnXFId = aXFId.mnXFIndex = rXFIndexes[ i ];
1299 AppendXFId( aXFId );
1300 }
1301
1302 // remove leading and trailing unused XF indexes
1303 if( !maXFIds.empty() && (maXFIds.front().mnXFIndex == EXC_XF_NOTFOUND) )
1304 {
1305 SetXclCol( GetXclCol() + maXFIds.front().mnCount );
1306 maXFIds.erase(maXFIds.begin(), maXFIds.begin() + 1);
1307 }
1308 if( !maXFIds.empty() && (maXFIds.back().mnXFIndex == EXC_XF_NOTFOUND) )
1309 maXFIds.pop_back();
1310
1311 // The Save() function will skip all XF indexes equal to EXC_XF_NOTFOUND.
1312 }
1313
GetStartColAllDefaultCell() const1314 sal_uInt16 XclExpMultiCellBase::GetStartColAllDefaultCell() const
1315 {
1316 sal_uInt16 col = GetXclCol();
1317 sal_uInt16 nMaxNonDefCol = col;
1318 for( const auto& rXFId : maXFIds )
1319 {
1320 col += rXFId.mnCount;
1321 if (rXFId.mnXFIndex != EXC_XF_DEFAULTCELL)
1322 nMaxNonDefCol = col;
1323 }
1324 return nMaxNonDefCol;
1325 }
1326
XclExpBlankCell(const XclAddress & rXclPos,const XclExpMultiXFId & rXFId)1327 XclExpBlankCell::XclExpBlankCell( const XclAddress& rXclPos, const XclExpMultiXFId& rXFId ) :
1328 XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos )
1329 {
1330 OSL_ENSURE( rXFId.mnCount > 0, "XclExpBlankCell::XclExpBlankCell - invalid count" );
1331 AppendXFId( rXFId );
1332 }
1333
XclExpBlankCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,sal_uInt16 nLastXclCol,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId)1334 XclExpBlankCell::XclExpBlankCell(
1335 const XclExpRoot& rRoot, const XclAddress& rXclPos, sal_uInt16 nLastXclCol,
1336 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId ) :
1337 XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos )
1338 {
1339 OSL_ENSURE( rXclPos.mnCol <= nLastXclCol, "XclExpBlankCell::XclExpBlankCell - invalid column range" );
1340 // #i46627# use default script type instead of ApiScriptType::WEAK
1341 AppendXFId( rRoot, pPattern, rRoot.GetDefApiScript(), nForcedXFId, nLastXclCol - rXclPos.mnCol + 1 );
1342 }
1343
TryMerge(const XclExpCellBase & rCell)1344 bool XclExpBlankCell::TryMerge( const XclExpCellBase& rCell )
1345 {
1346 const XclExpBlankCell* pBlankCell = dynamic_cast< const XclExpBlankCell* >( &rCell );
1347 return pBlankCell && TryMergeXFIds( *pBlankCell );
1348 }
1349
GetBlankXFIndexes(ScfUInt16Vec & rXFIndexes) const1350 void XclExpBlankCell::GetBlankXFIndexes( ScfUInt16Vec& rXFIndexes ) const
1351 {
1352 GetXFIndexes( rXFIndexes );
1353 }
1354
RemoveUnusedBlankCells(const ScfUInt16Vec & rXFIndexes,size_t nStartAllNotFound)1355 void XclExpBlankCell::RemoveUnusedBlankCells( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound )
1356 {
1357 RemoveUnusedXFIndexes( rXFIndexes, nStartAllNotFound );
1358 }
1359
WriteContents(XclExpStream &,sal_uInt16)1360 void XclExpBlankCell::WriteContents( XclExpStream& /*rStrm*/, sal_uInt16 /*nRelCol*/ )
1361 {
1362 }
1363
WriteXmlContents(XclExpXmlStream & rStrm,const XclAddress & rAddress,sal_uInt32 nXFId,sal_uInt16)1364 void XclExpBlankCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 /* nRelCol */ )
1365 {
1366 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1367 rWorksheet->singleElement( XML_c,
1368 XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(),
1369 XML_s, lcl_GetStyleId(rStrm, nXFId) );
1370 }
1371
XclExpRkCell(const XclExpRoot & rRoot,const XclAddress & rXclPos,const ScPatternAttr * pPattern,sal_uInt32 nForcedXFId,sal_Int32 nRkValue)1372 XclExpRkCell::XclExpRkCell(
1373 const XclExpRoot& rRoot, const XclAddress& rXclPos,
1374 const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, sal_Int32 nRkValue ) :
1375 XclExpMultiCellBase( EXC_ID_RK, EXC_ID_MULRK, 4, rXclPos )
1376 {
1377 // #i41210# always use latin script for number cells - may look wrong for special number formats...
1378 AppendXFId( rRoot, pPattern, ApiScriptType::LATIN, nForcedXFId );
1379 maRkValues.push_back( nRkValue );
1380 }
1381
TryMerge(const XclExpCellBase & rCell)1382 bool XclExpRkCell::TryMerge( const XclExpCellBase& rCell )
1383 {
1384 const XclExpRkCell* pRkCell = dynamic_cast< const XclExpRkCell* >( &rCell );
1385 if( pRkCell && TryMergeXFIds( *pRkCell ) )
1386 {
1387 maRkValues.insert( maRkValues.end(), pRkCell->maRkValues.begin(), pRkCell->maRkValues.end() );
1388 return true;
1389 }
1390 return false;
1391 }
1392
WriteXmlContents(XclExpXmlStream & rStrm,const XclAddress & rAddress,sal_uInt32 nXFId,sal_uInt16 nRelCol)1393 void XclExpRkCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 nRelCol )
1394 {
1395 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1396 rWorksheet->startElement( XML_c,
1397 XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(),
1398 XML_s, lcl_GetStyleId(rStrm, nXFId),
1399 XML_t, "n"
1400 // OOXTODO: XML_cm, XML_vm, XML_ph
1401 );
1402 rWorksheet->startElement( XML_v );
1403 rWorksheet->write( XclTools::GetDoubleFromRK( maRkValues[ nRelCol ] ) );
1404 rWorksheet->endElement( XML_v );
1405 rWorksheet->endElement( XML_c );
1406 }
1407
WriteContents(XclExpStream & rStrm,sal_uInt16 nRelCol)1408 void XclExpRkCell::WriteContents( XclExpStream& rStrm, sal_uInt16 nRelCol )
1409 {
1410 OSL_ENSURE( nRelCol < maRkValues.size(), "XclExpRkCell::WriteContents - overflow error" );
1411 rStrm << maRkValues[ nRelCol ];
1412 }
1413
1414 // Rows and Columns
1415
XclExpOutlineBuffer(const XclExpRoot & rRoot,bool bRows)1416 XclExpOutlineBuffer::XclExpOutlineBuffer( const XclExpRoot& rRoot, bool bRows ) :
1417 mpScOLArray( nullptr ),
1418 maLevelInfos( SC_OL_MAXDEPTH ),
1419 mnCurrLevel( 0 ),
1420 mbCurrCollapse( false )
1421 {
1422 if( const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ) )
1423 mpScOLArray = &(bRows ? pOutlineTable->GetRowArray() : pOutlineTable->GetColArray());
1424
1425 if( mpScOLArray )
1426 for( size_t nLevel = 0; nLevel < SC_OL_MAXDEPTH; ++nLevel )
1427 if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nLevel, 0 ) )
1428 maLevelInfos[ nLevel ].mnScEndPos = pEntry->GetEnd();
1429 }
1430
UpdateColRow(SCCOLROW nScPos)1431 void XclExpOutlineBuffer::UpdateColRow( SCCOLROW nScPos )
1432 {
1433 if( !mpScOLArray )
1434 return;
1435
1436 // find open level index for passed position
1437 size_t nNewOpenScLevel = 0; // new open level (0-based Calc index)
1438 sal_uInt8 nNewLevel = 0; // new open level (1-based Excel index)
1439
1440 if( mpScOLArray->FindTouchedLevel( nScPos, nScPos, nNewOpenScLevel ) )
1441 nNewLevel = static_cast< sal_uInt8 >( nNewOpenScLevel + 1 );
1442 // else nNewLevel keeps 0 to show that there are no groups
1443
1444 mbCurrCollapse = false;
1445 if( nNewLevel >= mnCurrLevel )
1446 {
1447 // new level(s) opened, or no level closed - update all level infos
1448 for( size_t nScLevel = 0; nScLevel <= nNewOpenScLevel; ++nScLevel )
1449 {
1450 /* In each level: check if a new group is started (there may be
1451 neighbored groups without gap - therefore check ALL levels). */
1452 if( maLevelInfos[ nScLevel ].mnScEndPos < nScPos )
1453 {
1454 if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nScLevel, nScPos ) )
1455 {
1456 maLevelInfos[ nScLevel ].mnScEndPos = pEntry->GetEnd();
1457 maLevelInfos[ nScLevel ].mbHidden = pEntry->IsHidden();
1458 }
1459 }
1460 }
1461 }
1462 else
1463 {
1464 // level(s) closed - check if any of the closed levels are collapsed
1465 // Calc uses 0-based level indexes
1466 sal_uInt16 nOldOpenScLevel = mnCurrLevel - 1;
1467 for( sal_uInt16 nScLevel = nNewOpenScLevel + 1; !mbCurrCollapse && (nScLevel <= nOldOpenScLevel); ++nScLevel )
1468 mbCurrCollapse = maLevelInfos[ nScLevel ].mbHidden;
1469 }
1470
1471 // cache new opened level
1472 mnCurrLevel = nNewLevel;
1473 }
1474
XclExpGuts(const XclExpRoot & rRoot)1475 XclExpGuts::XclExpGuts( const XclExpRoot& rRoot ) :
1476 XclExpRecord( EXC_ID_GUTS, 8 ),
1477 mnColLevels( 0 ),
1478 mnColWidth( 0 ),
1479 mnRowLevels( 0 ),
1480 mnRowWidth( 0 )
1481 {
1482 const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() );
1483 if(!pOutlineTable)
1484 return;
1485
1486 // column outline groups
1487 const ScOutlineArray& rColArray = pOutlineTable->GetColArray();
1488 mnColLevels = ulimit_cast< sal_uInt16 >( rColArray.GetDepth(), EXC_OUTLINE_MAX );
1489 if( mnColLevels )
1490 {
1491 ++mnColLevels;
1492 mnColWidth = 12 * mnColLevels + 5;
1493 }
1494
1495 // row outline groups
1496 const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
1497 mnRowLevels = ulimit_cast< sal_uInt16 >( rRowArray.GetDepth(), EXC_OUTLINE_MAX );
1498 if( mnRowLevels )
1499 {
1500 ++mnRowLevels;
1501 mnRowWidth = 12 * mnRowLevels + 5;
1502 }
1503 }
1504
WriteBody(XclExpStream & rStrm)1505 void XclExpGuts::WriteBody( XclExpStream& rStrm )
1506 {
1507 rStrm << mnRowWidth << mnColWidth << mnRowLevels << mnColLevels;
1508 }
1509
XclExpDimensions(const XclExpRoot & rRoot)1510 XclExpDimensions::XclExpDimensions( const XclExpRoot& rRoot ) :
1511 mrRoot(rRoot),
1512 mnFirstUsedXclRow( 0 ),
1513 mnFirstFreeXclRow( 0 ),
1514 mnFirstUsedXclCol( 0 ),
1515 mnFirstFreeXclCol( 0 )
1516 {
1517 switch( rRoot.GetBiff() )
1518 {
1519 case EXC_BIFF2: SetRecHeader( EXC_ID2_DIMENSIONS, 8 ); break;
1520 case EXC_BIFF3:
1521 case EXC_BIFF4:
1522 case EXC_BIFF5: SetRecHeader( EXC_ID3_DIMENSIONS, 10 ); break;
1523 case EXC_BIFF8: SetRecHeader( EXC_ID3_DIMENSIONS, 14 ); break;
1524 default: DBG_ERROR_BIFF();
1525 }
1526 }
1527
SetDimensions(sal_uInt16 nFirstUsedXclCol,sal_uInt32 nFirstUsedXclRow,sal_uInt16 nFirstFreeXclCol,sal_uInt32 nFirstFreeXclRow)1528 void XclExpDimensions::SetDimensions(
1529 sal_uInt16 nFirstUsedXclCol, sal_uInt32 nFirstUsedXclRow,
1530 sal_uInt16 nFirstFreeXclCol, sal_uInt32 nFirstFreeXclRow )
1531 {
1532 mnFirstUsedXclRow = nFirstUsedXclRow;
1533 mnFirstFreeXclRow = nFirstFreeXclRow;
1534 mnFirstUsedXclCol = nFirstUsedXclCol;
1535 mnFirstFreeXclCol = nFirstFreeXclCol;
1536 }
1537
SaveXml(XclExpXmlStream & rStrm)1538 void XclExpDimensions::SaveXml( XclExpXmlStream& rStrm )
1539 {
1540 ScRange aRange;
1541 aRange.aStart.SetRow( static_cast<SCROW>(mnFirstUsedXclRow) );
1542 aRange.aStart.SetCol( static_cast<SCCOL>(mnFirstUsedXclCol) );
1543
1544 if( mnFirstFreeXclRow != mnFirstUsedXclRow && mnFirstFreeXclCol != mnFirstUsedXclCol )
1545 {
1546 aRange.aEnd.SetRow( static_cast<SCROW>(mnFirstFreeXclRow-1) );
1547 aRange.aEnd.SetCol( static_cast<SCCOL>(mnFirstFreeXclCol-1) );
1548 }
1549
1550 aRange.PutInOrder();
1551 rStrm.GetCurrentStream()->singleElement( XML_dimension,
1552 // To be compatible with MS Office 2007,
1553 // we need full address notation format
1554 // e.g. "A1:AMJ177" and not partial like: "1:177".
1555 XML_ref, XclXmlUtils::ToOString(mrRoot.GetDoc(), aRange, true) );
1556 }
1557
WriteBody(XclExpStream & rStrm)1558 void XclExpDimensions::WriteBody( XclExpStream& rStrm )
1559 {
1560 XclBiff eBiff = rStrm.GetRoot().GetBiff();
1561 if( eBiff == EXC_BIFF8 )
1562 rStrm << mnFirstUsedXclRow << mnFirstFreeXclRow;
1563 else
1564 rStrm << static_cast< sal_uInt16 >( mnFirstUsedXclRow ) << static_cast< sal_uInt16 >( mnFirstFreeXclRow );
1565 rStrm << mnFirstUsedXclCol << mnFirstFreeXclCol;
1566 if( eBiff >= EXC_BIFF3 )
1567 rStrm << sal_uInt16( 0 );
1568 }
1569
1570 namespace {
1571
lclGetCChCorrection(const XclExpRoot & rRoot)1572 double lclGetCChCorrection(const XclExpRoot& rRoot)
1573 {
1574 // Convert the correction from 1/256ths of a character size to count of chars
1575 // TODO: make to fit ECMA-376-1:2016 18.3.1.81 sheetFormatPr (Sheet Format Properties):
1576 // 5 pixels are added to the base width: 2 for margin padding on each side, plus 1 for gridline
1577 // So this should depend on rRoot.GetCharWidth(), not on font height
1578
1579 tools::Long nFontHt = rRoot.GetFontBuffer().GetAppFontData().mnHeight;
1580 return XclTools::GetXclDefColWidthCorrection(nFontHt) / 256.0;
1581 }
1582
1583 } // namespace
1584
XclExpDefcolwidth(const XclExpRoot & rRoot)1585 XclExpDefcolwidth::XclExpDefcolwidth( const XclExpRoot& rRoot ) :
1586 XclExpDoubleRecord(EXC_ID_DEFCOLWIDTH, EXC_DEFCOLWIDTH_DEF + lclGetCChCorrection(rRoot)),
1587 XclExpRoot( rRoot )
1588 {
1589 }
1590
IsDefWidth(sal_uInt16 nXclColWidth) const1591 bool XclExpDefcolwidth::IsDefWidth( sal_uInt16 nXclColWidth ) const
1592 {
1593 // This formula is taking number of characters with GetValue()
1594 // and it is translating it into default column width.
1595 // https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.column.aspx
1596 double defaultColumnWidth = 256.0 * GetValue();
1597
1598 // exactly matched, if difference is less than 1/16 of a character to the left or to the right
1599 return std::abs(defaultColumnWidth - nXclColWidth) < 256.0 * 1.0 / 16.0;
1600 }
1601
SetDefWidth(sal_uInt16 nXclColWidth,bool bXLS)1602 void XclExpDefcolwidth::SetDefWidth( sal_uInt16 nXclColWidth, bool bXLS )
1603 {
1604 double fCCh = nXclColWidth / 256.0;
1605 if (bXLS)
1606 {
1607 const double fCorrection = lclGetCChCorrection(GetRoot());
1608 const double fCorrectedCCh = fCCh - fCorrection;
1609 // Now get the value which would be stored in XLS DefColWidth struct
1610 double fCChRound = std::round(fCorrectedCCh);
1611 // If default width was set to a value that is not representable as integral CCh between 0
1612 // and 255, then just ignore that value, and use an arbitrary default. That way, the stored
1613 // default might not represent the most used column width (or any used column width), but
1614 // that's OK, and it just means that those columns will explicitly store their width in
1615 // 1/256ths of char, and have fUserSet in their ColInfo records.
1616 if (fCChRound < 0.0 || fCChRound > 255.0 || std::abs(fCChRound - fCorrectedCCh) > 1.0 / 512)
1617 fCChRound = 8.0;
1618 fCCh = fCChRound + fCorrection;
1619 }
1620
1621 SetValue(fCCh);
1622 }
1623
Save(XclExpStream & rStrm)1624 void XclExpDefcolwidth::Save(XclExpStream& rStrm)
1625 {
1626 double fCorrectedCCh = GetValue() - lclGetCChCorrection(GetRoot());
1627 // Convert double to sal_uInt16
1628 XclExpUInt16Record aUInt16Rec(GetRecId(),
1629 static_cast<sal_uInt16>(std::round(fCorrectedCCh)));
1630 aUInt16Rec.Save(rStrm);
1631 }
1632
XclExpColinfo(const XclExpRoot & rRoot,SCCOL nScCol,SCROW nLastScRow,XclExpColOutlineBuffer & rOutlineBfr)1633 XclExpColinfo::XclExpColinfo( const XclExpRoot& rRoot,
1634 SCCOL nScCol, SCROW nLastScRow, XclExpColOutlineBuffer& rOutlineBfr ) :
1635 XclExpRecord( EXC_ID_COLINFO, 12 ),
1636 XclExpRoot( rRoot ),
1637 mbCustomWidth( false ),
1638 mnWidth( 0 ),
1639 mnScWidth( 0 ),
1640 mnFlags( 0 ),
1641 mnOutlineLevel( 0 ),
1642 mnFirstXclCol( static_cast< sal_uInt16 >( nScCol ) ),
1643 mnLastXclCol( static_cast< sal_uInt16 >( nScCol ) )
1644 {
1645 ScDocument& rDoc = GetDoc();
1646 SCTAB nScTab = GetCurrScTab();
1647
1648 // column default format
1649 maXFId.mnXFId = GetXFBuffer().Insert(
1650 rDoc.GetMostUsedPattern( nScCol, 0, nLastScRow, nScTab ), GetDefApiScript() );
1651
1652 // column width. If column is hidden then we should return real value (not zero)
1653 sal_uInt16 nScWidth = rDoc.GetColWidth( nScCol, nScTab, false );
1654 mnWidth = XclTools::GetXclColumnWidth( nScWidth, GetCharWidth() );
1655 mnScWidth = convertTwipToMm100(nScWidth);
1656
1657 // column flags
1658 ::set_flag( mnFlags, EXC_COLINFO_HIDDEN, rDoc.ColHidden(nScCol, nScTab) );
1659
1660 // outline data
1661 rOutlineBfr.Update( nScCol );
1662 ::set_flag( mnFlags, EXC_COLINFO_COLLAPSED, rOutlineBfr.IsCollapsed() );
1663 ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 8, 3 );
1664 mnOutlineLevel = rOutlineBfr.GetLevel();
1665 }
1666
ConvertXFIndexes()1667 void XclExpColinfo::ConvertXFIndexes()
1668 {
1669 maXFId.ConvertXFIndex( GetRoot() );
1670 }
1671
IsDefault(const XclExpDefcolwidth & rDefColWidth)1672 bool XclExpColinfo::IsDefault( const XclExpDefcolwidth& rDefColWidth )
1673 {
1674 mbCustomWidth = !rDefColWidth.IsDefWidth(mnWidth);
1675 return (maXFId.mnXFIndex == EXC_XF_DEFAULTCELL) &&
1676 (mnFlags == 0) &&
1677 (mnOutlineLevel == 0) &&
1678 !mbCustomWidth;
1679 }
1680
TryMerge(const XclExpColinfo & rColInfo)1681 bool XclExpColinfo::TryMerge( const XclExpColinfo& rColInfo )
1682 {
1683 if( (maXFId.mnXFIndex == rColInfo.maXFId.mnXFIndex) &&
1684 (mnWidth == rColInfo.mnWidth) &&
1685 (mnFlags == rColInfo.mnFlags) &&
1686 (mnOutlineLevel == rColInfo.mnOutlineLevel) &&
1687 (mnLastXclCol + 1 == rColInfo.mnFirstXclCol) )
1688 {
1689 mnLastXclCol = rColInfo.mnLastXclCol;
1690 return true;
1691 }
1692 return false;
1693 }
1694
WriteBody(XclExpStream & rStrm)1695 void XclExpColinfo::WriteBody( XclExpStream& rStrm )
1696 {
1697 // if last column is equal to last possible column, Excel adds one more
1698 sal_uInt16 nLastXclCol = mnLastXclCol;
1699 if( nLastXclCol == static_cast< sal_uInt16 >( rStrm.GetRoot().GetMaxPos().Col() ) )
1700 ++nLastXclCol;
1701
1702 rStrm << mnFirstXclCol
1703 << nLastXclCol
1704 << mnWidth
1705 << maXFId.mnXFIndex
1706 << mnFlags
1707 << sal_uInt16( 0 );
1708 }
1709
SaveXml(XclExpXmlStream & rStrm)1710 void XclExpColinfo::SaveXml( XclExpXmlStream& rStrm )
1711 {
1712 const double nExcelColumnWidth = mnScWidth / convertTwipToMm100<double>(GetCharWidth());
1713
1714 // tdf#101363 In MS specification the output value is set with double precision after delimiter:
1715 // =Truncate(({width in pixels} - 5)/{Maximum Digit Width} * 100 + 0.5)/100
1716 // Explanation of magic numbers:
1717 // 5 number - are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.
1718 // It is unknown if it should be applied during LibreOffice export
1719 // 100 number - used to limit precision to 0.01 with formula =Truncate( {value}*100+0.5 ) / 100
1720 // 0.5 number (0.005 to output value) - used to increase value before truncating,
1721 // to avoid situation when 2.997 will be truncated to 2.99 and not to 3.00
1722 const double nTruncatedExcelColumnWidth = std::trunc( nExcelColumnWidth * 100.0 + 0.5 ) / 100.0;
1723 rStrm.GetCurrentStream()->singleElement( XML_col,
1724 // OOXTODO: XML_bestFit,
1725 XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_COLLAPSED ) ),
1726 XML_customWidth, ToPsz( mbCustomWidth ),
1727 XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_HIDDEN ) ),
1728 XML_outlineLevel, OString::number(mnOutlineLevel),
1729 XML_max, OString::number(mnLastXclCol + 1),
1730 XML_min, OString::number(mnFirstXclCol + 1),
1731 // OOXTODO: XML_phonetic,
1732 XML_style, lcl_GetStyleId(rStrm, maXFId.mnXFIndex),
1733 XML_width, OString::number(nTruncatedExcelColumnWidth) );
1734 }
1735
XclExpColinfoBuffer(const XclExpRoot & rRoot)1736 XclExpColinfoBuffer::XclExpColinfoBuffer( const XclExpRoot& rRoot ) :
1737 XclExpRoot( rRoot ),
1738 maDefcolwidth( rRoot ),
1739 maOutlineBfr( rRoot ),
1740 mnHighestOutlineLevel( 0 )
1741 {
1742 }
1743
Initialize(SCROW nLastScRow)1744 void XclExpColinfoBuffer::Initialize( SCROW nLastScRow )
1745 {
1746
1747 for( sal_uInt16 nScCol = 0, nLastScCol = GetMaxPos().Col(); nScCol <= nLastScCol; ++nScCol )
1748 {
1749 maColInfos.AppendNewRecord( new XclExpColinfo( GetRoot(), nScCol, nLastScRow, maOutlineBfr ) );
1750 if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel )
1751 {
1752 mnHighestOutlineLevel = maOutlineBfr.GetLevel();
1753 }
1754 }
1755 }
1756
Finalize(ScfUInt16Vec & rXFIndexes,bool bXLS)1757 void XclExpColinfoBuffer::Finalize( ScfUInt16Vec& rXFIndexes, bool bXLS )
1758 {
1759 rXFIndexes.clear();
1760 rXFIndexes.reserve( maColInfos.GetSize() );
1761
1762 if( !maColInfos.IsEmpty())
1763 {
1764 XclExpColinfo* xPrevRec = maColInfos.GetRecord( 0 );
1765 xPrevRec->ConvertXFIndexes();
1766 for( size_t nPos = 1; nPos < maColInfos.GetSize(); ++nPos )
1767 {
1768 XclExpColinfo* xRec = maColInfos.GetRecord( nPos );
1769 xRec->ConvertXFIndexes();
1770
1771 // try to merge with previous record
1772 if( xPrevRec->TryMerge( *xRec ) )
1773 maColInfos.InvalidateRecord( nPos );
1774 else
1775 xPrevRec = xRec;
1776 }
1777 maColInfos.RemoveInvalidatedRecords();
1778 }
1779
1780 // put XF indexes into passed vector, collect use count of all different widths
1781 std::unordered_map< sal_uInt16, sal_uInt16 > aWidthMap;
1782 sal_uInt16 nMaxColCount = 0;
1783 sal_uInt16 nMaxUsedWidth = 0;
1784 for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos )
1785 {
1786 const XclExpColinfo* xRec = maColInfos.GetRecord( nPos );
1787 sal_uInt16 nColCount = xRec->GetColCount();
1788
1789 // add XF index to passed vector
1790 rXFIndexes.resize( rXFIndexes.size() + nColCount, xRec->GetXFIndex() );
1791
1792 // collect use count of column width
1793 sal_uInt16 nWidth = xRec->GetColWidth();
1794 sal_uInt16& rnMapCount = aWidthMap[ nWidth ];
1795 rnMapCount = rnMapCount + nColCount;
1796 if( rnMapCount > nMaxColCount )
1797 {
1798 nMaxColCount = rnMapCount;
1799 nMaxUsedWidth = nWidth;
1800 }
1801 }
1802 maDefcolwidth.SetDefWidth( nMaxUsedWidth, bXLS );
1803
1804 // remove all default COLINFO records
1805 for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos )
1806 {
1807 XclExpColinfo* xRec = maColInfos.GetRecord( nPos );
1808 if( xRec->IsDefault( maDefcolwidth ) )
1809 maColInfos.InvalidateRecord( nPos );
1810 }
1811 maColInfos.RemoveInvalidatedRecords();
1812 }
1813
Save(XclExpStream & rStrm)1814 void XclExpColinfoBuffer::Save( XclExpStream& rStrm )
1815 {
1816 // DEFCOLWIDTH
1817 maDefcolwidth.Save( rStrm );
1818 // COLINFO records
1819 maColInfos.Save( rStrm );
1820 }
1821
SaveXml(XclExpXmlStream & rStrm)1822 void XclExpColinfoBuffer::SaveXml( XclExpXmlStream& rStrm )
1823 {
1824 if( maColInfos.IsEmpty() )
1825 return;
1826
1827 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
1828 rWorksheet->startElement(XML_cols);
1829 maColInfos.SaveXml( rStrm );
1830 rWorksheet->endElement( XML_cols );
1831 }
1832
XclExpDefaultRowData()1833 XclExpDefaultRowData::XclExpDefaultRowData() :
1834 mnFlags( EXC_DEFROW_DEFAULTFLAGS ),
1835 mnHeight( EXC_DEFROW_DEFAULTHEIGHT )
1836 {
1837 }
1838
XclExpDefaultRowData(const XclExpRow & rRow)1839 XclExpDefaultRowData::XclExpDefaultRowData( const XclExpRow& rRow ) :
1840 mnFlags( EXC_DEFROW_DEFAULTFLAGS ),
1841 mnHeight( rRow.GetHeight() )
1842 {
1843 ::set_flag( mnFlags, EXC_DEFROW_HIDDEN, rRow.IsHidden() );
1844 ::set_flag( mnFlags, EXC_DEFROW_UNSYNCED, rRow.IsUnsynced() );
1845 }
1846
operator <(const XclExpDefaultRowData & rLeft,const XclExpDefaultRowData & rRight)1847 static bool operator<( const XclExpDefaultRowData& rLeft, const XclExpDefaultRowData& rRight )
1848 {
1849 return (rLeft.mnHeight < rRight.mnHeight) ||
1850 ((rLeft.mnHeight == rRight.mnHeight) && (rLeft.mnFlags < rRight.mnFlags));
1851 }
1852
XclExpDefrowheight()1853 XclExpDefrowheight::XclExpDefrowheight() :
1854 XclExpRecord( EXC_ID3_DEFROWHEIGHT, 4 )
1855 {
1856 }
1857
SetDefaultData(const XclExpDefaultRowData & rDefData)1858 void XclExpDefrowheight::SetDefaultData( const XclExpDefaultRowData& rDefData )
1859 {
1860 maDefData = rDefData;
1861 }
1862
WriteBody(XclExpStream & rStrm)1863 void XclExpDefrowheight::WriteBody( XclExpStream& rStrm )
1864 {
1865 OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 );
1866 rStrm << maDefData.mnFlags << maDefData.mnHeight;
1867 }
1868
XclExpRow(const XclExpRoot & rRoot,sal_uInt32 nXclRow,XclExpRowOutlineBuffer & rOutlineBfr,bool bAlwaysEmpty,bool bHidden,sal_uInt16 nHeight)1869 XclExpRow::XclExpRow( const XclExpRoot& rRoot, sal_uInt32 nXclRow,
1870 XclExpRowOutlineBuffer& rOutlineBfr, bool bAlwaysEmpty, bool bHidden, sal_uInt16 nHeight ) :
1871 XclExpRecord( EXC_ID3_ROW, 16 ),
1872 XclExpRoot( rRoot ),
1873 mnXclRow( nXclRow ),
1874 mnHeight( nHeight ),
1875 mnFlags( EXC_ROW_DEFAULTFLAGS ),
1876 mnXFIndex( EXC_XF_DEFAULTCELL ),
1877 mnOutlineLevel( 0 ),
1878 mnXclRowRpt( 1 ),
1879 mnCurrentRow( nXclRow ),
1880 mbAlwaysEmpty( bAlwaysEmpty ),
1881 mbEnabled( true )
1882 {
1883 SCTAB nScTab = GetCurrScTab();
1884 SCROW nScRow = static_cast< SCROW >( mnXclRow );
1885
1886 // *** Row flags *** ------------------------------------------------------
1887
1888 CRFlags nRowFlags = GetDoc().GetRowFlags( nScRow, nScTab );
1889 bool bUserHeight( nRowFlags & CRFlags::ManualSize );
1890 ::set_flag( mnFlags, EXC_ROW_UNSYNCED, bUserHeight );
1891 ::set_flag( mnFlags, EXC_ROW_HIDDEN, bHidden );
1892
1893 // *** Outline data *** ---------------------------------------------------
1894
1895 rOutlineBfr.Update( nScRow );
1896 ::set_flag( mnFlags, EXC_ROW_COLLAPSED, rOutlineBfr.IsCollapsed() );
1897 ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 0, 3 );
1898 mnOutlineLevel = rOutlineBfr.GetLevel();
1899
1900 // *** Progress bar *** ---------------------------------------------------
1901
1902 XclExpProgressBar& rProgress = GetProgressBar();
1903 rProgress.IncRowRecordCount();
1904 rProgress.Progress();
1905 }
1906
findFirstAllSameUntilEnd(const ScfUInt16Vec & rIndexes,sal_uInt16 value,size_t searchStart=std::numeric_limits<size_t>::max ())1907 static size_t findFirstAllSameUntilEnd( const ScfUInt16Vec& rIndexes, sal_uInt16 value,
1908 size_t searchStart = std::numeric_limits<size_t>::max())
1909 {
1910 for( size_t i = std::min(rIndexes.size(), searchStart); i >= 1; --i )
1911 {
1912 if( rIndexes[ i - 1 ] != value )
1913 return i;
1914 }
1915 return 0;
1916 }
1917
AppendCell(XclExpCellRef const & xCell,bool bIsMergedBase)1918 void XclExpRow::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase )
1919 {
1920 OSL_ENSURE( !mbAlwaysEmpty, "XclExpRow::AppendCell - row is marked to be always empty" );
1921 // try to merge with last existing cell
1922 InsertCell( xCell, maCellList.GetSize(), bIsMergedBase );
1923 }
1924
Finalize(const ScfUInt16Vec & rColXFIndexes,ScfUInt16Vec & aXFIndexes,size_t nStartColAllDefault,bool bProgress)1925 void XclExpRow::Finalize( const ScfUInt16Vec& rColXFIndexes, ScfUInt16Vec& aXFIndexes, size_t nStartColAllDefault, bool bProgress )
1926 {
1927 size_t nPos, nSize;
1928
1929 // *** Convert XF identifiers *** -----------------------------------------
1930
1931 // additionally collect the blank XF indexes
1932 size_t nColCount = GetMaxPos().Col() + 1;
1933 OSL_ENSURE( rColXFIndexes.size() == nColCount, "XclExpRow::Finalize - wrong column XF index count" );
1934
1935 // The vector should be preset to all items being EXC_XF_NOTFOUND, to avoid repeated allocations
1936 // and clearing.
1937 assert( aXFIndexes.size() == nColCount );
1938 assert( aXFIndexes.front() == EXC_XF_NOTFOUND );
1939 assert( aXFIndexes.back() == EXC_XF_NOTFOUND );
1940 for( nPos = 0, nSize = maCellList.GetSize(); nPos < nSize; ++nPos )
1941 {
1942 XclExpCellBase* pCell = maCellList.GetRecord( nPos );
1943 pCell->ConvertXFIndexes( GetRoot() );
1944 pCell->GetBlankXFIndexes( aXFIndexes );
1945 }
1946
1947 // *** Fill gaps with BLANK/MULBLANK cell records *** ---------------------
1948
1949 /* This is needed because nonexistent cells in Calc are not formatted at all,
1950 but in Excel they would have the column default format. Blank cells that
1951 are equal to the respective column default are removed later in this function. */
1952 if( !mbAlwaysEmpty )
1953 {
1954 // XF identifier representing default cell XF
1955 XclExpMultiXFId aXFId( XclExpXFBuffer::GetDefCellXFId() );
1956 aXFId.ConvertXFIndex( GetRoot() );
1957
1958 nPos = 0;
1959 while( nPos <= maCellList.GetSize() ) // don't cache list size, may change in the loop
1960 {
1961 // get column index that follows previous cell
1962 sal_uInt16 nFirstFreeXclCol = (nPos > 0) ? (maCellList.GetRecord( nPos - 1 )->GetLastXclCol() + 1) : 0;
1963 // get own column index
1964 sal_uInt16 nNextUsedXclCol = (nPos < maCellList.GetSize()) ? maCellList.GetRecord( nPos )->GetXclCol() : (GetMaxPos().Col() + 1);
1965
1966 // is there a gap?
1967 if( nFirstFreeXclCol < nNextUsedXclCol )
1968 {
1969 aXFId.mnCount = nNextUsedXclCol - nFirstFreeXclCol;
1970 XclExpCellRef xNewCell = new XclExpBlankCell( XclAddress( nFirstFreeXclCol, mnXclRow ), aXFId );
1971 // insert the cell, InsertCell() may merge it with existing BLANK records
1972 InsertCell( xNewCell, nPos, false );
1973 // insert default XF indexes into aXFIndexes
1974 for( size_t i = nFirstFreeXclCol; i < nNextUsedXclCol; ++i )
1975 aXFIndexes[ i ] = aXFId.mnXFIndex;
1976 // don't step forward with nPos, InsertCell() may remove records
1977 }
1978 else
1979 ++nPos;
1980 }
1981 }
1982
1983 // *** Find default row format *** ----------------------------------------
1984
1985 // Often there will be many EXC_XF_DEFAULTCELL at the end, optimize by ignoring them.
1986 size_t nStartSearchAllDefault = aXFIndexes.size();
1987 if( !maCellList.IsEmpty() && dynamic_cast< const XclExpBlankCell* >( maCellList.GetLastRecord()))
1988 {
1989 const XclExpBlankCell* pLastBlank = static_cast< const XclExpBlankCell* >( maCellList.GetLastRecord());
1990 assert(pLastBlank->GetLastXclCol() == aXFIndexes.size() - 1);
1991 nStartSearchAllDefault = pLastBlank->GetStartColAllDefaultCell();
1992 }
1993 size_t nStartAllDefault = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_DEFAULTCELL, nStartSearchAllDefault);
1994
1995 // find most used XF index in the row
1996 sal_uInt16 nRowXFIndex = EXC_XF_DEFAULTCELL;
1997 const size_t nHalfIndexes = aXFIndexes.size() / 2;
1998 if( nStartAllDefault > nHalfIndexes ) // Otherwise most are EXC_XF_DEFAULTCELL.
1999 {
2000 // Very likely the most common one is going to be the last one.
2001 nRowXFIndex = aXFIndexes.back();
2002 size_t nStartLastSame = findFirstAllSameUntilEnd( aXFIndexes, nRowXFIndex );
2003 if( nStartLastSame > nHalfIndexes ) // No, find out the most used one by counting.
2004 {
2005 std::unordered_map< sal_uInt16, size_t > aIndexMap;
2006 size_t nMaxXFCount = 0;
2007 for( const auto& rXFIndex : aXFIndexes )
2008 {
2009 if( rXFIndex != EXC_XF_NOTFOUND )
2010 {
2011 size_t& rnCount = aIndexMap[ rXFIndex ];
2012 ++rnCount;
2013 if( rnCount > nMaxXFCount )
2014 {
2015 nRowXFIndex = rXFIndex;
2016 nMaxXFCount = rnCount;
2017 if (nMaxXFCount > nHalfIndexes)
2018 {
2019 // No other XF index can have a greater usage count, we
2020 // don't need to loop through the remaining cells.
2021 // Specifically for the tail of unused default
2022 // cells/columns this makes a difference.
2023 break; // for
2024 }
2025 }
2026 }
2027 }
2028 }
2029 }
2030
2031 // decide whether to use the row default XF index or column default XF indexes
2032 bool bUseColDefXFs = nRowXFIndex == EXC_XF_DEFAULTCELL;
2033 if( !bUseColDefXFs )
2034 {
2035 // count needed XF indexes for blank cells with and without row default XF index
2036 size_t nXFCountWithRowDefXF = 0;
2037 size_t nXFCountWithoutRowDefXF = 0;
2038 ScfUInt16Vec::const_iterator aColIt = rColXFIndexes.begin();
2039 for( const auto& rXFIndex : aXFIndexes )
2040 {
2041 sal_uInt16 nXFIndex = rXFIndex;
2042 if( nXFIndex != EXC_XF_NOTFOUND )
2043 {
2044 if( nXFIndex != nRowXFIndex )
2045 ++nXFCountWithRowDefXF; // with row default XF index
2046 if( nXFIndex != *aColIt )
2047 ++nXFCountWithoutRowDefXF; // without row default XF index
2048 }
2049 ++aColIt;
2050 }
2051
2052 // use column XF indexes if this would cause less or equal number of BLANK records
2053 bUseColDefXFs = nXFCountWithoutRowDefXF <= nXFCountWithRowDefXF;
2054 }
2055
2056 // *** Remove unused BLANK cell records *** -------------------------------
2057
2058 size_t maxStartAllNotFound;
2059 if( bUseColDefXFs )
2060 {
2061 size_t maxStartAllDefault = std::max( nStartAllDefault, nStartColAllDefault );
2062 // use column default XF indexes
2063 // #i194#: remove cell XF indexes equal to column default XF indexes
2064 for( size_t i = 0; i < maxStartAllDefault; ++i )
2065 {
2066 if( aXFIndexes[ i ] == rColXFIndexes[ i ] )
2067 aXFIndexes[ i ] = EXC_XF_NOTFOUND;
2068 }
2069 // They can differ only up to maxNonDefault, in the rest they are the same.
2070 for( size_t i = maxStartAllDefault; i < aXFIndexes.size(); ++i )
2071 aXFIndexes[ i ] = EXC_XF_NOTFOUND;
2072 maxStartAllNotFound = maxStartAllDefault;
2073 }
2074 else
2075 {
2076 // use row default XF index
2077 mnXFIndex = nRowXFIndex;
2078 ::set_flag( mnFlags, EXC_ROW_USEDEFXF );
2079 // #98133#, #i194#, #i27407#: remove cell XF indexes equal to row default XF index
2080 for( auto& rXFIndex : aXFIndexes )
2081 if( rXFIndex == nRowXFIndex )
2082 rXFIndex = EXC_XF_NOTFOUND;
2083 maxStartAllNotFound = aXFIndexes.size();
2084 }
2085
2086 // remove unused parts of BLANK/MULBLANK cell records
2087 size_t nStartAllNotFound = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_NOTFOUND, maxStartAllNotFound );
2088 nPos = 0;
2089 while( nPos < maCellList.GetSize() ) // do not cache list size, may change in the loop
2090 {
2091 XclExpCellBase* xCell = maCellList.GetRecord( nPos );
2092 xCell->RemoveUnusedBlankCells( aXFIndexes, nStartAllNotFound );
2093 if( xCell->IsEmpty() )
2094 maCellList.RemoveRecord( nPos );
2095 else
2096 ++nPos;
2097 }
2098 // Ensure it's all EXC_XF_NOTFOUND again for next reuse.
2099 for( size_t i = 0; i < nStartAllNotFound; ++i )
2100 aXFIndexes[ i ] = EXC_XF_NOTFOUND;
2101
2102 // progress bar includes disabled rows; only update it in the lead thread.
2103 if (bProgress)
2104 GetProgressBar().Progress();
2105 }
GetFirstUsedXclCol() const2106 sal_uInt16 XclExpRow::GetFirstUsedXclCol() const
2107 {
2108 return maCellList.IsEmpty() ? 0 : maCellList.GetFirstRecord()->GetXclCol();
2109 }
2110
GetFirstFreeXclCol() const2111 sal_uInt16 XclExpRow::GetFirstFreeXclCol() const
2112 {
2113 return maCellList.IsEmpty() ? 0 : (maCellList.GetLastRecord()->GetLastXclCol() + 1);
2114 }
2115
IsDefaultable() const2116 bool XclExpRow::IsDefaultable() const
2117 {
2118 const sal_uInt16 nFlagsAlwaysMarkedAsDefault = EXC_ROW_DEFAULTFLAGS | EXC_ROW_HIDDEN | EXC_ROW_UNSYNCED;
2119 return !::get_flag( mnFlags, static_cast< sal_uInt16 >( ~nFlagsAlwaysMarkedAsDefault ) ) &&
2120 IsEmpty();
2121 }
2122
DisableIfDefault(const XclExpDefaultRowData & rDefRowData)2123 void XclExpRow::DisableIfDefault( const XclExpDefaultRowData& rDefRowData )
2124 {
2125 mbEnabled = !IsDefaultable() ||
2126 (mnHeight != rDefRowData.mnHeight) ||
2127 (IsHidden() != rDefRowData.IsHidden()) ||
2128 (IsUnsynced() != rDefRowData.IsUnsynced());
2129 }
2130
WriteCellList(XclExpStream & rStrm)2131 void XclExpRow::WriteCellList( XclExpStream& rStrm )
2132 {
2133 OSL_ENSURE( mbEnabled || maCellList.IsEmpty(), "XclExpRow::WriteCellList - cells in disabled row" );
2134 maCellList.Save( rStrm );
2135 }
2136
Save(XclExpStream & rStrm)2137 void XclExpRow::Save( XclExpStream& rStrm )
2138 {
2139 if( mbEnabled )
2140 {
2141 mnCurrentRow = mnXclRow;
2142 for ( sal_uInt32 i = 0; i < mnXclRowRpt; ++i, ++mnCurrentRow )
2143 XclExpRecord::Save( rStrm );
2144 }
2145 }
2146
InsertCell(XclExpCellRef xCell,size_t nPos,bool bIsMergedBase)2147 void XclExpRow::InsertCell( XclExpCellRef xCell, size_t nPos, bool bIsMergedBase )
2148 {
2149 OSL_ENSURE( xCell, "XclExpRow::InsertCell - missing cell" );
2150
2151 /* If we have a multi-line text in a merged cell, and the resulting
2152 row height has not been confirmed, we need to force the EXC_ROW_UNSYNCED
2153 flag to be true to ensure Excel works correctly. */
2154 if( bIsMergedBase && xCell->IsMultiLineText() )
2155 ::set_flag( mnFlags, EXC_ROW_UNSYNCED );
2156
2157 // try to merge with previous cell, insert the new cell if not successful
2158 XclExpCellBase* xPrevCell = maCellList.GetRecord( nPos - 1 );
2159 if( xPrevCell && xPrevCell->TryMerge( *xCell ) )
2160 xCell = xPrevCell;
2161 else
2162 maCellList.InsertRecord( xCell, nPos++ );
2163 // nPos points now to following cell
2164
2165 // try to merge with following cell, remove it if successful
2166 XclExpCellRef xNextCell = maCellList.GetRecord( nPos );
2167 if( xNextCell && xCell->TryMerge( *xNextCell ) )
2168 maCellList.RemoveRecord( nPos );
2169 }
2170
WriteBody(XclExpStream & rStrm)2171 void XclExpRow::WriteBody( XclExpStream& rStrm )
2172 {
2173 rStrm << static_cast< sal_uInt16 >(mnCurrentRow)
2174 << GetFirstUsedXclCol()
2175 << GetFirstFreeXclCol()
2176 << mnHeight
2177 << sal_uInt32( 0 )
2178 << mnFlags
2179 << mnXFIndex;
2180 }
2181
SaveXml(XclExpXmlStream & rStrm)2182 void XclExpRow::SaveXml( XclExpXmlStream& rStrm )
2183 {
2184 if( !mbEnabled )
2185 return;
2186 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2187 bool haveFormat = ::get_flag( mnFlags, EXC_ROW_USEDEFXF );
2188 mnCurrentRow = mnXclRow + 1;
2189 for ( sal_uInt32 i=0; i<mnXclRowRpt; ++i )
2190 {
2191 rWorksheet->startElement( XML_row,
2192 XML_r, OString::number(mnCurrentRow++),
2193 // OOXTODO: XML_spans, optional
2194 XML_s, haveFormat ? lcl_GetStyleId( rStrm, mnXFIndex ).getStr() : nullptr,
2195 XML_customFormat, ToPsz( haveFormat ),
2196 XML_ht, OString::number(static_cast<double>(mnHeight) / 20.0),
2197 XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_ROW_HIDDEN ) ),
2198 XML_customHeight, ToPsz( ::get_flag( mnFlags, EXC_ROW_UNSYNCED ) ),
2199 XML_outlineLevel, OString::number(mnOutlineLevel),
2200 XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_ROW_COLLAPSED ) )
2201 // OOXTODO: XML_thickTop, bool
2202 // OOXTODO: XML_thickBot, bool
2203 // OOXTODO: XML_ph, bool
2204 );
2205 // OOXTODO: XML_extLst
2206 maCellList.SaveXml( rStrm );
2207 rWorksheet->endElement( XML_row );
2208 }
2209 }
2210
XclExpRowBuffer(const XclExpRoot & rRoot)2211 XclExpRowBuffer::XclExpRowBuffer( const XclExpRoot& rRoot ) :
2212 XclExpRoot( rRoot ),
2213 maOutlineBfr( rRoot ),
2214 maDimensions( rRoot ),
2215 mnHighestOutlineLevel( 0 )
2216 {
2217 }
2218
AppendCell(XclExpCellRef const & xCell,bool bIsMergedBase)2219 void XclExpRowBuffer::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase )
2220 {
2221 OSL_ENSURE( xCell, "XclExpRowBuffer::AppendCell - missing cell" );
2222 GetOrCreateRow( xCell->GetXclRow(), false ).AppendCell( xCell, bIsMergedBase );
2223 }
2224
CreateRows(SCROW nFirstFreeScRow)2225 void XclExpRowBuffer::CreateRows( SCROW nFirstFreeScRow )
2226 {
2227 if( nFirstFreeScRow > 0 )
2228 GetOrCreateRow( ::std::max ( nFirstFreeScRow - 1, GetMaxPos().Row() ), true );
2229 }
2230
2231 namespace {
2232
2233 class RowFinalizeTask : public comphelper::ThreadTask
2234 {
2235 bool mbProgress;
2236 const ScfUInt16Vec& mrColXFIndexes;
2237 size_t mnStartColAllDefault;
2238 std::vector< XclExpRow * > maRows;
2239 public:
RowFinalizeTask(const std::shared_ptr<comphelper::ThreadTaskTag> & pTag,const ScfUInt16Vec & rColXFIndexes,size_t nStartColAllDefault,bool bProgress)2240 RowFinalizeTask( const std::shared_ptr<comphelper::ThreadTaskTag> & pTag,
2241 const ScfUInt16Vec& rColXFIndexes,
2242 size_t nStartColAllDefault,
2243 bool bProgress ) :
2244 comphelper::ThreadTask( pTag ),
2245 mbProgress( bProgress ),
2246 mrColXFIndexes( rColXFIndexes ),
2247 mnStartColAllDefault( nStartColAllDefault )
2248 {}
2249
push_back(XclExpRow * pRow)2250 void push_back( XclExpRow *pRow ) { maRows.push_back( pRow ); }
doWork()2251 virtual void doWork() override
2252 {
2253 ScfUInt16Vec aXFIndexes( mrColXFIndexes.size(), EXC_XF_NOTFOUND );
2254 for (XclExpRow* p : maRows)
2255 p->Finalize( mrColXFIndexes, aXFIndexes, mnStartColAllDefault, mbProgress );
2256 }
2257 };
2258
2259 }
2260
Finalize(XclExpDefaultRowData & rDefRowData,const ScfUInt16Vec & rColXFIndexes,size_t nStartColAllDefault)2261 void XclExpRowBuffer::Finalize( XclExpDefaultRowData& rDefRowData,
2262 const ScfUInt16Vec& rColXFIndexes,
2263 size_t nStartColAllDefault )
2264 {
2265 // *** Finalize all rows *** ----------------------------------------------
2266
2267 GetProgressBar().ActivateFinalRowsSegment();
2268
2269 #if 1
2270 // This is staggeringly slow, and each element operates only
2271 // on its own data.
2272 const size_t nRows = maRowMap.size();
2273 const size_t nThreads = nRows < 128 ? 1 : comphelper::ThreadPool::getPreferredConcurrency();
2274 #else
2275 const size_t nThreads = 1; // globally disable multi-threading for now.
2276 #endif
2277 if (nThreads == 1)
2278 {
2279 ScfUInt16Vec aXFIndexes( rColXFIndexes.size(), EXC_XF_NOTFOUND );
2280 for (auto& rEntry : maRowMap)
2281 rEntry.second->Finalize( rColXFIndexes, aXFIndexes, nStartColAllDefault, true );
2282 }
2283 else
2284 {
2285 comphelper::ThreadPool &rPool = comphelper::ThreadPool::getSharedOptimalPool();
2286 std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
2287 std::vector<std::unique_ptr<RowFinalizeTask>> aTasks(nThreads);
2288 for ( size_t i = 0; i < nThreads; i++ )
2289 aTasks[ i ].reset( new RowFinalizeTask( pTag, rColXFIndexes, nStartColAllDefault, i == 0 ) );
2290
2291 size_t nIdx = 0;
2292 for ( const auto& rEntry : maRowMap )
2293 {
2294 aTasks[ nIdx % nThreads ]->push_back( rEntry.second.get() );
2295 ++nIdx;
2296 }
2297
2298 for ( size_t i = 1; i < nThreads; i++ )
2299 rPool.pushTask( std::move(aTasks[ i ]) );
2300
2301 // Progress bar updates must be synchronous to avoid deadlock
2302 aTasks[0]->doWork();
2303
2304 rPool.waitUntilDone(pTag);
2305 }
2306
2307 // *** Default row format *** ---------------------------------------------
2308
2309 std::map< XclExpDefaultRowData, size_t > aDefRowMap;
2310
2311 XclExpDefaultRowData aMaxDefData;
2312 size_t nMaxDefCount = 0;
2313 // only look for default format in existing rows, if there are more than unused
2314 // if the row is hidden, then row xml must be created even if it not contain cells
2315 XclExpRow* pPrev = nullptr;
2316 std::vector< XclExpRow* > aRepeated;
2317 for (const auto& rEntry : maRowMap)
2318 {
2319 const RowRef& rRow = rEntry.second;
2320 if ( rRow->IsDefaultable() )
2321 {
2322 XclExpDefaultRowData aDefData( *rRow );
2323 size_t& rnDefCount = aDefRowMap[ aDefData ];
2324 ++rnDefCount;
2325 if( rnDefCount > nMaxDefCount )
2326 {
2327 nMaxDefCount = rnDefCount;
2328 aMaxDefData = aDefData;
2329 }
2330 }
2331 if ( pPrev )
2332 {
2333 if ( pPrev->IsDefaultable() )
2334 {
2335 // if the previous row we processed is not
2336 // defaultable then afaict the rows in between are
2337 // not used ( and not repeatable )
2338 sal_uInt32 nRpt = rRow->GetXclRow() - pPrev->GetXclRow();
2339 if ( nRpt > 1 )
2340 aRepeated.push_back( pPrev );
2341 pPrev->SetXclRowRpt( nRpt );
2342 XclExpDefaultRowData aDefData( *pPrev );
2343 size_t& rnDefCount = aDefRowMap[ aDefData ];
2344 rnDefCount += ( pPrev->GetXclRowRpt() - 1 );
2345 if( rnDefCount > nMaxDefCount )
2346 {
2347 nMaxDefCount = rnDefCount;
2348 aMaxDefData = aDefData;
2349 }
2350 }
2351 }
2352 pPrev = rRow.get();
2353 }
2354 // return the default row format to caller
2355 rDefRowData = aMaxDefData;
2356
2357 // now disable repeating extra (empty) rows that are equal to the default row
2358 for (auto& rpRow : aRepeated)
2359 {
2360 if ( rpRow->GetXclRowRpt() > 1
2361 && rpRow->GetHeight() == rDefRowData.mnHeight
2362 && rpRow->IsHidden() == rDefRowData.IsHidden() )
2363 {
2364 rpRow->SetXclRowRpt( 1 );
2365 }
2366 }
2367
2368 // *** Disable unused ROW records, find used area *** ---------------------
2369
2370 sal_uInt16 nFirstUsedXclCol = SAL_MAX_UINT16;
2371 sal_uInt16 nFirstFreeXclCol = 0;
2372 sal_uInt32 nFirstUsedXclRow = SAL_MAX_UINT32;
2373 sal_uInt32 nFirstFreeXclRow = 0;
2374
2375 for (const auto& rEntry : maRowMap)
2376 {
2377 const RowRef& rRow = rEntry.second;
2378 // disable unused rows
2379 rRow->DisableIfDefault( aMaxDefData );
2380
2381 // find used column range
2382 if( !rRow->IsEmpty() ) // empty rows return (0...0) as used range
2383 {
2384 nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, rRow->GetFirstUsedXclCol() );
2385 nFirstFreeXclCol = ::std::max( nFirstFreeXclCol, rRow->GetFirstFreeXclCol() );
2386 }
2387
2388 // find used row range
2389 if( rRow->IsEnabled() )
2390 {
2391 sal_uInt32 nXclRow = rRow->GetXclRow();
2392 nFirstUsedXclRow = ::std::min< sal_uInt32 >( nFirstUsedXclRow, nXclRow );
2393 nFirstFreeXclRow = ::std::max< sal_uInt32 >( nFirstFreeXclRow, nXclRow + 1 );
2394 }
2395 }
2396
2397 // adjust start position, if there are no or only empty/disabled ROW records
2398 nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, nFirstFreeXclCol );
2399 nFirstUsedXclRow = ::std::min( nFirstUsedXclRow, nFirstFreeXclRow );
2400
2401 // initialize the DIMENSIONS record
2402 maDimensions.SetDimensions(
2403 nFirstUsedXclCol, nFirstUsedXclRow, nFirstFreeXclCol, nFirstFreeXclRow );
2404 }
2405
Save(XclExpStream & rStrm)2406 void XclExpRowBuffer::Save( XclExpStream& rStrm )
2407 {
2408 // DIMENSIONS record
2409 maDimensions.Save( rStrm );
2410
2411 // save in blocks of 32 rows, each block contains first all ROWs, then all cells
2412 size_t nSize = maRowMap.size();
2413 RowMap::iterator itr = maRowMap.begin(), itrEnd = maRowMap.end();
2414 RowMap::iterator itrBlkStart = maRowMap.begin(), itrBlkEnd = maRowMap.begin();
2415 sal_uInt16 nStartXclRow = (nSize == 0) ? 0 : itr->second->GetXclRow();
2416
2417 for (; itr != itrEnd; ++itr)
2418 {
2419 // find end of row block
2420 itrBlkEnd = std::find_if_not(itrBlkEnd, itrEnd,
2421 [&nStartXclRow](const RowMap::value_type& rRow) { return rRow.second->GetXclRow() - nStartXclRow < EXC_ROW_ROWBLOCKSIZE; });
2422
2423 // write the ROW records
2424 std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->Save( rStrm ); });
2425
2426 // write the cell records
2427 std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->WriteCellList( rStrm ); });
2428
2429 itrBlkStart = (itrBlkEnd == itrEnd) ? itrBlkEnd : itrBlkEnd++;
2430 nStartXclRow += EXC_ROW_ROWBLOCKSIZE;
2431 }
2432 }
2433
SaveXml(XclExpXmlStream & rStrm)2434 void XclExpRowBuffer::SaveXml( XclExpXmlStream& rStrm )
2435 {
2436 if (std::none_of(maRowMap.begin(), maRowMap.end(), [](const RowMap::value_type& rRow) { return rRow.second->IsEnabled(); }))
2437 {
2438 rStrm.GetCurrentStream()->singleElement(XML_sheetData);
2439 return;
2440 }
2441
2442 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2443 rWorksheet->startElement(XML_sheetData);
2444 for (const auto& rEntry : maRowMap)
2445 rEntry.second->SaveXml(rStrm);
2446 rWorksheet->endElement( XML_sheetData );
2447 }
2448
GetOrCreateRow(sal_uInt32 nXclRow,bool bRowAlwaysEmpty)2449 XclExpRow& XclExpRowBuffer::GetOrCreateRow( sal_uInt32 nXclRow, bool bRowAlwaysEmpty )
2450 {
2451 // This is called rather often, so optimize for the most common case of saving row by row
2452 // (so the requested row is often the last one in the map or belongs after the last one).
2453 RowMap::iterator itr;
2454 if(maRowMap.empty())
2455 itr = maRowMap.end();
2456 else
2457 {
2458 RowMap::reverse_iterator last = maRowMap.rbegin();
2459 if( last->first == nXclRow )
2460 return *last->second;
2461 if( nXclRow > last->first )
2462 itr = maRowMap.end();
2463 else
2464 itr = maRowMap.lower_bound( nXclRow );
2465 }
2466 const bool bFound = itr != maRowMap.end();
2467 // bFoundHigher: nXclRow was identical to the previous entry, so not explicitly created earlier
2468 // coverity[deref_iterator : FALSE] - clearly itr if only derefed if bFound which checks for valid itr
2469 const bool bFoundHigher = bFound && itr->first != nXclRow;
2470 if( bFound && !bFoundHigher )
2471 return *itr->second;
2472
2473 size_t nFrom = 0;
2474 RowRef pPrevEntry;
2475 if( itr != maRowMap.begin() )
2476 {
2477 --itr;
2478 pPrevEntry = itr->second;
2479 if( bFoundHigher )
2480 nFrom = nXclRow;
2481 else
2482 nFrom = itr->first + 1;
2483 }
2484
2485 const ScDocument& rDoc = GetRoot().GetDoc();
2486 const SCTAB nScTab = GetRoot().GetCurrScTab();
2487 // Do not repeatedly call RowHidden() / GetRowHeight() for same values.
2488 bool bHidden = false;
2489 SCROW lastSameHiddenRow = -1;
2490 sal_uInt16 nHeight = 0;
2491 SCROW lastSameHeightRow = -1;
2492 // create the missing rows first
2493 while( nFrom <= nXclRow )
2494 {
2495 // only create RowMap entries if it is first row in spreadsheet,
2496 // if it is the desired row, or for rows that differ from previous.
2497 if( static_cast<SCROW>(nFrom) > lastSameHiddenRow )
2498 bHidden = rDoc.RowHidden(nFrom, nScTab, nullptr, &lastSameHiddenRow);
2499 // Always get the actual row height even if the manual size flag is
2500 // not set, to correctly export the heights of rows with wrapped
2501 // texts.
2502 if( static_cast<SCROW>(nFrom) > lastSameHeightRow )
2503 nHeight = rDoc.GetRowHeight(nFrom, nScTab, nullptr, &lastSameHeightRow, false);
2504 if ( !pPrevEntry || ( nFrom == nXclRow ) ||
2505 ( maOutlineBfr.IsCollapsed() ) ||
2506 ( maOutlineBfr.GetLevel() != 0 ) ||
2507 ( bRowAlwaysEmpty && !pPrevEntry->IsEmpty() ) ||
2508 ( bHidden != pPrevEntry->IsHidden() ) ||
2509 ( nHeight != pPrevEntry->GetHeight() ) )
2510 {
2511 if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel )
2512 {
2513 mnHighestOutlineLevel = maOutlineBfr.GetLevel();
2514 }
2515 RowRef p = std::make_shared<XclExpRow>(GetRoot(), nFrom, maOutlineBfr, bRowAlwaysEmpty, bHidden, nHeight);
2516 maRowMap.emplace(nFrom, p);
2517 pPrevEntry = std::move(p);
2518 }
2519 ++nFrom;
2520 }
2521 itr = maRowMap.find(nXclRow);
2522 return *itr->second;
2523 }
2524
2525 // Cell Table
2526
XclExpCellTable(const XclExpRoot & rRoot)2527 XclExpCellTable::XclExpCellTable( const XclExpRoot& rRoot ) :
2528 XclExpRoot( rRoot ),
2529 maColInfoBfr( rRoot ),
2530 maRowBfr( rRoot ),
2531 maArrayBfr( rRoot ),
2532 maShrfmlaBfr( rRoot ),
2533 maTableopBfr( rRoot ),
2534 mxDefrowheight( new XclExpDefrowheight() ),
2535 mxGuts( new XclExpGuts( rRoot ) ),
2536 mxNoteList( new XclExpNoteList ),
2537 mxMergedcells( new XclExpMergedcells( rRoot ) ),
2538 mxHyperlinkList( new XclExpHyperlinkList ),
2539 mxDval( new XclExpDval( rRoot ) ),
2540 mxExtLst( new XclExtLst( rRoot ) )
2541 {
2542 ScDocument& rDoc = GetDoc();
2543 SCTAB nScTab = GetCurrScTab();
2544 SvNumberFormatter& rFormatter = GetFormatter();
2545
2546 // maximum sheet limits
2547 SCCOL nMaxScCol = GetMaxPos().Col();
2548 SCROW nMaxScRow = GetMaxPos().Row();
2549
2550 // find used area (non-empty cells)
2551 SCCOL nLastUsedScCol;
2552 SCROW nLastUsedScRow;
2553 rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow );
2554
2555 if(nLastUsedScCol > nMaxScCol)
2556 nLastUsedScCol = nMaxScCol;
2557
2558 // check extra blank rows to avoid of losing their not default settings (workaround for tdf#41425)
2559 nLastUsedScRow += 1000;
2560
2561 if(nLastUsedScRow > nMaxScRow)
2562 nLastUsedScRow = nMaxScRow;
2563
2564 ScRange aUsedRange( 0, 0, nScTab, nLastUsedScCol, nLastUsedScRow, nScTab );
2565 GetAddressConverter().ValidateRange( aUsedRange, true );
2566 nLastUsedScRow = aUsedRange.aEnd.Row();
2567
2568 // first row without any set attributes (height/hidden/...)
2569 SCROW nFirstUnflaggedScRow = rDoc.GetLastFlaggedRow( nScTab ) + 1;
2570
2571 // find range of outlines
2572 SCROW nFirstUngroupedScRow = 0;
2573 if( const ScOutlineTable* pOutlineTable = rDoc.GetOutlineTable( nScTab ) )
2574 {
2575 SCCOLROW nScStartPos, nScEndPos;
2576 const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
2577 rRowArray.GetRange( nScStartPos, nScEndPos );
2578 // +1 because open/close button is in next row in Excel, +1 for "end->first unused"
2579 nFirstUngroupedScRow = static_cast< SCROW >( nScEndPos + 2 );
2580 }
2581
2582 // column settings
2583 /* #i30411# Files saved with SO7/OOo1.x with nonstandard default column
2584 formatting cause big Excel files, because all rows from row 1 to row
2585 32000 are exported. Now, if the used area goes exactly to row 32000,
2586 use this row as default and ignore all rows >32000.
2587 #i59220# Tolerance of +-128 rows for inserted/removed rows. */
2588 if( (31871 <= nLastUsedScRow) && (nLastUsedScRow <= 32127) && (nFirstUnflaggedScRow < nLastUsedScRow) && (nFirstUngroupedScRow <= nLastUsedScRow) )
2589 nMaxScRow = nLastUsedScRow;
2590 maColInfoBfr.Initialize( nMaxScRow );
2591
2592 // range for cell iterator
2593 SCCOL nLastIterScCol = nMaxScCol;
2594 SCROW nLastIterScRow = ulimit_cast< SCROW >( nLastUsedScRow, nMaxScRow );
2595 ScUsedAreaIterator aIt( rDoc, nScTab, 0, 0, nLastIterScCol, nLastIterScRow );
2596
2597 // activate the correct segment and sub segment at the progress bar
2598 GetProgressBar().ActivateCreateRowsSegment();
2599
2600 for( bool bIt = aIt.GetNext(); bIt; bIt = aIt.GetNext() )
2601 {
2602 SCCOL nScCol = aIt.GetStartCol();
2603 SCROW nScRow = aIt.GetRow();
2604 SCCOL nLastScCol = aIt.GetEndCol();
2605 ScAddress aScPos( nScCol, nScRow, nScTab );
2606
2607 XclAddress aXclPos( static_cast< sal_uInt16 >( nScCol ), static_cast< sal_uInt32 >( nScRow ) );
2608 sal_uInt16 nLastXclCol = static_cast< sal_uInt16 >( nLastScCol );
2609
2610 const ScRefCellValue& rScCell = aIt.GetCell();
2611 XclExpCellRef xCell;
2612
2613 const ScPatternAttr* pPattern = aIt.GetPattern();
2614
2615 // handle overlapped merged cells before creating the cell record
2616 sal_uInt32 nMergeBaseXFId = EXC_XFID_NOTFOUND;
2617 bool bIsMergedBase = false;
2618 if( pPattern )
2619 {
2620 const SfxItemSet& rItemSet = pPattern->GetItemSet();
2621 // base cell in a merged range
2622 const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE );
2623 bIsMergedBase = rMergeItem.IsMerged();
2624 /* overlapped cell in a merged range; in Excel all merged cells
2625 must contain same XF index, for correct border */
2626 const ScMergeFlagAttr& rMergeFlagItem = rItemSet.Get( ATTR_MERGE_FLAG );
2627 if( rMergeFlagItem.IsOverlapped() )
2628 nMergeBaseXFId = mxMergedcells->GetBaseXFId( aScPos );
2629 }
2630
2631 OUString aAddNoteText; // additional text to be appended to a note
2632
2633 switch (rScCell.getType())
2634 {
2635 case CELLTYPE_VALUE:
2636 {
2637 double fValue = rScCell.getDouble();
2638
2639 if (pPattern)
2640 {
2641 OUString aUrl = pPattern->GetItem(ATTR_HYPERLINK).GetValue();
2642 if (!aUrl.isEmpty())
2643 {
2644 rtl::Reference<XclExpHyperlink> aLink =
2645 new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos);
2646 mxHyperlinkList->AppendRecord(aLink);
2647 }
2648 }
2649
2650 // try to create a Boolean cell
2651 if( pPattern && ((fValue == 0.0) || (fValue == 1.0)) )
2652 {
2653 sal_uInt32 nScNumFmt = pPattern->GetItem( ATTR_VALUE_FORMAT ).GetValue();
2654 if( rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL )
2655 xCell = new XclExpBooleanCell(
2656 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue != 0.0 );
2657 }
2658
2659 // try to create an RK value (compressed floating-point number)
2660 sal_Int32 nRkValue;
2661 if( !xCell && XclTools::GetRKFromDouble( nRkValue, fValue ) )
2662 xCell = new XclExpRkCell(
2663 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, nRkValue );
2664
2665 // else: simple floating-point number cell
2666 if( !xCell )
2667 xCell = new XclExpNumberCell(
2668 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue );
2669 }
2670 break;
2671
2672 case CELLTYPE_STRING:
2673 {
2674 xCell = new XclExpLabelCell(
2675 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.getSharedString()->getString());
2676 }
2677 break;
2678
2679 case CELLTYPE_EDIT:
2680 {
2681 XclExpHyperlinkHelper aLinkHelper( GetRoot(), aScPos );
2682 xCell = new XclExpLabelCell(
2683 GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.getEditText(), aLinkHelper);
2684
2685 // add a single created HLINK record to the record list
2686 if( aLinkHelper.HasLinkRecord() )
2687 mxHyperlinkList->AppendRecord( aLinkHelper.GetLinkRecord() );
2688 // add list of multiple URLs to the additional cell note text
2689 if( aLinkHelper.HasMultipleUrls() )
2690 aAddNoteText = ScGlobal::addToken( aAddNoteText, aLinkHelper.GetUrlList(), '\n', 2 );
2691 }
2692 break;
2693
2694 case CELLTYPE_FORMULA:
2695 {
2696 if (pPattern)
2697 {
2698 OUString aUrl = pPattern->GetItem(ATTR_HYPERLINK).GetValue();
2699 if (!aUrl.isEmpty())
2700 {
2701 rtl::Reference<XclExpHyperlink> aLink =
2702 new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos);
2703 mxHyperlinkList->AppendRecord(aLink);
2704 }
2705 }
2706
2707 xCell = new XclExpFormulaCell(
2708 GetRoot(), aXclPos, pPattern, nMergeBaseXFId,
2709 *rScCell.getFormula(), maArrayBfr, maShrfmlaBfr, maTableopBfr);
2710 }
2711 break;
2712
2713 default:
2714 OSL_FAIL( "XclExpCellTable::XclExpCellTable - unknown cell type" );
2715 [[fallthrough]];
2716 case CELLTYPE_NONE:
2717 {
2718 xCell = new XclExpBlankCell(
2719 GetRoot(), aXclPos, nLastXclCol, pPattern, nMergeBaseXFId );
2720 }
2721 break;
2722 }
2723
2724 assert(xCell && "can only reach here with xCell set");
2725
2726 // insert the cell into the current row
2727 maRowBfr.AppendCell( xCell, bIsMergedBase );
2728
2729 if ( !aAddNoteText.isEmpty() )
2730 mxNoteList->AppendNewRecord( new XclExpNote( GetRoot(), aScPos, nullptr, aAddNoteText ) );
2731
2732 // other sheet contents
2733 if( pPattern )
2734 {
2735 const SfxItemSet& rItemSet = pPattern->GetItemSet();
2736
2737 // base cell in a merged range
2738 if( bIsMergedBase )
2739 {
2740 const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE );
2741 ScRange aScRange( aScPos );
2742 aScRange.aEnd.IncCol( rMergeItem.GetColMerge() - 1 );
2743 aScRange.aEnd.IncRow( rMergeItem.GetRowMerge() - 1 );
2744 sal_uInt32 nXFId = xCell->GetFirstXFId();
2745 // blank cells merged vertically may occur repeatedly
2746 OSL_ENSURE( (aScRange.aStart.Col() == aScRange.aEnd.Col()) || (nScCol == nLastScCol),
2747 "XclExpCellTable::XclExpCellTable - invalid repeated blank merged cell" );
2748 for( SCCOL nIndex = nScCol; nIndex <= nLastScCol; ++nIndex )
2749 {
2750 mxMergedcells->AppendRange( aScRange, nXFId );
2751 aScRange.aStart.IncCol();
2752 aScRange.aEnd.IncCol();
2753 }
2754 }
2755
2756 // data validation
2757 if( ScfTools::CheckItem( rItemSet, ATTR_VALIDDATA, false ) )
2758 {
2759 sal_uInt32 nScHandle = rItemSet.Get( ATTR_VALIDDATA ).GetValue();
2760 ScRange aScRange( aScPos );
2761 aScRange.aEnd.SetCol( nLastScCol );
2762 mxDval->InsertCellRange( aScRange, nScHandle );
2763 }
2764 }
2765 }
2766
2767 // create missing row settings for rows anyhow flagged or with outlines
2768 maRowBfr.CreateRows( ::std::max( nFirstUnflaggedScRow, nFirstUngroupedScRow ) );
2769 }
2770
Finalize(bool bXLS)2771 void XclExpCellTable::Finalize(bool bXLS)
2772 {
2773 // Finalize multiple operations.
2774 maTableopBfr.Finalize();
2775
2776 /* Finalize column buffer. This calculates column default XF indexes from
2777 the XF identifiers and fills a vector with these XF indexes. */
2778 ScfUInt16Vec aColXFIndexes;
2779 maColInfoBfr.Finalize( aColXFIndexes, bXLS );
2780
2781 // Usually many indexes towards the end will be EXC_XF_DEFAULTCELL, find
2782 // the index that starts all EXC_XF_DEFAULTCELL until the end.
2783 size_t nStartColAllDefault = findFirstAllSameUntilEnd( aColXFIndexes, EXC_XF_DEFAULTCELL );
2784
2785 /* Finalize row buffer. This calculates all cell XF indexes from the XF
2786 identifiers. Then the XF index vector aColXFIndexes (filled above) is
2787 used to calculate the row default formats. With this, all unneeded blank
2788 cell records (equal to row default or column default) will be removed.
2789 The function returns the (most used) default row format in aDefRowData. */
2790 XclExpDefaultRowData aDefRowData;
2791 maRowBfr.Finalize( aDefRowData, aColXFIndexes, nStartColAllDefault );
2792
2793 // Initialize the DEFROWHEIGHT record.
2794 mxDefrowheight->SetDefaultData( aDefRowData );
2795 }
2796
CreateRecord(sal_uInt16 nRecId) const2797 XclExpRecordRef XclExpCellTable::CreateRecord( sal_uInt16 nRecId ) const
2798 {
2799 XclExpRecordRef xRec;
2800 switch( nRecId )
2801 {
2802 case EXC_ID3_DIMENSIONS: xRec = new XclExpDelegatingRecord( &const_cast<XclExpRowBuffer*>(&maRowBfr)->GetDimensions() ); break;
2803 case EXC_ID2_DEFROWHEIGHT: xRec = mxDefrowheight; break;
2804 case EXC_ID_GUTS: xRec = mxGuts; break;
2805 case EXC_ID_NOTE: xRec = mxNoteList; break;
2806 case EXC_ID_MERGEDCELLS: xRec = mxMergedcells; break;
2807 case EXC_ID_HLINK: xRec = mxHyperlinkList; break;
2808 case EXC_ID_DVAL: xRec = mxDval; break;
2809 case EXC_ID_EXTLST: xRec = mxExtLst; break;
2810 default: OSL_FAIL( "XclExpCellTable::CreateRecord - unknown record id" );
2811 }
2812 return xRec;
2813 }
2814
Save(XclExpStream & rStrm)2815 void XclExpCellTable::Save( XclExpStream& rStrm )
2816 {
2817 // DEFCOLWIDTH and COLINFOs
2818 maColInfoBfr.Save( rStrm );
2819 // ROWs and cell records
2820 maRowBfr.Save( rStrm );
2821 }
2822
SaveXml(XclExpXmlStream & rStrm)2823 void XclExpCellTable::SaveXml( XclExpXmlStream& rStrm )
2824 {
2825 // DEFAULT row height
2826 XclExpDefaultRowData& rDefData = mxDefrowheight->GetDefaultData();
2827 sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
2828 rWorksheet->startElement( XML_sheetFormatPr,
2829 // OOXTODO: XML_baseColWidth
2830 XML_defaultColWidth, OString::number(maColInfoBfr.GetDefColWidth()),
2831 // OOXTODO: XML_thickTop
2832 // OOXTODO: XML_thickBottom
2833 XML_defaultRowHeight, OString::number(static_cast<double> (rDefData.mnHeight) / 20.0),
2834 XML_customHeight, ToPsz(true),
2835 XML_zeroHeight, ToPsz( rDefData.IsHidden() ),
2836 XML_outlineLevelRow, OString::number(maRowBfr.GetHighestOutlineLevel()),
2837 XML_outlineLevelCol, OString::number(maColInfoBfr.GetHighestOutlineLevel()) );
2838 rWorksheet->endElement( XML_sheetFormatPr );
2839
2840 maColInfoBfr.SaveXml( rStrm );
2841 maRowBfr.SaveXml( rStrm );
2842 mxExtLst->SaveXml( rStrm );
2843 }
2844
2845 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2846