xref: /core/sc/source/filter/oox/sheetdatabuffer.cxx (revision 358bdc5e)
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 
60 CellModel::CellModel() :
61     mnCellType( XML_TOKEN_INVALID ),
62     mnXfId( -1 ),
63     mbShowPhonetic( false )
64 {
65 }
66 
67 CellFormulaModel::CellFormulaModel() :
68     mnFormulaType( XML_TOKEN_INVALID ),
69     mnSharedId( -1 )
70 {
71 }
72 
73 bool CellFormulaModel::isValidArrayRef( const ScAddress& rCellAddr )
74 {
75     return (maFormulaRef.aStart == rCellAddr );
76 }
77 
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 
86 DataTableModel::DataTableModel() :
87     mb2dTable( false ),
88     mbRowTable( false ),
89     mbRef1Deleted( false ),
90     mbRef2Deleted( false )
91 {
92 }
93 
94 CellBlockBuffer::CellBlockBuffer( const WorksheetHelper& rHelper ) :
95     WorksheetHelper( rHelper ),
96     mnCurrRow( -1 )
97 {
98 }
99 
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 
111 SheetDataBuffer::SheetDataBuffer( const WorksheetHelper& rHelper ) :
112     WorksheetHelper( rHelper ),
113     maCellBlocks( rHelper ),
114     mbPendingSharedFmla( false )
115 {
116 }
117 
118 void SheetDataBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans )
119 {
120     maCellBlocks.setColSpans( nRow, rColSpans );
121 }
122 
123 void SheetDataBuffer::setBlankCell( const CellModel& rModel )
124 {
125     setCellFormat( rModel );
126 }
127 
128 void SheetDataBuffer::setValueCell( const CellModel& rModel, double fValue )
129 {
130     getDocImport().setNumericCell(rModel.maCellAddr, fValue);
131     setCellFormat( rModel );
132 }
133 
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 
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 
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 
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 
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 
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 
205 void SheetDataBuffer::setErrorCell( const CellModel& rModel, sal_uInt8 nErrorCode )
206 {
207     setErrorCell( rModel, getUnitConverter().calcErrorString( nErrorCode));
208 }
209 
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 
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 
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 
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 
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 
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 
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 
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 
349 void SheetDataBuffer::addColXfStyle( sal_Int32 nXfId, sal_Int32 nFormatId, const ScRange& rAddress, bool bProcessRowRange )
350 {
351     RowRangeStyle aStyleRows;
352     aStyleRows.mnNumFmt.first = nXfId;
353     aStyleRows.mnNumFmt.second = nFormatId;
354     aStyleRows.mnStartRow = rAddress.aStart.Row();
355     aStyleRows.mnEndRow = rAddress.aEnd.Row();
356     for ( sal_Int32 nCol = rAddress.aStart.Col(); nCol <= rAddress.aEnd.Col(); ++nCol )
357     {
358         if ( !bProcessRowRange )
359             maStylesPerColumn[ nCol ].insert( aStyleRows );
360         else
361         {
362             RowStyles& rRowStyles = maStylesPerColumn[ nCol ];
363             // Reset row range for each column
364             aStyleRows.mnStartRow = rAddress.aStart.Row();
365             aStyleRows.mnEndRow = rAddress.aEnd.Row();
366 
367             // If aStyleRows includes rows already allocated to a style
368             // in rRowStyles, then we need to split it into parts.
369             // ( to occupy only rows that have no style definition)
370 
371             // Start iterating at the first element that is not completely before aStyleRows
372             RowStyles::iterator rows_it = rRowStyles.lower_bound(aStyleRows);
373             RowStyles::iterator rows_end = rRowStyles.end();
374             bool bAddRange = true;
375             for ( ; rows_it != rows_end; ++rows_it )
376             {
377                 const RowRangeStyle& r = *rows_it;
378 
379                 // Add the part of aStyleRows that does not overlap with r
380                 if ( aStyleRows.mnStartRow < r.mnStartRow )
381                 {
382                     RowRangeStyle aSplit = aStyleRows;
383                     aSplit.mnEndRow = std::min(aStyleRows.mnEndRow, r.mnStartRow - 1);
384                     // Insert with hint that aSplit comes directly before the current position
385                     rRowStyles.insert( rows_it, aSplit );
386                 }
387 
388                 // Done if no part of aStyleRows extends beyond r
389                 if ( aStyleRows.mnEndRow <= r.mnEndRow )
390                 {
391                     bAddRange = false;
392                     break;
393                 }
394 
395                 // Cut off the part aStyleRows that was handled above
396                 aStyleRows.mnStartRow = r.mnEndRow + 1;
397             }
398             if ( bAddRange )
399                 rRowStyles.insert( aStyleRows );
400         }
401     }
402 }
403 
404 void SheetDataBuffer::finalizeImport()
405 {
406     // create all array formulas
407     for( const auto& [rRange, rTokens] : maArrayFormulas )
408         finalizeArrayFormula( rRange, rTokens );
409 
410     // create all table operations
411     for( const auto& [rRange, rModel] : maTableOperations )
412         finalizeTableOperation( rRange, rModel );
413 
414     // write default formatting of remaining row range
415     maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange );
416 
417     std::map< FormatKeyPair, ScRangeList > rangeStyleListMap;
418     for( const auto& [rFormatKeyPair, rRangeList] : maXfIdRangeLists )
419     {
420         addIfNotInMyMap( getStyles(), rangeStyleListMap, rFormatKeyPair.first, rFormatKeyPair.second, rRangeList );
421     }
422     // gather all ranges that have the same style and apply them in bulk
423     for ( const auto& [rFormatKeyPair, rRanges] : rangeStyleListMap )
424     {
425         for (size_t i = 0, nSize = rRanges.size(); i < nSize; ++i)
426             addColXfStyle( rFormatKeyPair.first, rFormatKeyPair.second, rRanges[i]);
427     }
428 
429     for ( const auto& [rXfId, rRowRangeList] : maXfIdRowRangeList )
430     {
431         if ( rXfId == -1 ) // it's a dud skip it
432             continue;
433         AddressConverter& rAddrConv = getAddressConverter();
434         // get all row ranges for id
435         for ( const auto& rRange : rRowRangeList )
436         {
437             ScRange aRange( 0, rRange.mnFirst, getSheetIndex(),
438                             rAddrConv.getMaxApiAddress().Col(), rRange.mnLast, getSheetIndex() );
439 
440             addColXfStyle( rXfId, -1, aRange, true );
441         }
442     }
443 
444     ScDocumentImport& rDocImport = getDocImport();
445     ScDocument& rDoc = rDocImport.getDoc();
446     StylesBuffer& rStyles = getStyles();
447     for ( const auto& [rCol, rRowStyles] : maStylesPerColumn )
448     {
449         SCCOL nScCol = static_cast< SCCOL >( rCol );
450 
451         // tdf#91567 Get pattern from the first row without AutoFilter
452         const ScPatternAttr* pDefPattern = nullptr;
453         bool bAutoFilter = true;
454         SCROW nScRow = 0;
455         while ( bAutoFilter && nScRow < rDoc.MaxRow() )
456         {
457             pDefPattern = rDoc.GetPattern( nScCol, nScRow, getSheetIndex() );
458             if ( pDefPattern )
459             {
460                 const ScMergeFlagAttr* pAttr = pDefPattern->GetItemSet().GetItem( ATTR_MERGE_FLAG );
461                 bAutoFilter = pAttr->HasAutoFilter();
462             }
463             else
464                 break;
465             nScRow++;
466         }
467         if ( !pDefPattern || nScRow == rDoc.MaxRow() )
468             pDefPattern = rDoc.GetDefPattern();
469 
470         Xf::AttrList aAttrs(pDefPattern);
471         for ( const auto& rRowStyle : rRowStyles )
472         {
473              Xf* pXf = rStyles.getCellXf( rRowStyle.mnNumFmt.first ).get();
474 
475              if ( pXf )
476                  pXf->applyPatternToAttrList( aAttrs,  rRowStyle.mnStartRow,  rRowStyle.mnEndRow,  rRowStyle.mnNumFmt.second );
477         }
478         if (aAttrs.maAttrs.empty() || aAttrs.maAttrs.back().nEndRow != rDoc.MaxRow())
479         {
480             ScAttrEntry aEntry;
481             aEntry.nEndRow = rDoc.MaxRow();
482             aEntry.pPattern = pDefPattern;
483             rDoc.GetPool()->Put(*aEntry.pPattern);
484             aAttrs.maAttrs.push_back(aEntry);
485 
486             if (!sc::NumFmtUtil::isLatinScript(*aEntry.pPattern, rDoc))
487                 aAttrs.mbLatinNumFmtOnly = false;
488         }
489 
490         ScDocumentImport::Attrs aAttrParam;
491         aAttrParam.mvData.swap(aAttrs.maAttrs);
492         aAttrParam.mbLatinNumFmtOnly = aAttrs.mbLatinNumFmtOnly;
493 
494         rDocImport.setAttrEntries(getSheetIndex(), nScCol, std::move(aAttrParam));
495     }
496 
497     // merge all cached merged ranges and update right/bottom cell borders
498     for( const auto& rMergedRange : maMergedRanges )
499         applyCellMerging( rMergedRange.maRange );
500     for( const auto& rCenterFillRange : maCenterFillRanges )
501         applyCellMerging( rCenterFillRange.maRange );
502 }
503 
504 // private --------------------------------------------------------------------
505 
506 SheetDataBuffer::XfIdRowRange::XfIdRowRange() :
507     maRowRange( -1 ),
508     mnXfId( -1 )
509 {
510 }
511 
512 void SheetDataBuffer::XfIdRowRange::set( sal_Int32 nRow, sal_Int32 nXfId )
513 {
514     maRowRange = ValueRange( nRow );
515     mnXfId = nXfId;
516 }
517 
518 bool SheetDataBuffer::XfIdRowRange::tryExpand( sal_Int32 nRow, sal_Int32 nXfId )
519 {
520     if( mnXfId == nXfId )
521     {
522         if( maRowRange.mnLast + 1 == nRow )
523         {
524             ++maRowRange.mnLast;
525             return true;
526         }
527         if( maRowRange.mnFirst == nRow + 1 )
528         {
529             --maRowRange.mnFirst;
530             return true;
531         }
532     }
533     return false;
534 }
535 
536 SheetDataBuffer::MergedRange::MergedRange( const ScRange& rRange ) :
537     maRange( rRange ),
538     mnHorAlign( XML_TOKEN_INVALID )
539 {
540 }
541 
542 SheetDataBuffer::MergedRange::MergedRange( const ScAddress& rAddress, sal_Int32 nHorAlign ) :
543     maRange( rAddress, rAddress ),
544     mnHorAlign( nHorAlign )
545 {
546 }
547 
548 bool SheetDataBuffer::MergedRange::tryExpand( const ScAddress& rAddress, sal_Int32 nHorAlign )
549 {
550     if( (mnHorAlign == nHorAlign) && (maRange.aStart.Row() == rAddress.Row() ) &&
551         (maRange.aEnd.Row() == rAddress.Row() ) && (maRange.aEnd.Col() + 1 == rAddress.Col() ) )
552     {
553         maRange.aEnd.IncCol();
554         return true;
555     }
556     return false;
557 }
558 
559 void SheetDataBuffer::setCellFormula( const ScAddress& rCellAddr, const ApiTokenSequence& rTokens )
560 {
561     if( rTokens.hasElements() )
562     {
563         putFormulaTokens( rCellAddr, rTokens );
564     }
565 }
566 
567 
568 ApiTokenSequence SheetDataBuffer::resolveSharedFormula( const ScAddress& rAddr ) const
569 {
570     BinAddress aAddr(rAddr);
571     ApiTokenSequence aTokens = ContainerHelper::getMapElement( maSharedFormulas, aAddr, ApiTokenSequence() );
572     return aTokens;
573 }
574 
575 void SheetDataBuffer::finalizeArrayFormula( const ScRange& rRange, const ApiTokenSequence& rTokens ) const
576 {
577     Reference< XArrayFormulaTokens > xTokens( getCellRange( rRange ), UNO_QUERY );
578     OSL_ENSURE( xTokens.is(), "SheetDataBuffer::finalizeArrayFormula - missing formula token interface" );
579     if( xTokens.is() )
580         xTokens->setArrayTokens( rTokens );
581 }
582 
583 void SheetDataBuffer::finalizeTableOperation( const ScRange& rRange, const DataTableModel& rModel )
584 {
585     if (rModel.mbRef1Deleted)
586         return;
587 
588     if (rModel.maRef1.isEmpty())
589         return;
590 
591     if (rRange.aStart.Col() <= 0 || rRange.aStart.Row() <= 0)
592         return;
593 
594     sal_Int16 nSheet = getSheetIndex();
595 
596     ScAddress aRef1( 0, 0, 0 );
597     if (!getAddressConverter().convertToCellAddress(aRef1, rModel.maRef1, nSheet, true))
598         return;
599 
600     ScDocumentImport& rDoc = getDocImport();
601     ScTabOpParam aParam;
602 
603     ScRange aScRange(rRange);
604 
605     if (rModel.mb2dTable)
606     {
607         // Two-variable data table.
608         if (rModel.mbRef2Deleted)
609             return;
610 
611         if (rModel.maRef2.isEmpty())
612             return;
613 
614         ScAddress aRef2( 0, 0, 0 );
615         if (!getAddressConverter().convertToCellAddress(aRef2, rModel.maRef2, nSheet, true))
616             return;
617 
618         aParam.meMode = ScTabOpParam::Both;
619 
620         aScRange.aStart.IncCol(-1);
621         aScRange.aStart.IncRow(-1);
622 
623         aParam.aRefFormulaCell.Set(aScRange.aStart.Col(), aScRange.aStart.Row(), nSheet, false, false, false);
624         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
625 
626         // Ref1 is row input cell and Ref2 is column input cell.
627         aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
628         aParam.aRefColCell.Set(aRef2.Col(), aRef2.Row(), aRef2.Tab(), false, false, false);
629         rDoc.setTableOpCells(aScRange, aParam);
630 
631         return;
632     }
633 
634     // One-variable data table.
635 
636     if (rModel.mbRowTable)
637     {
638         // One-variable row input cell (horizontal).
639         aParam.meMode = ScTabOpParam::Row;
640         aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
641         aParam.aRefFormulaCell.Set(rRange.aStart.Col()-1, rRange.aStart.Row(), nSheet, false, true, false);
642         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
643         aScRange.aStart.IncRow(-1);
644         rDoc.setTableOpCells(aScRange, aParam);
645     }
646     else
647     {
648         // One-variable column input cell (vertical).
649         aParam.meMode = ScTabOpParam::Column;
650         aParam.aRefColCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false);
651         aParam.aRefFormulaCell.Set(rRange.aStart.Col(), rRange.aStart.Row()-1, nSheet, true, false, false);
652         aParam.aRefFormulaEnd = aParam.aRefFormulaCell;
653         aScRange.aStart.IncCol(-1);
654         rDoc.setTableOpCells(aScRange, aParam);
655     }
656 }
657 
658 void SheetDataBuffer::setCellFormat( const CellModel& rModel )
659 {
660     if( rModel.mnXfId < 0 )
661         return;
662 
663     ScRangeList& rRangeList = maXfIdRangeLists[ XfIdNumFmtKey( rModel.mnXfId, -1 ) ];
664     ScRange* pLastRange = rRangeList.empty() ? nullptr : &rRangeList.back();
665     /* The xlsx sheet data contains row wise information.
666      * It is sufficient to check if the row range size is one
667      */
668     if (!rRangeList.empty() &&
669         *pLastRange == rModel.maCellAddr)
670         ; // do nothing - this probably bad data
671     else if (!rRangeList.empty() &&
672         pLastRange->aStart.Tab() == rModel.maCellAddr.Tab() &&
673         pLastRange->aStart.Row() == pLastRange->aEnd.Row() &&
674         pLastRange->aStart.Row() == rModel.maCellAddr.Row() &&
675         pLastRange->aEnd.Col() + 1 == rModel.maCellAddr.Col())
676     {
677         pLastRange->aEnd.IncCol();       // Expand Column
678     }
679     else
680     {
681         rRangeList.push_back(ScRange(rModel.maCellAddr));
682         pLastRange = &rRangeList.back();
683     }
684 
685     if (rRangeList.size() > 1)
686     {
687         for (size_t i = rRangeList.size() - 1; i != 0; --i)
688         {
689             ScRange& rMergeRange = rRangeList[i - 1];
690             if (pLastRange->aStart.Tab() != rMergeRange.aStart.Tab())
691                 break;
692 
693             /* Try to merge this with the previous range */
694             if (pLastRange->aStart.Row() == (rMergeRange.aEnd.Row() + 1) &&
695                 pLastRange->aStart.Col() == rMergeRange.aStart.Col() &&
696                 pLastRange->aEnd.Col() == rMergeRange.aEnd.Col())
697             {
698                 rMergeRange.aEnd.SetRow(pLastRange->aEnd.Row());
699                 rRangeList.Remove(rRangeList.size() - 1);
700                 break;
701             }
702             else if (pLastRange->aStart.Row() > (rMergeRange.aEnd.Row() + 1))
703                 break; // Un-necessary to check with any other rows
704         }
705     }
706     // update merged ranges for 'center across selection' and 'fill'
707     const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get();
708     if( !pXf )
709         return;
710 
711     sal_Int32 nHorAlign = pXf->getAlignment().getModel().mnHorAlign;
712     if( (nHorAlign == XML_centerContinuous) || (nHorAlign == XML_fill) )
713     {
714         /*  start new merged range, if cell is not empty (#108781#),
715             or try to expand last range with empty cell */
716         if( rModel.mnCellType != XML_TOKEN_INVALID )
717             maCenterFillRanges.emplace_back( rModel.maCellAddr, nHorAlign );
718         else if( !maCenterFillRanges.empty() )
719             maCenterFillRanges.rbegin()->tryExpand( rModel.maCellAddr, nHorAlign );
720     }
721 }
722 
723 static void lcl_SetBorderLine( ScDocument& rDoc, const ScRange& rRange, SCTAB nScTab, SvxBoxItemLine nLine )
724 {
725     SCCOL nFromScCol = (nLine == SvxBoxItemLine::RIGHT) ? rRange.aEnd.Col() : rRange.aStart.Col();
726     SCROW nFromScRow = (nLine == SvxBoxItemLine::BOTTOM) ? rRange.aEnd.Row() : rRange.aStart.Row();
727 
728     const SvxBoxItem* pFromItem =
729         rDoc.GetAttr( nFromScCol, nFromScRow, nScTab, ATTR_BORDER );
730     const SvxBoxItem* pToItem =
731         rDoc.GetAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, ATTR_BORDER );
732 
733     SvxBoxItem aNewItem( *pToItem );
734     aNewItem.SetLine( pFromItem->GetLine( nLine ), nLine );
735     rDoc.ApplyAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, aNewItem );
736 }
737 
738 void SheetDataBuffer::applyCellMerging( const ScRange& rRange )
739 {
740     bool bMultiCol = rRange.aStart.Col() < rRange.aEnd.Col();
741     bool bMultiRow = rRange.aStart.Row() < rRange.aEnd.Row();
742 
743     const ScAddress& rStart = rRange.aStart;
744     const ScAddress& rEnd = rRange.aEnd;
745     ScDocument& rDoc = getScDocument();
746     // set correct right border
747     if( bMultiCol )
748         lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::RIGHT );
749         // set correct lower border
750     if( bMultiRow )
751         lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::BOTTOM );
752     // do merge
753     if( bMultiCol || bMultiRow )
754         rDoc.DoMerge( getSheetIndex(), rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row() );
755 }
756 
757 } // namespace oox
758 
759 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
760