diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/filter/oox/sheetdatabuffer.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/filter/oox/sheetdatabuffer.cxx')
-rw-r--r-- | sc/source/filter/oox/sheetdatabuffer.cxx | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/sc/source/filter/oox/sheetdatabuffer.cxx b/sc/source/filter/oox/sheetdatabuffer.cxx new file mode 100644 index 000000000..244d70b62 --- /dev/null +++ b/sc/source/filter/oox/sheetdatabuffer.cxx @@ -0,0 +1,823 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sheetdatabuffer.hxx> + +#include <algorithm> +#include <com/sun/star/sheet/XArrayFormulaTokens.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/table/XCell.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <editeng/boxitem.hxx> +#include <oox/helper/containerhelper.hxx> +#include <oox/helper/propertyset.hxx> +#include <oox/token/properties.hxx> +#include <oox/token/tokens.hxx> +#include <addressconverter.hxx> +#include <formulaparser.hxx> +#include <sharedstringsbuffer.hxx> +#include <unitconverter.hxx> +#include <rangelst.hxx> +#include <document.hxx> +#include <scitems.hxx> +#include <docpool.hxx> +#include <paramisc.hxx> +#include <patattr.hxx> +#include <documentimport.hxx> +#include <formulabuffer.hxx> +#include <numformat.hxx> +#include <sax/tools/converter.hxx> + +namespace oox::xls { + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sheet; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; + +CellModel::CellModel() : + mnCellType( XML_TOKEN_INVALID ), + mnXfId( -1 ), + mbShowPhonetic( false ) +{ +} + +CellFormulaModel::CellFormulaModel() : + mnFormulaType( XML_TOKEN_INVALID ), + mnSharedId( -1 ) +{ +} + +bool CellFormulaModel::isValidArrayRef( const ScAddress& rCellAddr ) +{ + return (maFormulaRef.aStart == rCellAddr ); +} + +bool CellFormulaModel::isValidSharedRef( const ScAddress& rCellAddr ) +{ + return + (maFormulaRef.aStart.Tab() == rCellAddr.Tab() ) && + (maFormulaRef.aStart.Col() <= rCellAddr.Col() ) && (rCellAddr.Col() <= maFormulaRef.aEnd.Col()) && + (maFormulaRef.aStart.Row() <= rCellAddr.Row() ) && (rCellAddr.Row() <= maFormulaRef.aEnd.Row()); +} + +DataTableModel::DataTableModel() : + mb2dTable( false ), + mbRowTable( false ), + mbRef1Deleted( false ), + mbRef2Deleted( false ) +{ +} + +CellBlockBuffer::CellBlockBuffer( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ), + mnCurrRow( -1 ) +{ +} + +void CellBlockBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans ) +{ + OSL_ENSURE( maColSpans.count( nRow ) == 0, "CellBlockBuffer::setColSpans - multiple column spans for the same row" ); + OSL_ENSURE( (mnCurrRow < nRow) && (maColSpans.empty() || (maColSpans.rbegin()->first < nRow)), "CellBlockBuffer::setColSpans - rows are unsorted" ); + if( mnCurrRow >= nRow ) + return; + auto pair = maColSpans.try_emplace(nRow, rColSpans.getRanges()); + if( pair.second ) // insert happened + mnCurrRow = nRow; +} + +SheetDataBuffer::SheetDataBuffer( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ), + maCellBlocks( rHelper ), + mbPendingSharedFmla( false ) +{ +} + +void SheetDataBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans ) +{ + maCellBlocks.setColSpans( nRow, rColSpans ); +} + +void SheetDataBuffer::setBlankCell( const CellModel& rModel ) +{ + setCellFormat( rModel ); +} + +void SheetDataBuffer::setValueCell( const CellModel& rModel, double fValue ) +{ + getDocImport().setNumericCell(rModel.maCellAddr, fValue); + setCellFormat( rModel ); +} + +void SheetDataBuffer::setStringCell( const CellModel& rModel, const OUString& rText ) +{ + if (!rText.isEmpty()) + getDocImport().setStringCell(rModel.maCellAddr, rText); + + setCellFormat( rModel ); +} + +void SheetDataBuffer::setStringCell( const CellModel& rModel, const RichStringRef& rxString ) +{ + OSL_ENSURE( rxString, "SheetDataBuffer::setStringCell - missing rich string object" ); + const oox::xls::Font* pFirstPortionFont = getStyles().getFontFromCellXf( rModel.mnXfId ).get(); + OUString aText; + if( rxString->extractPlainString( aText, pFirstPortionFont ) ) + { + setStringCell( rModel, aText ); + } + else + { + putRichString( rModel.maCellAddr, *rxString, pFirstPortionFont ); + setCellFormat( rModel ); + } +} + +void SheetDataBuffer::setStringCell( const CellModel& rModel, sal_Int32 nStringId ) +{ + RichStringRef xString = getSharedStrings().getString( nStringId ); + if( xString ) + setStringCell( rModel, xString ); + else + setBlankCell( rModel ); +} + +void SheetDataBuffer::setDateTimeCell( const CellModel& rModel, const css::util::DateTime& rDateTime ) +{ + // write serial date/time value into the cell + double fSerial = getUnitConverter().calcSerialFromDateTime( rDateTime ); + setValueCell( rModel, fSerial ); + // set appropriate number format + using namespace ::com::sun::star::util::NumberFormat; + sal_Int16 nStdFmt = (fSerial < 1.0) ? TIME : (((rDateTime.Hours > 0) || (rDateTime.Minutes > 0) || (rDateTime.Seconds > 0)) ? DATETIME : DATE); + // set number format + try + { + Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY_THROW ); + Reference< XNumberFormatTypes > xNumFmtTypes( xNumFmtsSupp->getNumberFormats(), UNO_QUERY_THROW ); + sal_Int32 nIndex = xNumFmtTypes->getStandardFormat( nStdFmt, Locale() ); + PropertySet aPropSet( getCell( rModel.maCellAddr ) ); + aPropSet.setProperty( PROP_NumberFormat, nIndex ); + } + catch( Exception& ) + { + } +} + +void SheetDataBuffer::setBooleanCell( const CellModel& rModel, bool bValue ) +{ + getFormulaBuffer().setCellFormula( + rModel.maCellAddr, bValue ? OUString("TRUE()") : OUString("FALSE()")); + + // #108770# set 'Standard' number format for all Boolean cells + setCellFormat( rModel ); +} + +void SheetDataBuffer::setErrorCell( const CellModel& rModel, const OUString& rErrorCode ) +{ + // Using the formula compiler now we can simply pass on the error string. + getFormulaBuffer().setCellFormula( rModel.maCellAddr, rErrorCode); + setCellFormat( rModel ); +} + +void SheetDataBuffer::setErrorCell( const CellModel& rModel, sal_uInt8 nErrorCode ) +{ + setErrorCell( rModel, getUnitConverter().calcErrorString( nErrorCode)); +} + +void SheetDataBuffer::setDateCell( const CellModel& rModel, const OUString& rDateString ) +{ + css::util::DateTime aDateTime; + if (!sax::Converter::parseDateTime( aDateTime, rDateString)) + { + SAL_WARN("sc.filter", "SheetDataBuffer::setDateCell - could not parse: " << rDateString); + // At least don't lose data. + setStringCell( rModel, rDateString); + return; + } + + double fSerial = getUnitConverter().calcSerialFromDateTime( aDateTime); + setValueCell( rModel, fSerial); +} + +void SheetDataBuffer::createSharedFormula(const ScAddress& rAddr, const ApiTokenSequence& rTokens) +{ + BinAddress aAddr(rAddr); + maSharedFormulas[aAddr] = rTokens; + if( mbPendingSharedFmla ) + setCellFormula( maSharedFmlaAddr, resolveSharedFormula( maSharedBaseAddr ) ); +} + +void SheetDataBuffer::setFormulaCell( const CellModel& rModel, const ApiTokenSequence& rTokens ) +{ + mbPendingSharedFmla = false; + ApiTokenSequence aTokens; + + /* Detect special token passed as placeholder for array formulas, shared + formulas, and table operations. In BIFF, these formulas are represented + by a single tExp resp. tTbl token. If the formula parser finds these + tokens, it puts a single OPCODE_BAD token with the base address and + formula type into the token sequence. This information will be + extracted here, and in case of a shared formula, the shared formula + buffer will generate the resulting formula token array. */ + ApiSpecialTokenInfo aTokenInfo; + if( rTokens.hasElements() && getFormulaParser().extractSpecialTokenInfo( aTokenInfo, rTokens ) ) + { + /* The second member of the token info is set to true, if the formula + represents a table operation, which will be skipped. In BIFF12 it + is not possible to distinguish array and shared formulas + (BIFF5/BIFF8 provide this information with a special flag in the + FORMULA record). */ + if( !aTokenInfo.Second ) + { + /* Construct the token array representing the shared formula. If + the returned sequence is empty, the definition of the shared + formula has not been loaded yet, or the cell is part of an + array formula. In this case, the cell will be remembered. After + reading the formula definition it will be retried to insert the + formula via retryPendingSharedFormulaCell(). */ + ScAddress aTokenAddr( aTokenInfo.First.Column, aTokenInfo.First.Row, aTokenInfo.First.Sheet ); + aTokens = resolveSharedFormula( aTokenAddr ); + if( !aTokens.hasElements() ) + { + maSharedFmlaAddr = rModel.maCellAddr; + maSharedBaseAddr = aTokenAddr; + mbPendingSharedFmla = true; + } + } + } + else + { + // simple formula, use the passed token array + aTokens = rTokens; + } + + setCellFormula( rModel.maCellAddr, aTokens ); + setCellFormat( rModel ); +} + +void SheetDataBuffer::createArrayFormula( const ScRange& rRange, const ApiTokenSequence& rTokens ) +{ + /* Array formulas will be inserted later in finalizeImport(). This is + needed to not disturb collecting all the cells, which will be put into + the sheet in large blocks to increase performance. */ + maArrayFormulas.emplace_back( rRange, rTokens ); +} + +void SheetDataBuffer::createTableOperation( const ScRange& rRange, const DataTableModel& rModel ) +{ + /* Table operations will be inserted later in finalizeImport(). This is + needed to not disturb collecting all the cells, which will be put into + the sheet in large blocks to increase performance. */ + maTableOperations.emplace_back( rRange, rModel ); +} + +void SheetDataBuffer::setRowFormat( sal_Int32 nRow, sal_Int32 nXfId, bool bCustomFormat ) +{ + // set row formatting + if( bCustomFormat ) + { + // try to expand cached row range, if formatting is equal + if( (maXfIdRowRange.maRowRange.mnLast < 0) || !maXfIdRowRange.tryExpand( nRow, nXfId ) ) + { + + maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange ); + maXfIdRowRange.set( nRow, nXfId ); + } + } + else if( maXfIdRowRange.maRowRange.mnLast >= 0 ) + { + // finish last cached row range + maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange ); + maXfIdRowRange.set( -1, -1 ); + } +} + +void SheetDataBuffer::setMergedRange( const ScRange& rRange ) +{ + maMergedRanges.emplace_back( rRange ); +} + +typedef std::pair<sal_Int32, sal_Int32> FormatKeyPair; + +static void addIfNotInMyMap( const StylesBuffer& rStyles, std::map< FormatKeyPair, ScRangeList >& rMap, sal_Int32 nXfId, sal_Int32 nFormatId, const ScRangeList& rRangeList ) +{ + Xf* pXf1 = rStyles.getCellXf( nXfId ).get(); + if ( !pXf1 ) + return; + + auto it = std::find_if(rMap.begin(), rMap.end(), + [&nFormatId, &rStyles, &pXf1](const std::pair<FormatKeyPair, ScRangeList>& rEntry) { + if (rEntry.first.second != nFormatId) + return false; + Xf* pXf2 = rStyles.getCellXf( rEntry.first.first ).get(); + return *pXf1 == *pXf2; + }); + if (it != rMap.end()) // already exists + { + // add ranges from the rangelist to the existing rangelist for the + // matching style ( should we check if they overlap ? ) + it->second.insert(it->second.end(), rRangeList.begin(), rRangeList.end()); + return; + } + rMap[ FormatKeyPair( nXfId, nFormatId ) ] = rRangeList; +} + +void SheetDataBuffer::addColXfStyles() +{ + std::map< FormatKeyPair, ScRangeList > rangeStyleListMap; + for( const auto& [rFormatKeyPair, rRangeList] : maXfIdRangeLists ) + { + addIfNotInMyMap( getStyles(), rangeStyleListMap, rFormatKeyPair.first, rFormatKeyPair.second, rRangeList ); + } + // gather all ranges that have the same style and apply them in bulk + // Collect data in unsorted vectors and sort them just once at the end + // instead of possibly slow repeated inserts. + TmpColStyles tmpStylesPerColumn; + for ( const auto& [rFormatKeyPair, rRanges] : rangeStyleListMap ) + { + for (const ScRange & rAddress : rRanges) + { + RowRangeStyle aStyleRows; + aStyleRows.mnNumFmt.first = rFormatKeyPair.first; + aStyleRows.mnNumFmt.second = rFormatKeyPair.second; + aStyleRows.mnStartRow = rAddress.aStart.Row(); + aStyleRows.mnEndRow = rAddress.aEnd.Row(); + for ( sal_Int32 nCol = rAddress.aStart.Col(); nCol <= rAddress.aEnd.Col(); ++nCol ) + tmpStylesPerColumn[ nCol ].push_back( aStyleRows ); + } + } + for( auto& rowStyles : tmpStylesPerColumn ) + { + TmpRowStyles& s = rowStyles.second; + std::sort( s.begin(), s.end(), StyleRowRangeComp()); + s.erase( std::unique( s.begin(), s.end(), + [](const RowRangeStyle& lhs, const RowRangeStyle& rhs) + // Synthetize operator== from operator < . Do not create an actual operator== + // as operator< is somewhat specific (see StyleRowRangeComp). + { return !StyleRowRangeComp()(lhs,rhs) && !StyleRowRangeComp()(rhs,lhs); } ), + s.end()); + // Broken documents may have overlapping ranges that cause problems, repeat once more. + if(!std::is_sorted(s.begin(), s.end(), StyleRowRangeComp())) + { + std::sort( s.begin(), s.end(), StyleRowRangeComp()); + s.erase( std::unique( s.begin(), s.end(), + [](const RowRangeStyle& lhs, const RowRangeStyle& rhs) + { return !StyleRowRangeComp()(lhs,rhs) && !StyleRowRangeComp()(rhs,lhs); } ), + s.end()); + } + maStylesPerColumn[ rowStyles.first ].insert_sorted_unique_vector( std::move( s )); + } +} + +void SheetDataBuffer::addColXfStyleProcessRowRanges() +{ + // count the number of row-range-styles we have + AddressConverter& rAddrConv = getAddressConverter(); + int cnt = 0; + for ( const auto& [nXfId, rRowRangeList] : maXfIdRowRangeList ) + { + if ( nXfId == -1 ) // it's a dud skip it + continue; + cnt += rRowRangeList.size(); + } + // pre-allocate space in the sorted_vector + for ( sal_Int32 nCol = 0; nCol <= rAddrConv.getMaxApiAddress().Col(); ++nCol ) + { + RowStyles& rRowStyles = maStylesPerColumn[ nCol ]; + rRowStyles.reserve(rRowStyles.size() + cnt); + } + const auto nMaxCol = rAddrConv.getMaxApiAddress().Col(); + for ( sal_Int32 nCol = 0; nCol <= nMaxCol; ++nCol ) + { + RowStyles& rRowStyles = maStylesPerColumn[ nCol ]; + for ( const auto& [nXfId, rRowRangeList] : maXfIdRowRangeList ) + { + if ( nXfId == -1 ) // it's a dud skip it + continue; + // get all row ranges for id + for ( const auto& rRange : rRowRangeList ) + { + RowRangeStyle aStyleRows; + aStyleRows.mnNumFmt.first = nXfId; + aStyleRows.mnNumFmt.second = -1; + + // Reset row range for each column + aStyleRows.mnStartRow = rRange.mnFirst; + aStyleRows.mnEndRow = rRange.mnLast; + + // If aStyleRows includes rows already allocated to a style + // in rRowStyles, then we need to split it into parts. + // ( to occupy only rows that have no style definition) + + // Start iterating at the first element that is not completely before aStyleRows + RowStyles::const_iterator rows_it = rRowStyles.lower_bound(aStyleRows); + bool bAddRange = true; + for ( ; rows_it != rRowStyles.end(); ++rows_it ) + { + // Add the part of aStyleRows that does not overlap with r + if ( aStyleRows.mnStartRow < rows_it->mnStartRow ) + { + RowRangeStyle aSplit = aStyleRows; + aSplit.mnEndRow = std::min(aStyleRows.mnEndRow, rows_it->mnStartRow - 1); + rows_it = rRowStyles.insert( aSplit ).first; + } + + // Done if no part of aStyleRows extends beyond r + if ( aStyleRows.mnEndRow <= rows_it->mnEndRow ) + { + bAddRange = false; + break; + } + + // Cut off the part aStyleRows that was handled above + aStyleRows.mnStartRow = rows_it->mnEndRow + 1; + } + if ( bAddRange ) + rRowStyles.insert( aStyleRows ); + } + } + } +} + +void SheetDataBuffer::finalizeImport() +{ + ScDocumentImport& rDocImport = getDocImport(); + + SCTAB nStartTabInvalidatedIters(SCTAB_MAX); + SCTAB nEndTabInvalidatedIters(0); + + // create all array formulas + for( const auto& [rRange, rTokens] : maArrayFormulas ) + { + finalizeArrayFormula(rRange, rTokens); + + nStartTabInvalidatedIters = std::min(rRange.aStart.Tab(), nStartTabInvalidatedIters); + nEndTabInvalidatedIters = std::max(rRange.aEnd.Tab(), nEndTabInvalidatedIters); + } + + for (SCTAB nTab = nStartTabInvalidatedIters; nTab <= nEndTabInvalidatedIters; ++nTab) + rDocImport.invalidateBlockPositionSet(nTab); + + // create all table operations + for( const auto& [rRange, rModel] : maTableOperations ) + finalizeTableOperation( rRange, rModel ); + + // write default formatting of remaining row range + maXfIdRowRangeList[ maXfIdRowRange.mnXfId ].push_back( maXfIdRowRange.maRowRange ); + + addColXfStyles(); + + addColXfStyleProcessRowRanges(); + + ScDocument& rDoc = rDocImport.getDoc(); + StylesBuffer& rStyles = getStyles(); + ScDocumentImport::Attrs aPendingAttrParam; + SCCOL pendingColStart = -1; + SCCOL pendingColEnd = -1; + for ( const auto& [rCol, rRowStyles] : maStylesPerColumn ) + { + SCCOL nScCol = static_cast< SCCOL >( rCol ); + + // tdf#91567 Get pattern from the first row without AutoFilter + const ScPatternAttr* pDefPattern = nullptr; + bool bAutoFilter = true; + SCROW nScRow = 0; + while ( bAutoFilter && nScRow < rDoc.MaxRow() ) + { + pDefPattern = rDoc.GetPattern( nScCol, nScRow, getSheetIndex() ); + if ( pDefPattern ) + { + const ScMergeFlagAttr* pAttr = pDefPattern->GetItemSet().GetItem( ATTR_MERGE_FLAG ); + bAutoFilter = pAttr->HasAutoFilter(); + } + else + break; + nScRow++; + } + if ( !pDefPattern || nScRow == rDoc.MaxRow() ) + pDefPattern = rDoc.GetDefPattern(); + + Xf::AttrList aAttrs(pDefPattern); + for ( const auto& rRowStyle : rRowStyles ) + { + Xf* pXf = rStyles.getCellXf( rRowStyle.mnNumFmt.first ).get(); + + if ( pXf ) + pXf->applyPatternToAttrList( aAttrs, rRowStyle.mnStartRow, rRowStyle.mnEndRow, rRowStyle.mnNumFmt.second ); + } + if (aAttrs.maAttrs.empty() || aAttrs.maAttrs.back().nEndRow != rDoc.MaxRow()) + { + ScAttrEntry aEntry; + aEntry.nEndRow = rDoc.MaxRow(); + aEntry.pPattern = pDefPattern; + rDoc.GetPool()->Put(*aEntry.pPattern); + aAttrs.maAttrs.push_back(aEntry); + + if (!sc::NumFmtUtil::isLatinScript(*aEntry.pPattern, rDoc)) + aAttrs.mbLatinNumFmtOnly = false; + } + + ScDocumentImport::Attrs aAttrParam; + aAttrParam.mvData.swap(aAttrs.maAttrs); + aAttrParam.mbLatinNumFmtOnly = aAttrs.mbLatinNumFmtOnly; + + // Compress setting the attributes, set the same set in one call. + if( pendingColStart != -1 && pendingColEnd == nScCol - 1 && aAttrParam == aPendingAttrParam ) + ++pendingColEnd; + else + { + if( pendingColStart != -1 ) + rDocImport.setAttrEntries(getSheetIndex(), pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + pendingColStart = pendingColEnd = nScCol; + aPendingAttrParam = std::move( aAttrParam ); + } + } + if( pendingColStart != -1 ) + rDocImport.setAttrEntries(getSheetIndex(), pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + + // merge all cached merged ranges and update right/bottom cell borders + for( const auto& rMergedRange : maMergedRanges ) + applyCellMerging( rMergedRange.maRange ); + for( const auto& rCenterFillRange : maCenterFillRanges ) + applyCellMerging( rCenterFillRange.maRange ); +} + +// private -------------------------------------------------------------------- + +SheetDataBuffer::XfIdRowRange::XfIdRowRange() : + maRowRange( -1 ), + mnXfId( -1 ) +{ +} + +void SheetDataBuffer::XfIdRowRange::set( sal_Int32 nRow, sal_Int32 nXfId ) +{ + maRowRange = ValueRange( nRow ); + mnXfId = nXfId; +} + +bool SheetDataBuffer::XfIdRowRange::tryExpand( sal_Int32 nRow, sal_Int32 nXfId ) +{ + if( mnXfId == nXfId ) + { + if( maRowRange.mnLast + 1 == nRow ) + { + ++maRowRange.mnLast; + return true; + } + if( maRowRange.mnFirst == nRow + 1 ) + { + --maRowRange.mnFirst; + return true; + } + } + return false; +} + +SheetDataBuffer::MergedRange::MergedRange( const ScRange& rRange ) : + maRange( rRange ), + mnHorAlign( XML_TOKEN_INVALID ) +{ +} + +SheetDataBuffer::MergedRange::MergedRange( const ScAddress& rAddress, sal_Int32 nHorAlign ) : + maRange( rAddress, rAddress ), + mnHorAlign( nHorAlign ) +{ +} + +bool SheetDataBuffer::MergedRange::tryExpand( const ScAddress& rAddress, sal_Int32 nHorAlign ) +{ + if( (mnHorAlign == nHorAlign) && (maRange.aStart.Row() == rAddress.Row() ) && + (maRange.aEnd.Row() == rAddress.Row() ) && (maRange.aEnd.Col() + 1 == rAddress.Col() ) ) + { + maRange.aEnd.IncCol(); + return true; + } + return false; +} + +void SheetDataBuffer::setCellFormula( const ScAddress& rCellAddr, const ApiTokenSequence& rTokens ) +{ + if( rTokens.hasElements() ) + { + putFormulaTokens( rCellAddr, rTokens ); + } +} + + +ApiTokenSequence SheetDataBuffer::resolveSharedFormula( const ScAddress& rAddr ) const +{ + BinAddress aAddr(rAddr); + ApiTokenSequence aTokens = ContainerHelper::getMapElement( maSharedFormulas, aAddr, ApiTokenSequence() ); + return aTokens; +} + +void SheetDataBuffer::finalizeArrayFormula( const ScRange& rRange, const ApiTokenSequence& rTokens ) const +{ + Reference< XArrayFormulaTokens > xTokens( getCellRange( rRange ), UNO_QUERY ); + OSL_ENSURE( xTokens.is(), "SheetDataBuffer::finalizeArrayFormula - missing formula token interface" ); + if( xTokens.is() ) + xTokens->setArrayTokens( rTokens ); +} + +void SheetDataBuffer::finalizeTableOperation( const ScRange& rRange, const DataTableModel& rModel ) +{ + if (rModel.mbRef1Deleted) + return; + + if (rModel.maRef1.isEmpty()) + return; + + if (rRange.aStart.Col() <= 0 || rRange.aStart.Row() <= 0) + return; + + sal_Int16 nSheet = getSheetIndex(); + + ScAddress aRef1( 0, 0, 0 ); + if (!getAddressConverter().convertToCellAddress(aRef1, rModel.maRef1, nSheet, true)) + return; + + ScDocumentImport& rDoc = getDocImport(); + ScTabOpParam aParam; + + ScRange aScRange(rRange); + + if (rModel.mb2dTable) + { + // Two-variable data table. + if (rModel.mbRef2Deleted) + return; + + if (rModel.maRef2.isEmpty()) + return; + + ScAddress aRef2( 0, 0, 0 ); + if (!getAddressConverter().convertToCellAddress(aRef2, rModel.maRef2, nSheet, true)) + return; + + aParam.meMode = ScTabOpParam::Both; + + aScRange.aStart.IncCol(-1); + aScRange.aStart.IncRow(-1); + + aParam.aRefFormulaCell.Set(aScRange.aStart.Col(), aScRange.aStart.Row(), nSheet, false, false, false); + aParam.aRefFormulaEnd = aParam.aRefFormulaCell; + + // Ref1 is row input cell and Ref2 is column input cell. + aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false); + aParam.aRefColCell.Set(aRef2.Col(), aRef2.Row(), aRef2.Tab(), false, false, false); + rDoc.setTableOpCells(aScRange, aParam); + + return; + } + + // One-variable data table. + + if (rModel.mbRowTable) + { + // One-variable row input cell (horizontal). + aParam.meMode = ScTabOpParam::Row; + aParam.aRefRowCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false); + aParam.aRefFormulaCell.Set(rRange.aStart.Col()-1, rRange.aStart.Row(), nSheet, false, true, false); + aParam.aRefFormulaEnd = aParam.aRefFormulaCell; + aScRange.aStart.IncRow(-1); + rDoc.setTableOpCells(aScRange, aParam); + } + else + { + // One-variable column input cell (vertical). + aParam.meMode = ScTabOpParam::Column; + aParam.aRefColCell.Set(aRef1.Col(), aRef1.Row(), aRef1.Tab(), false, false, false); + aParam.aRefFormulaCell.Set(rRange.aStart.Col(), rRange.aStart.Row()-1, nSheet, true, false, false); + aParam.aRefFormulaEnd = aParam.aRefFormulaCell; + aScRange.aStart.IncCol(-1); + rDoc.setTableOpCells(aScRange, aParam); + } +} + +void SheetDataBuffer::setCellFormat( const CellModel& rModel ) +{ + if( rModel.mnXfId < 0 ) + return; + + ScRangeList& rRangeList = maXfIdRangeLists[ XfIdNumFmtKey( rModel.mnXfId, -1 ) ]; + ScRange* pLastRange = rRangeList.empty() ? nullptr : &rRangeList.back(); + /* The xlsx sheet data contains row wise information. + * It is sufficient to check if the row range size is one + */ + if (!rRangeList.empty() && + *pLastRange == rModel.maCellAddr) + ; // do nothing - this probably bad data + else if (!rRangeList.empty() && + pLastRange->aStart.Tab() == rModel.maCellAddr.Tab() && + pLastRange->aStart.Row() == pLastRange->aEnd.Row() && + pLastRange->aStart.Row() == rModel.maCellAddr.Row() && + pLastRange->aEnd.Col() + 1 == rModel.maCellAddr.Col()) + { + pLastRange->aEnd.IncCol(); // Expand Column + } + else + { + rRangeList.push_back(ScRange(rModel.maCellAddr)); + pLastRange = &rRangeList.back(); + } + + if (rRangeList.size() > 1) + { + for (size_t i = rRangeList.size() - 1; i != 0; --i) + { + ScRange& rMergeRange = rRangeList[i - 1]; + if (pLastRange->aStart.Tab() != rMergeRange.aStart.Tab()) + break; + + /* Try to merge this with the previous range */ + if (pLastRange->aStart.Row() == (rMergeRange.aEnd.Row() + 1) && + pLastRange->aStart.Col() == rMergeRange.aStart.Col() && + pLastRange->aEnd.Col() == rMergeRange.aEnd.Col()) + { + rMergeRange.aEnd.SetRow(pLastRange->aEnd.Row()); + rRangeList.Remove(rRangeList.size() - 1); + break; + } + else if (pLastRange->aStart.Row() > (rMergeRange.aEnd.Row() + 1)) + break; // Un-necessary to check with any other rows + } + } + // update merged ranges for 'center across selection' and 'fill' + const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get(); + if( !pXf ) + return; + + sal_Int32 nHorAlign = pXf->getAlignment().getModel().mnHorAlign; + if( (nHorAlign == XML_centerContinuous) || (nHorAlign == XML_fill) ) + { + /* start new merged range, if cell is not empty (#108781#), + or try to expand last range with empty cell */ + if( rModel.mnCellType != XML_TOKEN_INVALID ) + maCenterFillRanges.emplace_back( rModel.maCellAddr, nHorAlign ); + else if( !maCenterFillRanges.empty() ) + maCenterFillRanges.rbegin()->tryExpand( rModel.maCellAddr, nHorAlign ); + } +} + +static void lcl_SetBorderLine( ScDocument& rDoc, const ScRange& rRange, SCTAB nScTab, SvxBoxItemLine nLine ) +{ + SCCOL nFromScCol = (nLine == SvxBoxItemLine::RIGHT) ? rRange.aEnd.Col() : rRange.aStart.Col(); + SCROW nFromScRow = (nLine == SvxBoxItemLine::BOTTOM) ? rRange.aEnd.Row() : rRange.aStart.Row(); + + const SvxBoxItem* pFromItem = + rDoc.GetAttr( nFromScCol, nFromScRow, nScTab, ATTR_BORDER ); + const SvxBoxItem* pToItem = + rDoc.GetAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, ATTR_BORDER ); + + SvxBoxItem aNewItem( *pToItem ); + aNewItem.SetLine( pFromItem->GetLine( nLine ), nLine ); + rDoc.ApplyAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, aNewItem ); +} + +void SheetDataBuffer::applyCellMerging( const ScRange& rRange ) +{ + bool bMultiCol = rRange.aStart.Col() < rRange.aEnd.Col(); + bool bMultiRow = rRange.aStart.Row() < rRange.aEnd.Row(); + + const ScAddress& rStart = rRange.aStart; + const ScAddress& rEnd = rRange.aEnd; + ScDocument& rDoc = getScDocument(); + // set correct right border + if( bMultiCol ) + lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::RIGHT ); + // set correct lower border + if( bMultiRow ) + lcl_SetBorderLine( rDoc, rRange, getSheetIndex(), SvxBoxItemLine::BOTTOM ); + // do merge + if( bMultiCol || bMultiRow ) + rDoc.DoMerge( rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row(), getSheetIndex() ); +} + +} // namespace oox + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |