summaryrefslogtreecommitdiff
path: root/oox/source/xls/sheetdatabuffer.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'oox/source/xls/sheetdatabuffer.cxx')
-rwxr-xr-xoox/source/xls/sheetdatabuffer.cxx935
1 files changed, 935 insertions, 0 deletions
diff --git a/oox/source/xls/sheetdatabuffer.cxx b/oox/source/xls/sheetdatabuffer.cxx
new file mode 100755
index 000000000000..8bdcccca10cf
--- /dev/null
+++ b/oox/source/xls/sheetdatabuffer.cxx
@@ -0,0 +1,935 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+#include "oox/xls/sheetdatabuffer.hxx"
+
+#include <algorithm>
+#include <com/sun/star/sheet/XArrayFormulaTokens.hpp>
+#include <com/sun/star/sheet/XCellRangeData.hpp>
+#include <com/sun/star/sheet/XFormulaTokens.hpp>
+#include <com/sun/star/sheet/XMultipleOperation.hpp>
+#include <com/sun/star/table/XCell.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/util/DateTime.hpp>
+#include <com/sun/star/util/NumberFormat.hpp>
+#include <com/sun/star/util/XMergeable.hpp>
+#include <com/sun/star/util/XNumberFormatTypes.hpp>
+#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
+#include <rtl/ustrbuf.hxx>
+#include "oox/helper/containerhelper.hxx"
+#include "oox/helper/propertymap.hxx"
+#include "oox/helper/propertyset.hxx"
+#include "oox/token/tokens.hxx"
+#include "oox/xls/addressconverter.hxx"
+#include "oox/xls/biffinputstream.hxx"
+#include "oox/xls/formulaparser.hxx"
+#include "oox/xls/sharedstringsbuffer.hxx"
+#include "oox/xls/unitconverter.hxx"
+
+namespace oox {
+namespace xls {
+
+// ============================================================================
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::sheet;
+using namespace ::com::sun::star::table;
+using namespace ::com::sun::star::text;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+
+using ::rtl::OUString;
+using ::rtl::OUStringBuffer;
+
+// ============================================================================
+
+CellModel::CellModel() :
+ mnCellType( XML_TOKEN_INVALID ),
+ mnXfId( -1 ),
+ mbShowPhonetic( false )
+{
+}
+
+// ----------------------------------------------------------------------------
+
+CellFormulaModel::CellFormulaModel() :
+ mnFormulaType( XML_TOKEN_INVALID ),
+ mnSharedId( -1 )
+{
+}
+
+bool CellFormulaModel::isValidArrayRef( const CellAddress& rCellAddr )
+{
+ return
+ (maFormulaRef.Sheet == rCellAddr.Sheet) &&
+ (maFormulaRef.StartColumn == rCellAddr.Column) &&
+ (maFormulaRef.StartRow == rCellAddr.Row);
+}
+
+bool CellFormulaModel::isValidSharedRef( const CellAddress& rCellAddr )
+{
+ return
+ (maFormulaRef.Sheet == rCellAddr.Sheet) &&
+ (maFormulaRef.StartColumn <= rCellAddr.Column) && (rCellAddr.Column <= maFormulaRef.EndColumn) &&
+ (maFormulaRef.StartRow <= rCellAddr.Row) && (rCellAddr.Row <= maFormulaRef.EndRow);
+}
+
+// ----------------------------------------------------------------------------
+
+DataTableModel::DataTableModel() :
+ mb2dTable( false ),
+ mbRowTable( false ),
+ mbRef1Deleted( false ),
+ mbRef2Deleted( false )
+{
+}
+
+// ============================================================================
+
+namespace {
+
+const sal_Int32 CELLBLOCK_MAXROWS = 16; /// Number of rows in a cell block.
+
+} // namespace
+
+CellBlock::CellBlock( const WorksheetHelper& rHelper, const ValueRange& rColSpan, sal_Int32 nRow ) :
+ WorksheetHelper( rHelper ),
+ maRange( rHelper.getSheetIndex(), rColSpan.mnFirst, nRow, rColSpan.mnLast, nRow ),
+ mnRowLength( rColSpan.mnLast - rColSpan.mnFirst + 1 ),
+ mnFirstFreeIndex( 0 )
+{
+ maCellArray.realloc( 1 );
+ maCellArray[ 0 ].realloc( mnRowLength );
+ mpCurrCellRow = maCellArray[ 0 ].getArray();
+}
+
+bool CellBlock::isExpandable( const ValueRange& rColSpan ) const
+{
+ return (maRange.StartColumn == rColSpan.mnFirst) && (maRange.EndColumn == rColSpan.mnLast);
+}
+
+bool CellBlock::isBefore( const ValueRange& rColSpan ) const
+{
+ return (maRange.EndColumn < rColSpan.mnLast) ||
+ ((maRange.EndColumn == rColSpan.mnLast) && (maRange.StartColumn != rColSpan.mnFirst));
+}
+
+bool CellBlock::contains( sal_Int32 nCol ) const
+{
+ return (maRange.StartColumn <= nCol) && (nCol <= maRange.EndColumn);
+}
+
+void CellBlock::insertRichString( const CellAddress& rAddress, const RichStringRef& rxString, const Font* pFirstPortionFont )
+{
+ maRichStrings.push_back( RichStringCell( rAddress, rxString, pFirstPortionFont ) );
+}
+
+void CellBlock::startNextRow()
+{
+ // fill last cells in current row with empty strings (placeholder for empty cells)
+ fillUnusedCells( mnRowLength );
+ // flush if the cell block reaches maximum size
+ if( maCellArray.getLength() == CELLBLOCK_MAXROWS )
+ {
+ finalizeImport();
+ maRange.StartRow = ++maRange.EndRow;
+ maCellArray.realloc( 1 );
+ mpCurrCellRow = maCellArray[ 0 ].getArray();
+ }
+ else
+ {
+ // prepare next row
+ ++maRange.EndRow;
+ sal_Int32 nRowCount = maCellArray.getLength();
+ maCellArray.realloc( nRowCount + 1 );
+ maCellArray[ nRowCount ].realloc( mnRowLength );
+ mpCurrCellRow = maCellArray[ nRowCount ].getArray();
+ }
+ mnFirstFreeIndex = 0;
+}
+
+Any& CellBlock::getCellAny( sal_Int32 nCol )
+{
+ OSL_ENSURE( contains( nCol ), "CellBlock::getCellAny - invalid column" );
+ // fill cells before passed column with empty strings (the placeholder for empty cells)
+ sal_Int32 nIndex = nCol - maRange.StartColumn;
+ fillUnusedCells( nIndex );
+ mnFirstFreeIndex = nIndex + 1;
+ return mpCurrCellRow[ nIndex ];
+}
+
+void CellBlock::finalizeImport()
+{
+ // fill last cells in last row with empty strings (placeholder for empty cells)
+ fillUnusedCells( mnRowLength );
+ // insert all buffered cells into the Calc sheet
+ try
+ {
+ Reference< XCellRangeData > xRangeData( getCellRange( maRange ), UNO_QUERY_THROW );
+ xRangeData->setDataArray( maCellArray );
+ }
+ catch( Exception& )
+ {
+ }
+ // insert uncacheable cells separately
+ for( RichStringCellList::const_iterator aIt = maRichStrings.begin(), aEnd = maRichStrings.end(); aIt != aEnd; ++aIt )
+ putRichString( aIt->maCellAddr, *aIt->mxString, aIt->mpFirstPortionFont );
+}
+
+// private --------------------------------------------------------------------
+
+CellBlock::RichStringCell::RichStringCell( const CellAddress& rCellAddr, const RichStringRef& rxString, const Font* pFirstPortionFont ) :
+ maCellAddr( rCellAddr ),
+ mxString( rxString ),
+ mpFirstPortionFont( pFirstPortionFont )
+{
+}
+
+void CellBlock::fillUnusedCells( sal_Int32 nIndex )
+{
+ if( mnFirstFreeIndex < nIndex )
+ {
+ Any* pCellEnd = mpCurrCellRow + nIndex;
+ for( Any* pCell = mpCurrCellRow + mnFirstFreeIndex; pCell < pCellEnd; ++pCell )
+ *pCell <<= OUString();
+ }
+}
+
+// ============================================================================
+
+CellBlockBuffer::CellBlockBuffer( const WorksheetHelper& rHelper ) :
+ WorksheetHelper( rHelper ),
+ mnCurrRow( -1 )
+{
+ maCellBlockIt = maCellBlocks.end();
+}
+
+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) && (maColSpans.count( nRow ) == 0) )
+ maColSpans[ nRow ] = rColSpans.getRanges();
+}
+
+CellBlock* CellBlockBuffer::getCellBlock( const CellAddress& rCellAddr )
+{
+ OSL_ENSURE( rCellAddr.Row >= mnCurrRow, "CellBlockBuffer::getCellBlock - passed row out of order" );
+ // prepare cell blocks, if row changes
+ if( rCellAddr.Row != mnCurrRow )
+ {
+ // find colspans for the new row
+ ColSpanVectorMap::iterator aIt = maColSpans.find( rCellAddr.Row );
+
+ /* Gap between rows, or rows out of order, or no colspan
+ information for the new row found: flush all open cell blocks. */
+ if( (aIt == maColSpans.end()) || (rCellAddr.Row != mnCurrRow + 1) )
+ {
+ finalizeImport();
+ maCellBlocks.clear();
+ maCellBlockIt = maCellBlocks.end();
+ }
+
+ /* Prepare matching cell blocks, create new cell blocks, finalize
+ unmatching cell blocks, if colspan information is available. */
+ if( aIt != maColSpans.end() )
+ {
+ /* The colspan vector aIt points to is sorted by columns, as well
+ as the cell block map. In the folloing, this vector and the
+ list of cell blocks can be iterated simultanously. */
+ CellBlockMap::iterator aMIt = maCellBlocks.begin();
+ const ValueRangeVector& rColRanges = aIt->second;
+ for( ValueRangeVector::const_iterator aVIt = rColRanges.begin(), aVEnd = rColRanges.end(); aVIt != aVEnd; ++aVIt, ++aMIt )
+ {
+ const ValueRange& rColSpan = *aVIt;
+ /* Finalize and remove all cell blocks up to end of the column
+ range (cell blocks are keyed by end column index).
+ CellBlock::isBefore() returns true, if the end index of the
+ passed colspan is greater than the column end index of the
+ cell block, or if the passed range has the same end index
+ but the start indexes do not match. */
+ while( (aMIt != maCellBlocks.end()) && aMIt->second->isBefore( rColSpan ) )
+ {
+ aMIt->second->finalizeImport();
+ maCellBlocks.erase( aMIt++ );
+ }
+ /* If the current cell block (aMIt) fits to the colspan, start
+ a new row there, otherwise create and insert a new cell block. */
+ if( (aMIt != maCellBlocks.end()) && aMIt->second->isExpandable( rColSpan ) )
+ aMIt->second->startNextRow();
+ else
+ aMIt = maCellBlocks.insert( aMIt, CellBlockMap::value_type( rColSpan.mnLast,
+ CellBlockMap::mapped_type( new CellBlock( *this, rColSpan, rCellAddr.Row ) ) ) );
+ }
+ // finalize and remove all remaining cell blocks
+ CellBlockMap::iterator aMEnd = maCellBlocks.end();
+ for( CellBlockMap::iterator aMIt2 = aMIt; aMIt2 != aMEnd; ++aMIt2 )
+ aMIt2->second->finalizeImport();
+ maCellBlocks.erase( aMIt, aMEnd );
+
+ // remove cached colspan information (including current one aIt points to)
+ maColSpans.erase( maColSpans.begin(), ++aIt );
+ }
+ maCellBlockIt = maCellBlocks.begin();
+ mnCurrRow = rCellAddr.Row;
+ }
+
+ // try to find a valid cell block (update maCellBlockIt)
+ if( ((maCellBlockIt != maCellBlocks.end()) && maCellBlockIt->second->contains( rCellAddr.Column )) ||
+ (((maCellBlockIt = maCellBlocks.lower_bound( rCellAddr.Column )) != maCellBlocks.end()) && maCellBlockIt->second->contains( rCellAddr.Column )) )
+ {
+ // maCellBlockIt points to valid cell block
+ return maCellBlockIt->second.get();
+ }
+
+ // no valid cell block found
+ return 0;
+}
+
+void CellBlockBuffer::finalizeImport()
+{
+ maCellBlocks.forEachMem( &CellBlock::finalizeImport );
+}
+
+// ============================================================================
+
+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 )
+{
+ if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
+ pCellBlock->getCellAny( rModel.maCellAddr.Column ) <<= fValue;
+ else
+ putValue( rModel.maCellAddr, fValue );
+ setCellFormat( rModel );
+}
+
+void SheetDataBuffer::setStringCell( const CellModel& rModel, const OUString& rText )
+{
+ if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
+ pCellBlock->getCellAny( rModel.maCellAddr.Column ) <<= rText;
+ else
+ putString( rModel.maCellAddr, rText );
+ setCellFormat( rModel );
+}
+
+void SheetDataBuffer::setStringCell( const CellModel& rModel, const RichStringRef& rxString )
+{
+ OSL_ENSURE( rxString.get(), "SheetDataBuffer::setStringCell - missing rich string object" );
+ const Font* pFirstPortionFont = getStyles().getFontFromCellXf( rModel.mnXfId ).get();
+ OUString aText;
+ if( rxString->extractPlainString( aText, pFirstPortionFont ) )
+ {
+ setStringCell( rModel, aText );
+ }
+ else
+ {
+ if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
+ pCellBlock->insertRichString( rModel.maCellAddr, rxString, pFirstPortionFont );
+ else
+ putRichString( rModel.maCellAddr, *rxString, pFirstPortionFont );
+ setCellFormat( rModel );
+ }
+}
+
+void SheetDataBuffer::setStringCell( const CellModel& rModel, sal_Int32 nStringId )
+{
+ RichStringRef xString = getSharedStrings().getString( nStringId );
+ if( xString.get() )
+ setStringCell( rModel, xString );
+ else
+ setBlankCell( rModel );
+}
+
+void SheetDataBuffer::setDateTimeCell( const CellModel& rModel, const 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);
+ setStandardNumFmt( rModel.maCellAddr, nStdFmt );
+}
+
+void SheetDataBuffer::setBooleanCell( const CellModel& rModel, bool bValue )
+{
+ setCellFormula( rModel.maCellAddr, getFormulaParser().convertBoolToFormula( bValue ) );
+ // #108770# set 'Standard' number format for all Boolean cells
+ setCellFormat( rModel, 0 );
+}
+
+void SheetDataBuffer::setErrorCell( const CellModel& rModel, const OUString& rErrorCode )
+{
+ setErrorCell( rModel, getUnitConverter().calcBiffErrorCode( rErrorCode ) );
+}
+
+void SheetDataBuffer::setErrorCell( const CellModel& rModel, sal_uInt8 nErrorCode )
+{
+ setCellFormula( rModel.maCellAddr, getFormulaParser().convertErrorToFormula( nErrorCode ) );
+ setCellFormat( rModel );
+}
+
+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(). */
+ BinAddress aBaseAddr( aTokenInfo.First );
+ aTokens = resolveSharedFormula( aBaseAddr );
+ if( !aTokens.hasElements() )
+ {
+ maSharedFmlaAddr = rModel.maCellAddr;
+ maSharedBaseAddr = aBaseAddr;
+ mbPendingSharedFmla = true;
+ }
+ }
+ }
+ else
+ {
+ // simple formula, use the passed token array
+ aTokens = rTokens;
+ }
+
+ setCellFormula( rModel.maCellAddr, aTokens );
+ setCellFormat( rModel );
+}
+
+void SheetDataBuffer::setFormulaCell( const CellModel& rModel, sal_Int32 nSharedId )
+{
+ setCellFormula( rModel.maCellAddr, resolveSharedFormula( BinAddress( nSharedId, 0 ) ) );
+ setCellFormat( rModel );
+}
+
+void SheetDataBuffer::createArrayFormula( const CellRangeAddress& 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.push_back( ArrayFormula( rRange, rTokens ) );
+}
+
+void SheetDataBuffer::createTableOperation( const CellRangeAddress& 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.push_back( TableOperation( rRange, rModel ) );
+}
+
+void SheetDataBuffer::createSharedFormula( sal_Int32 nSharedId, const ApiTokenSequence& rTokens )
+{
+ createSharedFormula( BinAddress( nSharedId, 0 ), rTokens );
+}
+
+void SheetDataBuffer::createSharedFormula( const CellAddress& rCellAddr, const ApiTokenSequence& rTokens )
+{
+ createSharedFormula( BinAddress( rCellAddr ), rTokens );
+}
+
+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 ) )
+ {
+ writeXfIdRowRangeProperties( maXfIdRowRange );
+ maXfIdRowRange.set( nRow, nXfId );
+ }
+ }
+ else if( maXfIdRowRange.maRowRange.mnLast >= 0 )
+ {
+ // finish last cached row range
+ writeXfIdRowRangeProperties( maXfIdRowRange );
+ maXfIdRowRange.set( -1, -1 );
+ }
+}
+
+void SheetDataBuffer::setMergedRange( const CellRangeAddress& rRange )
+{
+ maMergedRanges.push_back( MergedRange( rRange ) );
+}
+
+void SheetDataBuffer::setStandardNumFmt( const CellAddress& rCellAddr, sal_Int16 nStdNumFmt )
+{
+ try
+ {
+ Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY_THROW );
+ Reference< XNumberFormatTypes > xNumFmtTypes( xNumFmtsSupp->getNumberFormats(), UNO_QUERY_THROW );
+ sal_Int32 nIndex = xNumFmtTypes->getStandardFormat( nStdNumFmt, Locale() );
+ PropertySet aPropSet( getCell( rCellAddr ) );
+ aPropSet.setProperty( PROP_NumberFormat, nIndex );
+ }
+ catch( Exception& )
+ {
+ }
+}
+
+void SheetDataBuffer::finalizeImport()
+{
+ // insert all cells of all open cell blocks
+ maCellBlocks.finalizeImport();
+
+ // create all array formulas
+ for( ArrayFormulaList::iterator aIt = maArrayFormulas.begin(), aEnd = maArrayFormulas.end(); aIt != aEnd; ++aIt )
+ finalizeArrayFormula( aIt->first, aIt->second );
+
+ // create all table operations
+ for( TableOperationList::iterator aIt = maTableOperations.begin(), aEnd = maTableOperations.end(); aIt != aEnd; ++aIt )
+ finalizeTableOperation( aIt->first, aIt->second );
+
+ // write default formatting of remaining row range
+ writeXfIdRowRangeProperties( maXfIdRowRange );
+
+ // try to merge remaining inserted ranges
+ mergeXfIdRanges();
+ // write all formatting
+ for( XfIdRangeMap::const_iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end(); aIt != aEnd; ++aIt )
+ writeXfIdRangeProperties( aIt->second );
+
+ // merge all cached merged ranges and update right/bottom cell borders
+ for( MergedRangeList::iterator aIt = maMergedRanges.begin(), aEnd = maMergedRanges.end(); aIt != aEnd; ++aIt )
+ finalizeMergedRange( aIt->maRange );
+ for( MergedRangeList::iterator aIt = maCenterFillRanges.begin(), aEnd = maCenterFillRanges.end(); aIt != aEnd; ++aIt )
+ finalizeMergedRange( aIt->maRange );
+}
+
+// private --------------------------------------------------------------------
+
+SheetDataBuffer::XfIdRowRange::XfIdRowRange() :
+ maRowRange( -1 ),
+ mnXfId( -1 )
+{
+}
+
+bool SheetDataBuffer::XfIdRowRange::intersects( const CellRangeAddress& rRange ) const
+{
+ return (rRange.StartRow <= maRowRange.mnLast) && (maRowRange.mnFirst <= rRange.EndRow);
+}
+
+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;
+}
+
+void SheetDataBuffer::XfIdRange::set( const CellAddress& rCellAddr, sal_Int32 nXfId, sal_Int32 nNumFmtId )
+{
+ maRange.Sheet = rCellAddr.Sheet;
+ maRange.StartColumn = maRange.EndColumn = rCellAddr.Column;
+ maRange.StartRow = maRange.EndRow = rCellAddr.Row;
+ mnXfId = nXfId;
+ mnNumFmtId = nNumFmtId;
+}
+
+bool SheetDataBuffer::XfIdRange::tryExpand( const CellAddress& rCellAddr, sal_Int32 nXfId, sal_Int32 nNumFmtId )
+{
+ if( (mnXfId == nXfId) && (mnNumFmtId == nNumFmtId) &&
+ (maRange.StartRow == rCellAddr.Row) &&
+ (maRange.EndRow == rCellAddr.Row) &&
+ (maRange.EndColumn + 1 == rCellAddr.Column) )
+ {
+ ++maRange.EndColumn;
+ return true;
+ }
+ return false;
+}
+
+bool SheetDataBuffer::XfIdRange::tryMerge( const XfIdRange& rXfIdRange )
+{
+ if( (mnXfId == rXfIdRange.mnXfId) &&
+ (mnNumFmtId == rXfIdRange.mnNumFmtId) &&
+ (maRange.EndRow + 1 == rXfIdRange.maRange.StartRow) &&
+ (maRange.StartColumn == rXfIdRange.maRange.StartColumn) &&
+ (maRange.EndColumn == rXfIdRange.maRange.EndColumn) )
+ {
+ maRange.EndRow = rXfIdRange.maRange.EndRow;
+ return true;
+ }
+ return false;
+}
+
+
+SheetDataBuffer::MergedRange::MergedRange( const CellRangeAddress& rRange ) :
+ maRange( rRange ),
+ mnHorAlign( XML_TOKEN_INVALID )
+{
+}
+
+SheetDataBuffer::MergedRange::MergedRange( const CellAddress& rAddress, sal_Int32 nHorAlign ) :
+ maRange( rAddress.Sheet, rAddress.Column, rAddress.Row, rAddress.Column, rAddress.Row ),
+ mnHorAlign( nHorAlign )
+{
+}
+
+bool SheetDataBuffer::MergedRange::tryExpand( const CellAddress& rAddress, sal_Int32 nHorAlign )
+{
+ if( (mnHorAlign == nHorAlign) && (maRange.StartRow == rAddress.Row) &&
+ (maRange.EndRow == rAddress.Row) && (maRange.EndColumn + 1 == rAddress.Column) )
+ {
+ ++maRange.EndColumn;
+ return true;
+ }
+ return false;
+}
+
+// ----------------------------------------------------------------------------
+
+void SheetDataBuffer::setCellFormula( const CellAddress& rCellAddr, const ApiTokenSequence& rTokens )
+{
+ if( rTokens.hasElements() )
+ {
+ if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rCellAddr ) )
+ pCellBlock->getCellAny( rCellAddr.Column ) <<= rTokens;
+ else
+ putFormulaTokens( rCellAddr, rTokens );
+ }
+}
+
+void SheetDataBuffer::createSharedFormula( const BinAddress& rMapKey, const ApiTokenSequence& rTokens )
+{
+ // create the defined name that will represent the shared formula
+ OUString aName = OUStringBuffer().appendAscii( RTL_CONSTASCII_STRINGPARAM( "__shared_" ) ).
+ append( static_cast< sal_Int32 >( getSheetIndex() + 1 ) ).
+ append( sal_Unicode( '_' ) ).append( rMapKey.mnRow ).
+ append( sal_Unicode( '_' ) ).append( rMapKey.mnCol ).makeStringAndClear();
+ Reference< XNamedRange > xNamedRange = createNamedRangeObject( aName );
+ OSL_ENSURE( xNamedRange.is(), "SheetDataBuffer::createSharedFormula - cannot create shared formula" );
+ PropertySet aNameProps( xNamedRange );
+ aNameProps.setProperty( PROP_IsSharedFormula, true );
+
+ // get and store the token index of the defined name
+ OSL_ENSURE( maSharedFormulas.count( rMapKey ) == 0, "SheetDataBuffer::createSharedFormula - shared formula exists already" );
+ sal_Int32 nTokenIndex = 0;
+ if( aNameProps.getProperty( nTokenIndex, PROP_TokenIndex ) && (nTokenIndex >= 0) ) try
+ {
+ // store the token index in the map
+ maSharedFormulas[ rMapKey ] = nTokenIndex;
+ // set the formula definition
+ Reference< XFormulaTokens > xTokens( xNamedRange, UNO_QUERY_THROW );
+ xTokens->setTokens( rTokens );
+ // retry to insert a pending shared formula cell
+ if( mbPendingSharedFmla )
+ setCellFormula( maSharedFmlaAddr, resolveSharedFormula( maSharedBaseAddr ) );
+ }
+ catch( Exception& )
+ {
+ }
+ mbPendingSharedFmla = false;
+}
+
+ApiTokenSequence SheetDataBuffer::resolveSharedFormula( const BinAddress& rMapKey ) const
+{
+ sal_Int32 nTokenIndex = ContainerHelper::getMapElement( maSharedFormulas, rMapKey, -1 );
+ return (nTokenIndex >= 0) ? getFormulaParser().convertNameToFormula( nTokenIndex ) : ApiTokenSequence();
+}
+
+void SheetDataBuffer::finalizeArrayFormula( const CellRangeAddress& 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 CellRangeAddress& rRange, const DataTableModel& rModel ) const
+{
+ sal_Int16 nSheet = getSheetIndex();
+ bool bOk = false;
+ if( !rModel.mbRef1Deleted && (rModel.maRef1.getLength() > 0) && (rRange.StartColumn > 0) && (rRange.StartRow > 0) )
+ {
+ CellRangeAddress aOpRange = rRange;
+ CellAddress aRef1;
+ if( getAddressConverter().convertToCellAddress( aRef1, rModel.maRef1, nSheet, true ) ) try
+ {
+ if( rModel.mb2dTable )
+ {
+ CellAddress aRef2;
+ if( !rModel.mbRef2Deleted && getAddressConverter().convertToCellAddress( aRef2, rModel.maRef2, nSheet, true ) )
+ {
+ // API call expects input values inside operation range
+ --aOpRange.StartColumn;
+ --aOpRange.StartRow;
+ // formula range is top-left cell of operation range
+ CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn, aOpRange.StartRow, aOpRange.StartColumn, aOpRange.StartRow );
+ // set multiple operation
+ Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
+ xMultOp->setTableOperation( aFormulaRange, TableOperationMode_BOTH, aRef2, aRef1 );
+ bOk = true;
+ }
+ }
+ else if( rModel.mbRowTable )
+ {
+ // formula range is column to the left of operation range
+ CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn - 1, aOpRange.StartRow, aOpRange.StartColumn - 1, aOpRange.EndRow );
+ // API call expects input values (top row) inside operation range
+ --aOpRange.StartRow;
+ // set multiple operation
+ Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
+ xMultOp->setTableOperation( aFormulaRange, TableOperationMode_ROW, aRef1, aRef1 );
+ bOk = true;
+ }
+ else
+ {
+ // formula range is row above operation range
+ CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn, aOpRange.StartRow - 1, aOpRange.EndColumn, aOpRange.StartRow - 1 );
+ // API call expects input values (left column) inside operation range
+ --aOpRange.StartColumn;
+ // set multiple operation
+ Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
+ xMultOp->setTableOperation( aFormulaRange, TableOperationMode_COLUMN, aRef1, aRef1 );
+ bOk = true;
+ }
+ }
+ catch( Exception& )
+ {
+ }
+ }
+
+ // on error: fill cell range with #REF! error codes
+ if( !bOk ) try
+ {
+ Reference< XCellRangeData > xCellRangeData( getCellRange( rRange ), UNO_QUERY_THROW );
+ size_t nWidth = static_cast< size_t >( rRange.EndColumn - rRange.StartColumn + 1 );
+ size_t nHeight = static_cast< size_t >( rRange.EndRow - rRange.StartRow + 1 );
+ Matrix< Any > aErrorCells( nWidth, nHeight, Any( getFormulaParser().convertErrorToFormula( BIFF_ERR_REF ) ) );
+ xCellRangeData->setDataArray( ContainerHelper::matrixToSequenceSequence( aErrorCells ) );
+ }
+ catch( Exception& )
+ {
+ }
+}
+
+void SheetDataBuffer::setCellFormat( const CellModel& rModel, sal_Int32 nNumFmtId )
+{
+ if( (rModel.mnXfId >= 0) || (nNumFmtId >= 0) )
+ {
+ // try to merge existing ranges and to write some formatting properties
+ if( !maXfIdRanges.empty() )
+ {
+ // get row index of last inserted cell
+ sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow;
+ // row changed - try to merge ranges of last row with existing ranges
+ if( rModel.maCellAddr.Row != nLastRow )
+ {
+ mergeXfIdRanges();
+ // write format properties of all ranges above last row and remove them
+ XfIdRangeMap::iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end();
+ while( aIt != aEnd )
+ {
+ // check that range cannot be merged with current row, and that range is not in cached row range
+ if( (aIt->second.maRange.EndRow < nLastRow) && !maXfIdRowRange.intersects( aIt->second.maRange ) )
+ {
+ writeXfIdRangeProperties( aIt->second );
+ maXfIdRanges.erase( aIt++ );
+ }
+ else
+ ++aIt;
+ }
+ }
+ }
+
+ // try to expand last existing range, or create new range entry
+ if( maXfIdRanges.empty() || !maXfIdRanges.rbegin()->second.tryExpand( rModel.maCellAddr, rModel.mnXfId, nNumFmtId ) )
+ maXfIdRanges[ BinAddress( rModel.maCellAddr ) ].set( rModel.maCellAddr, rModel.mnXfId, nNumFmtId );
+
+ // update merged ranges for 'center across selection' and 'fill'
+ if( const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get() )
+ {
+ 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.push_back( MergedRange( rModel.maCellAddr, nHorAlign ) );
+ else if( !maCenterFillRanges.empty() )
+ maCenterFillRanges.rbegin()->tryExpand( rModel.maCellAddr, nHorAlign );
+ }
+ }
+ }
+}
+
+void SheetDataBuffer::writeXfIdRowRangeProperties( const XfIdRowRange& rXfIdRowRange ) const
+{
+ if( (rXfIdRowRange.maRowRange.mnLast >= 0) && (rXfIdRowRange.mnXfId >= 0) )
+ {
+ AddressConverter& rAddrConv = getAddressConverter();
+ CellRangeAddress aRange( getSheetIndex(), 0, rXfIdRowRange.maRowRange.mnFirst, rAddrConv.getMaxApiAddress().Column, rXfIdRowRange.maRowRange.mnLast );
+ if( rAddrConv.validateCellRange( aRange, true, false ) )
+ {
+ PropertySet aPropSet( getCellRange( aRange ) );
+ getStyles().writeCellXfToPropertySet( aPropSet, rXfIdRowRange.mnXfId );
+ }
+ }
+}
+
+void SheetDataBuffer::writeXfIdRangeProperties( const XfIdRange& rXfIdRange ) const
+{
+ StylesBuffer& rStyles = getStyles();
+ PropertyMap aPropMap;
+ if( rXfIdRange.mnXfId >= 0 )
+ rStyles.writeCellXfToPropertyMap( aPropMap, rXfIdRange.mnXfId );
+ if( rXfIdRange.mnNumFmtId >= 0 )
+ rStyles.writeNumFmtToPropertyMap( aPropMap, rXfIdRange.mnNumFmtId );
+ PropertySet aPropSet( getCellRange( rXfIdRange.maRange ) );
+ aPropSet.setProperties( aPropMap );
+}
+
+void SheetDataBuffer::mergeXfIdRanges()
+{
+ if( !maXfIdRanges.empty() )
+ {
+ // get row index of last range
+ sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow;
+ // process all ranges located in the same row of the last range
+ XfIdRangeMap::iterator aMergeIt = maXfIdRanges.end();
+ while( (aMergeIt != maXfIdRanges.begin()) && ((--aMergeIt)->second.maRange.StartRow == nLastRow) )
+ {
+ const XfIdRange& rMergeXfIdRange = aMergeIt->second;
+ // try to find a range that can be merged with rMergeRange
+ bool bFound = false;
+ for( XfIdRangeMap::iterator aIt = maXfIdRanges.begin(); !bFound && (aIt != aMergeIt); ++aIt )
+ if( (bFound = aIt->second.tryMerge( rMergeXfIdRange )) == true )
+ maXfIdRanges.erase( aMergeIt++ );
+ }
+ }
+}
+
+void SheetDataBuffer::finalizeMergedRange( const CellRangeAddress& rRange )
+{
+ bool bMultiCol = rRange.StartColumn < rRange.EndColumn;
+ bool bMultiRow = rRange.StartRow < rRange.EndRow;
+
+ if( bMultiCol || bMultiRow ) try
+ {
+ // merge the cell range
+ Reference< XMergeable > xMerge( getCellRange( rRange ), UNO_QUERY_THROW );
+ xMerge->merge( sal_True );
+
+ // if merging this range worked (no overlapping merged ranges), update cell borders
+ Reference< XCell > xTopLeft( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.StartRow ) ), UNO_SET_THROW );
+ PropertySet aTopLeftProp( xTopLeft );
+
+ // copy right border of top-right cell to right border of top-left cell
+ if( bMultiCol )
+ {
+ PropertySet aTopRightProp( getCell( CellAddress( getSheetIndex(), rRange.EndColumn, rRange.StartRow ) ) );
+ BorderLine aLine;
+ if( aTopRightProp.getProperty( aLine, PROP_RightBorder ) )
+ aTopLeftProp.setProperty( PROP_RightBorder, aLine );
+ }
+
+ // copy bottom border of bottom-left cell to bottom border of top-left cell
+ if( bMultiRow )
+ {
+ PropertySet aBottomLeftProp( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.EndRow ) ) );
+ BorderLine aLine;
+ if( aBottomLeftProp.getProperty( aLine, PROP_BottomBorder ) )
+ aTopLeftProp.setProperty( PROP_BottomBorder, aLine );
+ }
+
+ // #i93609# merged range in a single row: test if manual row height is needed
+ if( !bMultiRow )
+ {
+ bool bTextWrap = aTopLeftProp.getBoolProperty( PROP_IsTextWrapped );
+ if( !bTextWrap && (xTopLeft->getType() == CellContentType_TEXT) )
+ {
+ Reference< XText > xText( xTopLeft, UNO_QUERY );
+ bTextWrap = xText.is() && (xText->getString().indexOf( '\x0A' ) >= 0);
+ }
+ if( bTextWrap )
+ setManualRowHeight( rRange.StartRow );
+ }
+ }
+ catch( Exception& )
+ {
+ }
+}
+
+// ============================================================================
+
+} // namespace xls
+} // namespace oox