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 <sheetdatabuffer.hxx>
21 
22 #include <algorithm>
23 #include <com/sun/star/sheet/XArrayFormulaTokens.hpp>
24 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
25 #include <com/sun/star/table/XCell.hpp>
26 #include <com/sun/star/table/XCellRange.hpp>
27 #include <com/sun/star/util/DateTime.hpp>
28 #include <com/sun/star/util/NumberFormat.hpp>
29 #include <com/sun/star/util/XNumberFormatTypes.hpp>
30 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
31 #include <sal/log.hxx>
32 #include <osl/diagnose.h>
33 #include <editeng/boxitem.hxx>
34 #include <oox/helper/containerhelper.hxx>
35 #include <oox/helper/propertyset.hxx>
36 #include <oox/token/properties.hxx>
37 #include <oox/token/tokens.hxx>
38 #include <addressconverter.hxx>
39 #include <formulaparser.hxx>
40 #include <sharedstringsbuffer.hxx>
41 #include <unitconverter.hxx>
42 #include <rangelst.hxx>
43 #include <document.hxx>
44 #include <scitems.hxx>
45 #include <docpool.hxx>
46 #include <paramisc.hxx>
47 #include <patattr.hxx>
48 #include <documentimport.hxx>
49 #include <formulabuffer.hxx>
50 #include <numformat.hxx>
51 #include <sax/tools/converter.hxx>
52 
53 namespace oox::xls {
54 
55 using namespace ::com::sun::star::lang;
56 using namespace ::com::sun::star::sheet;
57 using namespace ::com::sun::star::uno;
58 using namespace ::com::sun::star::util;
59 
CellModel()60 CellModel::CellModel() :
61     mnCellType( XML_TOKEN_INVALID ),
62     mnXfId( -1 ),
63     mbShowPhonetic( false )
64 {
65 }
66 
CellFormulaModel()67 CellFormulaModel::CellFormulaModel() :
68     mnFormulaType( XML_TOKEN_INVALID ),
69     mnSharedId( -1 )
70 {
71 }
72 
isValidArrayRef(const ScAddress & rCellAddr)73 bool CellFormulaModel::isValidArrayRef( const ScAddress& rCellAddr )
74 {
75     return (maFormulaRef.aStart == rCellAddr );
76 }
77 
isValidSharedRef(const ScAddress & rCellAddr)78 bool CellFormulaModel::isValidSharedRef( const ScAddress& rCellAddr )
79 {
80     return
81         (maFormulaRef.aStart.Tab() == rCellAddr.Tab() ) &&
82         (maFormulaRef.aStart.Col() <= rCellAddr.Col() ) && (rCellAddr.Col() <= maFormulaRef.aEnd.Col()) &&
83         (maFormulaRef.aStart.Row() <= rCellAddr.Row() ) && (rCellAddr.Row() <= maFormulaRef.aEnd.Row());
84 }
85 
DataTableModel()86 DataTableModel::DataTableModel() :
87     mb2dTable( false ),
88     mbRowTable( false ),
89     mbRef1Deleted( false ),
90     mbRef2Deleted( false )
91 {
92 }
93 
CellBlockBuffer(const WorksheetHelper & rHelper)94 CellBlockBuffer::CellBlockBuffer( const WorksheetHelper& rHelper ) :
95     WorksheetHelper( rHelper ),
96     mnCurrRow( -1 )
97 {
98 }
99 
setColSpans(sal_Int32 nRow,const ValueRangeSet & rColSpans)100 void CellBlockBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans )
101 {
102     OSL_ENSURE( maColSpans.count( nRow ) == 0, "CellBlockBuffer::setColSpans - multiple column spans for the same row" );
103     OSL_ENSURE( (mnCurrRow < nRow) && (maColSpans.empty() || (maColSpans.rbegin()->first < nRow)), "CellBlockBuffer::setColSpans - rows are unsorted" );
104     if( (mnCurrRow < nRow) && (maColSpans.count( nRow ) == 0) )
105     {
106         maColSpans[ nRow ] = rColSpans.getRanges();
107         mnCurrRow = nRow;
108     }
109 }
110 
SheetDataBuffer(const WorksheetHelper & rHelper)111 SheetDataBuffer::SheetDataBuffer( const WorksheetHelper& rHelper ) :
112     WorksheetHelper( rHelper ),
113     maCellBlocks( rHelper ),
114     mbPendingSharedFmla( false )
115 {
116 }
117 
setColSpans(sal_Int32 nRow,const ValueRangeSet & rColSpans)118 void SheetDataBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans )
119 {
120     maCellBlocks.setColSpans( nRow, rColSpans );
121 }
122 
setBlankCell(const CellModel & rModel)123 void SheetDataBuffer::setBlankCell( const CellModel& rModel )
124 {
125     setCellFormat( rModel );
126 }
127 
setValueCell(const CellModel & rModel,double fValue)128 void SheetDataBuffer::setValueCell( const CellModel& rModel, double fValue )
129 {
130     getDocImport().setNumericCell(rModel.maCellAddr, fValue);
131     setCellFormat( rModel );
132 }
133 
setStringCell(const CellModel & rModel,const OUString & rText)134 void SheetDataBuffer::setStringCell( const CellModel& rModel, const OUString& rText )
135 {
136     if (!rText.isEmpty())
137         getDocImport().setStringCell(rModel.maCellAddr, rText);
138 
139     setCellFormat( rModel );
140 }
141 
setStringCell(const CellModel & rModel,const RichStringRef & rxString)142 void SheetDataBuffer::setStringCell( const CellModel& rModel, const RichStringRef& rxString )
143 {
144     OSL_ENSURE( rxString, "SheetDataBuffer::setStringCell - missing rich string object" );
145     const oox::xls::Font* pFirstPortionFont = getStyles().getFontFromCellXf( rModel.mnXfId ).get();
146     OUString aText;
147     if( rxString->extractPlainString( aText, pFirstPortionFont ) )
148     {
149         setStringCell( rModel, aText );
150     }
151     else
152     {
153         putRichString( rModel.maCellAddr, *rxString, pFirstPortionFont );
154         setCellFormat( rModel );
155     }
156 }
157 
setStringCell(const CellModel & rModel,sal_Int32 nStringId)158 void SheetDataBuffer::setStringCell( const CellModel& rModel, sal_Int32 nStringId )
159 {
160     RichStringRef xString = getSharedStrings().getString( nStringId );
161     if( xString )
162         setStringCell( rModel, xString );
163     else
164         setBlankCell( rModel );
165 }
166 
setDateTimeCell(const CellModel & rModel,const css::util::DateTime & rDateTime)167 void SheetDataBuffer::setDateTimeCell( const CellModel& rModel, const css::util::DateTime& rDateTime )
168 {
169     // write serial date/time value into the cell
170     double fSerial = getUnitConverter().calcSerialFromDateTime( rDateTime );
171     setValueCell( rModel, fSerial );
172     // set appropriate number format
173     using namespace ::com::sun::star::util::NumberFormat;
174     sal_Int16 nStdFmt = (fSerial < 1.0) ? TIME : (((rDateTime.Hours > 0) || (rDateTime.Minutes > 0) || (rDateTime.Seconds > 0)) ? DATETIME : DATE);
175     // set number format
176     try
177     {
178         Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY_THROW );
179         Reference< XNumberFormatTypes > xNumFmtTypes( xNumFmtsSupp->getNumberFormats(), UNO_QUERY_THROW );
180         sal_Int32 nIndex = xNumFmtTypes->getStandardFormat( nStdFmt, Locale() );
181         PropertySet aPropSet( getCell( rModel.maCellAddr ) );
182         aPropSet.setProperty( PROP_NumberFormat, nIndex );
183     }
184     catch( Exception& )
185     {
186     }
187 }
188 
setBooleanCell(const CellModel & rModel,bool bValue)189 void SheetDataBuffer::setBooleanCell( const CellModel& rModel, bool bValue )
190 {
191     getFormulaBuffer().setCellFormula(
192         rModel.maCellAddr, bValue ? OUString("TRUE()") : OUString("FALSE()"));
193 
194     // #108770# set 'Standard' number format for all Boolean cells
195     setCellFormat( rModel );
196 }
197 
setErrorCell(const CellModel & rModel,const OUString & rErrorCode)198 void SheetDataBuffer::setErrorCell( const CellModel& rModel, const OUString& rErrorCode )
199 {
200     // Using the formula compiler now we can simply pass on the error string.
201     getFormulaBuffer().setCellFormula( rModel.maCellAddr, rErrorCode);
202     setCellFormat( rModel );
203 }
204 
setErrorCell(const CellModel & rModel,sal_uInt8 nErrorCode)205 void SheetDataBuffer::setErrorCell( const CellModel& rModel, sal_uInt8 nErrorCode )
206 {
207     setErrorCell( rModel, getUnitConverter().calcErrorString( nErrorCode));
208 }
209 
setDateCell(const CellModel & rModel,const OUString & rDateString)210 void SheetDataBuffer::setDateCell( const CellModel& rModel, const OUString& rDateString )
211 {
212     css::util::DateTime aDateTime;
213     if (!sax::Converter::parseDateTime( aDateTime, rDateString))
214     {
215         SAL_WARN("sc.filter", "SheetDataBuffer::setDateCell - could not parse: " << rDateString);
216         // At least don't lose data.
217         setStringCell( rModel, rDateString);
218         return;
219     }
220 
221     double fSerial = getUnitConverter().calcSerialFromDateTime( aDateTime);
222     setValueCell( rModel, fSerial);
223 }
224 
createSharedFormula(const ScAddress & rAddr,const ApiTokenSequence & rTokens)225 void SheetDataBuffer::createSharedFormula(const ScAddress& rAddr, const ApiTokenSequence& rTokens)
226 {
227     BinAddress aAddr(rAddr);
228     maSharedFormulas[aAddr] = rTokens;
229     if( mbPendingSharedFmla )
230         setCellFormula( maSharedFmlaAddr, resolveSharedFormula( maSharedBaseAddr ) );
231 }
232 
setFormulaCell(const CellModel & rModel,const ApiTokenSequence & rTokens)233 void SheetDataBuffer::setFormulaCell( const CellModel& rModel, const ApiTokenSequence& rTokens )
234 {
235     mbPendingSharedFmla = false;
236     ApiTokenSequence aTokens;
237 
238     /*  Detect special token passed as placeholder for array formulas, shared
239         formulas, and table operations. In BIFF, these formulas are represented
240         by a single tExp resp. tTbl token. If the formula parser finds these
241         tokens, it puts a single OPCODE_BAD token with the base address and
242         formula type into the token sequence. This information will be
243         extracted here, and in case of a shared formula, the shared formula
244         buffer will generate the resulting formula token array. */
245     ApiSpecialTokenInfo aTokenInfo;
246     if( rTokens.hasElements() && getFormulaParser().extractSpecialTokenInfo( aTokenInfo, rTokens ) )
247     {
248         /*  The second member of the token info is set to true, if the formula
249             represents a table operation, which will be skipped. In BIFF12 it
250             is not possible to distinguish array and shared formulas
251             (BIFF5/BIFF8 provide this information with a special flag in the
252             FORMULA record). */
253         if( !aTokenInfo.Second )
254         {
255             /*  Construct the token array representing the shared formula. If
256                 the returned sequence is empty, the definition of the shared
257                 formula has not been loaded yet, or the cell is part of an
258                 array formula. In this case, the cell will be remembered. After
259                 reading the formula definition it will be retried to insert the
260                 formula via retryPendingSharedFormulaCell(). */
261             ScAddress aTokenAddr( aTokenInfo.First.Column, aTokenInfo.First.Row, aTokenInfo.First.Sheet );
262             aTokens = resolveSharedFormula( aTokenAddr );
263             if( !aTokens.hasElements() )
264             {
265                 maSharedFmlaAddr = rModel.maCellAddr;
266                 maSharedBaseAddr = aTokenAddr;
267                 mbPendingSharedFmla = true;
268             }
269         }
270     }
271     else
272     {
273         // simple formula, use the passed token array
274         aTokens = rTokens;
275     }
276 
277     setCellFormula( rModel.maCellAddr, aTokens );
278     setCellFormat( rModel );
279 }
280 
createArrayFormula(const ScRange & rRange,const ApiTokenSequence & rTokens)281 void SheetDataBuffer::createArrayFormula( const ScRange& rRange, const ApiTokenSequence& rTokens )
282 {
283     /*  Array formulas will be inserted later in finalizeImport(). This is
284         needed to not disturb collecting all the cells, which will be put into
285         the sheet in large blocks to increase performance. */
286     maArrayFormulas.emplace_back( rRange, rTokens );
287 }
288 
createTableOperation(const ScRange & rRange,const DataTableModel & rModel)289 void SheetDataBuffer::createTableOperation( const ScRange& rRange, const DataTableModel& rModel )
290 {
291     /*  Table operations will be inserted later in finalizeImport(). This is
292         needed to not disturb collecting all the cells, which will be put into
293         the sheet in large blocks to increase performance. */
294     maTableOperations.emplace_back( rRange, rModel );
295 }
296 
setRowFormat(sal_Int32 nRow,sal_Int32 nXfId,bool bCustomFormat)297 void SheetDataBuffer::setRowFormat( sal_Int32 nRow, sal_Int32 nXfId, bool bCustomFormat )
298 {
299     // set row formatting
300     if( bCustomFormat )
301     {
302         // try to expand cached row range, if formatting is equal
303         if( (maXfIdRowRange.maRowRange.mnLast < 0) || !maXfIdRowRange.tryExpand( nRow, nXfId ) )
304         {
305 
306             maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange );
307             maXfIdRowRange.set( nRow, nXfId );
308         }
309     }
310     else if( maXfIdRowRange.maRowRange.mnLast >= 0 )
311     {
312         // finish last cached row range
313         maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange );
314         maXfIdRowRange.set( -1, -1 );
315     }
316 }
317 
setMergedRange(const ScRange & rRange)318 void SheetDataBuffer::setMergedRange( const ScRange& rRange )
319 {
320     maMergedRanges.emplace_back( rRange );
321 }
322 
323 typedef std::pair<sal_Int32, sal_Int32> FormatKeyPair;
324 
addIfNotInMyMap(const StylesBuffer & rStyles,std::map<FormatKeyPair,ScRangeList> & rMap,sal_Int32 nXfId,sal_Int32 nFormatId,const ScRangeList & rRangeList)325 static void addIfNotInMyMap( const StylesBuffer& rStyles, std::map< FormatKeyPair, ScRangeList >& rMap, sal_Int32 nXfId, sal_Int32 nFormatId, const ScRangeList& rRangeList )
326 {
327     Xf* pXf1 = rStyles.getCellXf( nXfId ).get();
328     if ( !pXf1 )
329         return;
330 
331     auto it = std::find_if(rMap.begin(), rMap.end(),
332         [&nFormatId, &rStyles, &pXf1](const std::pair<FormatKeyPair, ScRangeList>& rEntry) {
333             if (rEntry.first.second != nFormatId)
334                 return false;
335             Xf* pXf2 = rStyles.getCellXf( rEntry.first.first ).get();
336             return *pXf1 == *pXf2;
337         });
338     if (it != rMap.end()) // already exists
339     {
340         // add ranges from the rangelist to the existing rangelist for the
341         // matching style ( should we check if they overlap ? )
342         for (size_t i = 0, nSize = rRangeList.size(); i < nSize; ++i)
343             it->second.push_back(rRangeList[i]);
344         return;
345     }
346     rMap[ FormatKeyPair( nXfId, nFormatId ) ] = rRangeList;
347 }
348 
addColXfStyles()349 void SheetDataBuffer::addColXfStyles()
350 {
351     std::map< FormatKeyPair, ScRangeList > rangeStyleListMap;
352     for( const auto& [rFormatKeyPair, rRangeList] : maXfIdRangeLists )
353     {
354         addIfNotInMyMap( getStyles(), rangeStyleListMap, rFormatKeyPair.first, rFormatKeyPair.second, rRangeList );
355     }
356     // gather all ranges that have the same style and apply them in bulk
357     for ( const auto& [rFormatKeyPair, rRanges] : rangeStyleListMap )
358     {
359         for (const ScRange & rAddress : rRanges)
360         {
361             RowRangeStyle aStyleRows;
362             aStyleRows.mnNumFmt.first = rFormatKeyPair.first;
363             aStyleRows.mnNumFmt.second = rFormatKeyPair.second;
364             aStyleRows.mnStartRow = rAddress.aStart.Row();
365             aStyleRows.mnEndRow = rAddress.aEnd.Row();
366             for ( sal_Int32 nCol = rAddress.aStart.Col(); nCol <= rAddress.aEnd.Col(); ++nCol )
367                maStylesPerColumn[ nCol ].insert( aStyleRows );
368         }
369     }
370 }
371 
addColXfStyleProcessRowRanges()372 void SheetDataBuffer::addColXfStyleProcessRowRanges()
373 {
374     // count the number of row-range-styles we have
375     AddressConverter& rAddrConv = getAddressConverter();
376     int cnt = 0;
377     for ( const auto& [nXfId, rRowRangeList] : maXfIdRowRangeList )
378     {
379         if ( nXfId == -1 ) // it's a dud skip it
380             continue;
381         cnt += rRowRangeList.size();
382     }
383     // pre-allocate space in the sorted_vector
384     for ( sal_Int32 nCol = 0; nCol <= rAddrConv.getMaxApiAddress().Col(); ++nCol )
385     {
386        RowStyles& rRowStyles = maStylesPerColumn[ nCol ];
387        rRowStyles.reserve(rRowStyles.size() + cnt);
388     }
389     const auto nMaxCol = rAddrConv.getMaxApiAddress().Col();
390     for ( sal_Int32 nCol = 0; nCol <= nMaxCol; ++nCol )
391     {
392         RowStyles& rRowStyles = maStylesPerColumn[ nCol ];
393         for ( const auto& [nXfId, rRowRangeList] : maXfIdRowRangeList )
394         {
395             if ( nXfId == -1 ) // it's a dud skip it
396                 continue;
397             // get all row ranges for id
398             for ( const auto& rRange : rRowRangeList )
399             {
400                 RowRangeStyle aStyleRows;
401                 aStyleRows.mnNumFmt.first = nXfId;
402                 aStyleRows.mnNumFmt.second = -1;
403                 aStyleRows.mnStartRow = rRange.mnFirst;
404                 aStyleRows.mnEndRow = rRange.mnLast;
405 
406                 // Reset row range for each column
407                 aStyleRows.mnStartRow = rRange.mnFirst;
408                 aStyleRows.mnEndRow = rRange.mnLast;
409 
410                 // If aStyleRows includes rows already allocated to a style
411                 // in rRowStyles, then we need to split it into parts.
412                 // ( to occupy only rows that have no style definition)
413 
414                 // Start iterating at the first element that is not completely before aStyleRows
415                 RowStyles::const_iterator rows_it = rRowStyles.lower_bound(aStyleRows);
416                 bool bAddRange = true;
417                 for ( ; rows_it != rRowStyles.end(); ++rows_it )
418                 {
419                     // Add the part of aStyleRows that does not overlap with r
420                     if ( aStyleRows.mnStartRow < rows_it->mnStartRow )
421                     {
422                         RowRangeStyle aSplit = aStyleRows;
423                         aSplit.mnEndRow = std::min(aStyleRows.mnEndRow, rows_it->mnStartRow - 1);
424                         rows_it = rRowStyles.insert( aSplit ).first;
425                     }
426 
427                     // Done if no part of aStyleRows extends beyond r
428                     if ( aStyleRows.mnEndRow <= rows_it->mnEndRow )
429                     {
430                         bAddRange = false;
431                         break;
432                     }
433 
434                     // Cut off the part aStyleRows that was handled above
435                     aStyleRows.mnStartRow = rows_it->mnEndRow + 1;
436                 }
437                 if ( bAddRange )
438                     rRowStyles.insert( aStyleRows );
439             }
440         }
441     }
442 }
443 
finalizeImport()444 void SheetDataBuffer::finalizeImport()
445 {
446     ScDocumentImport& rDocImport = getDocImport();
447 
448     SCTAB nStartTabInvalidatedIters(SCTAB_MAX);
449     SCTAB nEndTabInvalidatedIters(0);
450 
451     // create all array formulas
452     for( const auto& [rRange, rTokens] : maArrayFormulas )
453     {
454         finalizeArrayFormula(rRange, rTokens);
455 
456         nStartTabInvalidatedIters = std::min(rRange.aStart.Tab(), nStartTabInvalidatedIters);
457         nEndTabInvalidatedIters = std::max(rRange.aEnd.Tab(), nEndTabInvalidatedIters);
458     }
459 
460     for (SCTAB nTab = nStartTabInvalidatedIters; nTab <= nEndTabInvalidatedIters; ++nTab)
461         rDocImport.invalidateBlockPositionSet(nTab);
462 
463     // create all table operations
464     for( const auto& [rRange, rModel] : maTableOperations )
465         finalizeTableOperation( rRange, rModel );
466 
467     // write default formatting of remaining row range
468     maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange );
469 
470     addColXfStyles();
471 
472     addColXfStyleProcessRowRanges();
473 
474     ScDocument& rDoc = rDocImport.getDoc();
475     StylesBuffer& rStyles = getStyles();
476     for ( const auto& [rCol, rRowStyles] : maStylesPerColumn )
477     {
478         SCCOL nScCol = static_cast< SCCOL >( rCol );
479 
480         // tdf#91567 Get pattern from the first row without AutoFilter
481         const ScPatternAttr* pDefPattern = nullptr;
482         bool bAutoFilter = true;
483         SCROW nScRow = 0;
484         while ( bAutoFilter && nScRow < rDoc.MaxRow() )
485         {
486             pDefPattern = rDoc.GetPattern( nScCol, nScRow, getSheetIndex() );
487             if ( pDefPattern )
488             {
489                 const ScMergeFlagAttr* pAttr = pDefPattern->GetItemSet().GetItem( ATTR_MERGE_FLAG );
490                 bAutoFilter = pAttr->HasAutoFilter();
491             }
492             else
493                 break;
494             nScRow++;
495         }
496         if ( !pDefPattern || nScRow == rDoc.MaxRow() )
497             pDefPattern = rDoc.GetDefPattern();
498 
499         Xf::AttrList aAttrs(pDefPattern);
500         for ( const auto& rRowStyle : rRowStyles )
501         {
502              Xf* pXf = rStyles.getCellXf( rRowStyle.mnNumFmt.first ).get();
503 
504              if ( pXf )
505                  pXf->applyPatternToAttrList( aAttrs,  rRowStyle.mnStartRow,  rRowStyle.mnEndRow,  rRowStyle.mnNumFmt.second );
506         }
507         if (aAttrs.maAttrs.empty() || aAttrs.maAttrs.back().nEndRow != rDoc.MaxRow())
508         {
509             ScAttrEntry aEntry;
510             aEntry.nEndRow = rDoc.MaxRow();
511             aEntry.pPattern = pDefPattern;
512             rDoc.GetPool()->Put(*aEntry.pPattern);
513             aAttrs.maAttrs.push_back(aEntry);
514 
515             if (!sc::NumFmtUtil::isLatinScript(*aEntry.pPattern, rDoc))
516                 aAttrs.mbLatinNumFmtOnly = false;
517         }
518 
519         ScDocumentImport::Attrs aAttrParam;
520         aAttrParam.mvData.swap(aAttrs.maAttrs);
521         aAttrParam.mbLatinNumFmtOnly = aAttrs.mbLatinNumFmtOnly;
522 
523         rDocImport.setAttrEntries(getSheetIndex(), nScCol, std::move(aAttrParam));
524     }
525 
526     // merge all cached merged ranges and update right/bottom cell borders
527     for( const auto& rMergedRange : maMergedRanges )
528         applyCellMerging( rMergedRange.maRange );
529     for( const auto& rCenterFillRange : maCenterFillRanges )
530         applyCellMerging( rCenterFillRange.maRange );
531 }
532 
533 // private --------------------------------------------------------------------
534 
XfIdRowRange()535 SheetDataBuffer::XfIdRowRange::XfIdRowRange() :
536     maRowRange( -1 ),
537     mnXfId( -1 )
538 {
539 }
540 
set(sal_Int32 nRow,sal_Int32 nXfId)541 void SheetDataBuffer::XfIdRowRange::set( sal_Int32 nRow, sal_Int32 nXfId )
542 {
543     maRowRange = ValueRange( nRow );
544     mnXfId = nXfId;
545 }
546 
tryExpand(sal_Int32 nRow,sal_Int32 nXfId)547 bool SheetDataBuffer::XfIdRowRange::tryExpand( sal_Int32 nRow, sal_Int32 nXfId )
548 {
549     if( mnXfId == nXfId )
550     {
551         if( maRowRange.mnLast + 1 == nRow )
552         {
553             ++maRowRange.mnLast;
554             return true;
555         }
556         if( maRowRange.mnFirst == nRow + 1 )
557         {
558             --maRowRange.mnFirst;
559             return true;
560         }
561     }
562     return false;
563 }
564 
MergedRange(const ScRange & rRange)565 SheetDataBuffer::MergedRange::MergedRange( const ScRange& rRange ) :
566     maRange( rRange ),
567     mnHorAlign( XML_TOKEN_INVALID )
568 {
569 }
570 
MergedRange(const ScAddress & rAddress,sal_Int32 nHorAlign)571 SheetDataBuffer::MergedRange::MergedRange( const ScAddress& rAddress, sal_Int32 nHorAlign ) :
572     maRange( rAddress, rAddress ),
573     mnHorAlign( nHorAlign )
574 {
575 }
576 
tryExpand(const ScAddress & rAddress,sal_Int32 nHorAlign)577 bool SheetDataBuffer::MergedRange::tryExpand( const ScAddress& rAddress, sal_Int32 nHorAlign )
578 {
579     if( (mnHorAlign == nHorAlign) && (maRange.aStart.Row() == rAddress.Row() ) &&
580         (maRange.aEnd.Row() == rAddress.Row() ) && (maRange.aEnd.Col() + 1 == rAddress.Col() ) )
581     {
582         maRange.aEnd.IncCol();
583         return true;
584     }
585     return false;
586 }
587 
setCellFormula(const ScAddress & rCellAddr,const ApiTokenSequence & rTokens)588 void SheetDataBuffer::setCellFormula( const ScAddress& rCellAddr, const ApiTokenSequence& rTokens )
589 {
590     if( rTokens.hasElements() )
591     {
592         putFormulaTokens( rCellAddr, rTokens );
593     }
594 }
595 
596 
resolveSharedFormula(const ScAddress & rAddr) const597 ApiTokenSequence SheetDataBuffer::resolveSharedFormula( const ScAddress& rAddr ) const
598 {
599     BinAddress aAddr(rAddr);
600     ApiTokenSequence aTokens = ContainerHelper::getMapElement( maSharedFormulas, aAddr, ApiTokenSequence() );
601     return aTokens;
602 }
603 
finalizeArrayFormula(const ScRange & rRange,const ApiTokenSequence & rTokens) const604 void SheetDataBuffer::finalizeArrayFormula( const ScRange& rRange, const ApiTokenSequence& rTokens ) const
605 {
606     Reference< XArrayFormulaTokens > xTokens( getCellRange( rRange ), UNO_QUERY );
607     OSL_ENSURE( xTokens.is(), "SheetDataBuffer::finalizeArrayFormula - missing formula token interface" );
608     if( xTokens.is() )
609         xTokens->setArrayTokens( rTokens );
610 }
611 
finalizeTableOperation(const ScRange & rRange,const DataTableModel & rModel)612 void SheetDataBuffer::finalizeTableOperation( const ScRange& rRange, const DataTableModel& rModel )
613 {
614     if (rModel.mbRef1Deleted)
615         return;
616 
617     if (rModel.maRef1.isEmpty())
618         return;
619 
620     if (rRange.aStart.Col() <= 0 || rRange.aStart.Row() <= 0)
621         return;
622 
623     sal_Int16 nSheet = getSheetIndex();
624 
625     ScAddress aRef1( 0, 0, 0 );
626     if (!getAddressConverter().convertToCellAddress(aRef1, rModel.maRef1, nSheet, true))
627         return;
628 
629     ScDocumentImport& rDoc = getDocImport();
630     ScTabOpParam aParam;
631 
632     ScRange aScRange(rRange);
633 
634     if (rModel.mb2dTable)
635     {
636         // Two-variable data table.
637         if (rModel.mbRef2Deleted)
638             return;
639 
640         if (rModel.maRef2.isEmpty())
641             return;
642 
643         ScAddress aRef2( 0, 0, 0 );
644         if (!getAddressConverter().convertToCellAddress(aRef2, rModel.maRef2, nSheet, true))
645             return;
646 
647         aParam.meMode = ScTabOpParam::Both;
648 
649         aScRange.aStart.IncCol(-1);
650         aScRange.aStart.IncRow(-1);
651 
652         aParam.aRefFormulaCell.Set(aScRange.aStart.Col(), aScRange.aStart.Row(), nSheet, false, false, false);
653         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
654 
655         // Ref1 is row input cell and Ref2 is column input cell.
656         aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
657         aParam.aRefColCell.Set(aRef2.Col(), aRef2.Row(), aRef2.Tab(), false, false, false);
658         rDoc.setTableOpCells(aScRange, aParam);
659 
660         return;
661     }
662 
663     // One-variable data table.
664 
665     if (rModel.mbRowTable)
666     {
667         // One-variable row input cell (horizontal).
668         aParam.meMode = ScTabOpParam::Row;
669         aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
670         aParam.aRefFormulaCell.Set(rRange.aStart.Col()-1, rRange.aStart.Row(), nSheet, false, true, false);
671         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
672         aScRange.aStart.IncRow(-1);
673         rDoc.setTableOpCells(aScRange, aParam);
674     }
675     else
676     {
677         // One-variable column input cell (vertical).
678         aParam.meMode = ScTabOpParam::Column;
679         aParam.aRefColCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
680         aParam.aRefFormulaCell.Set(rRange.aStart.Col(), rRange.aStart.Row()-1, nSheet, true, false, false);
681         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
682         aScRange.aStart.IncCol(-1);
683         rDoc.setTableOpCells(aScRange, aParam);
684     }
685 }
686 
setCellFormat(const CellModel & rModel)687 void SheetDataBuffer::setCellFormat( const CellModel& rModel )
688 {
689     if( rModel.mnXfId < 0 )
690         return;
691 
692     ScRangeList& rRangeList = maXfIdRangeLists[ XfIdNumFmtKey( rModel.mnXfId, -1 ) ];
693     ScRange* pLastRange = rRangeList.empty() ? nullptr : &rRangeList.back();
694     /* The xlsx sheet data contains row wise information.
695      * It is sufficient to check if the row range size is one
696      */
697     if (!rRangeList.empty() &&
698         *pLastRange == rModel.maCellAddr)
699         ; // do nothing - this probably bad data
700     else if (!rRangeList.empty() &&
701         pLastRange->aStart.Tab() == rModel.maCellAddr.Tab() &&
702         pLastRange->aStart.Row() == pLastRange->aEnd.Row() &&
703         pLastRange->aStart.Row() == rModel.maCellAddr.Row() &&
704         pLastRange->aEnd.Col() + 1 == rModel.maCellAddr.Col())
705     {
706         pLastRange->aEnd.IncCol();       // Expand Column
707     }
708     else
709     {
710         rRangeList.push_back(ScRange(rModel.maCellAddr));
711         pLastRange = &rRangeList.back();
712     }
713 
714     if (rRangeList.size() > 1)
715     {
716         for (size_t i = rRangeList.size() - 1; i != 0; --i)
717         {
718             ScRange& rMergeRange = rRangeList[i - 1];
719             if (pLastRange->aStart.Tab() != rMergeRange.aStart.Tab())
720                 break;
721 
722             /* Try to merge this with the previous range */
723             if (pLastRange->aStart.Row() == (rMergeRange.aEnd.Row() + 1) &&
724                 pLastRange->aStart.Col() == rMergeRange.aStart.Col() &&
725                 pLastRange->aEnd.Col() == rMergeRange.aEnd.Col())
726             {
727                 rMergeRange.aEnd.SetRow(pLastRange->aEnd.Row());
728                 rRangeList.Remove(rRangeList.size() - 1);
729                 break;
730             }
731             else if (pLastRange->aStart.Row() > (rMergeRange.aEnd.Row() + 1))
732                 break; // Un-necessary to check with any other rows
733         }
734     }
735     // update merged ranges for 'center across selection' and 'fill'
736     const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get();
737     if( !pXf )
738         return;
739 
740     sal_Int32 nHorAlign = pXf->getAlignment().getModel().mnHorAlign;
741     if( (nHorAlign == XML_centerContinuous) || (nHorAlign == XML_fill) )
742     {
743         /*  start new merged range, if cell is not empty (#108781#),
744             or try to expand last range with empty cell */
745         if( rModel.mnCellType != XML_TOKEN_INVALID )
746             maCenterFillRanges.emplace_back( rModel.maCellAddr, nHorAlign );
747         else if( !maCenterFillRanges.empty() )
748             maCenterFillRanges.rbegin()->tryExpand( rModel.maCellAddr, nHorAlign );
749     }
750 }
751 
lcl_SetBorderLine(ScDocument & rDoc,const ScRange & rRange,SCTAB nScTab,SvxBoxItemLine nLine)752 static void lcl_SetBorderLine( ScDocument& rDoc, const ScRange& rRange, SCTAB nScTab, SvxBoxItemLine nLine )
753 {
754     SCCOL nFromScCol = (nLine == SvxBoxItemLine::RIGHT) ? rRange.aEnd.Col() : rRange.aStart.Col();
755     SCROW nFromScRow = (nLine == SvxBoxItemLine::BOTTOM) ? rRange.aEnd.Row() : rRange.aStart.Row();
756 
757     const SvxBoxItem* pFromItem =
758         rDoc.GetAttr( nFromScCol, nFromScRow, nScTab, ATTR_BORDER );
759     const SvxBoxItem* pToItem =
760         rDoc.GetAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, ATTR_BORDER );
761 
762     SvxBoxItem aNewItem( *pToItem );
763     aNewItem.SetLine( pFromItem->GetLine( nLine ), nLine );
764     rDoc.ApplyAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, aNewItem );
765 }
766 
applyCellMerging(const ScRange & rRange)767 void SheetDataBuffer::applyCellMerging( const ScRange& rRange )
768 {
769     bool bMultiCol = rRange.aStart.Col() < rRange.aEnd.Col();
770     bool bMultiRow = rRange.aStart.Row() < rRange.aEnd.Row();
771 
772     const ScAddress& rStart = rRange.aStart;
773     const ScAddress& rEnd = rRange.aEnd;
774     ScDocument& rDoc = getScDocument();
775     // set correct right border
776     if( bMultiCol )
777         lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::RIGHT );
778         // set correct lower border
779     if( bMultiRow )
780         lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::BOTTOM );
781     // do merge
782     if( bMultiCol || bMultiRow )
783         rDoc.DoMerge( getSheetIndex(), rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row() );
784 }
785 
786 } // namespace oox
787 
788 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
789