/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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& 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: */